CORD-537 Added flow rules for vSG connectivity

- Added Q_IN_Q table
- Added flow rules for vSG connectivity
- Changed to listen port update event from Neutron to update vSG IPs

Change-Id: I227ba7a91e90ec0752481ebf623b4e848d585265
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
index e27aa74..e071550 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtn.java
@@ -38,7 +38,6 @@
 import org.onosproject.net.HostId;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.Port;
-import org.onosproject.net.SparseAnnotations;
 import org.onosproject.net.config.ConfigFactory;
 import org.onosproject.net.config.NetworkConfigEvent;
 import org.onosproject.net.config.NetworkConfigListener;
@@ -137,10 +136,14 @@
             };
 
     private static final String DEFAULT_TUNNEL = "vxlan";
-    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
     private static final String SERVICE_ID = "serviceId";
-    private static final String LOCATION_IP = "locationIp";
     private static final String OPENSTACK_VM_ID = "openstackVmId";
+    private static final String OPENSTACK_PORT_ID = "openstackPortId";
+    private static final String DATA_PLANE_IP = "dataPlaneIp";
+    private static final String DATA_PLANE_INTF = "dataPlaneIntf";
+    private static final String S_TAG = "stag";
+
+    private static final Ip4Address DEFAULT_DNS = Ip4Address.valueOf("8.8.8.8");
 
     private final ExecutorService eventExecutor =
             newSingleThreadScheduledExecutor(groupedThreads("onos/cordvtn", "event-handler"));
@@ -263,18 +266,24 @@
         }
 
         Set<IpAddress> ip = Sets.newHashSet(vPort.fixedIps().values());
-        SparseAnnotations annotations = DefaultAnnotations.builder()
-                .set(OPENSTACK_VM_ID, vPort.deviceId())
+        DefaultAnnotations.Builder annotations = DefaultAnnotations.builder()
                 .set(SERVICE_ID, vPort.networkId())
-                .set(LOCATION_IP, node.dpIp().ip().toString())
-                .build();
+                .set(OPENSTACK_VM_ID, vPort.deviceId())
+                .set(OPENSTACK_PORT_ID, vPort.id())
+                .set(DATA_PLANE_IP, node.dpIp().ip().toString())
+                .set(DATA_PLANE_INTF, node.dpIntf());
+
+        String serviceVlan = getServiceVlan(vPort);
+        if (serviceVlan != null) {
+            annotations.set(S_TAG, serviceVlan);
+        }
 
         HostDescription hostDesc = new DefaultHostDescription(
                 mac,
                 VlanId.NONE,
                 new HostLocation(connectPoint, System.currentTimeMillis()),
                 ip,
-                annotations);
+                annotations.build());
 
         hostProvider.hostDetected(hostId, hostDesc, false);
     }
@@ -294,6 +303,20 @@
         hostProvider.hostVanished(host.id());
     }
 
+    @Override
+    public void updateVirtualSubscriberGateways(HostId vSgHostId, String serviceVlan,
+                                                Set<IpAddress> vSgIps) {
+        Host vSgVm = hostService.getHost(vSgHostId);
+
+        if (vSgVm == null || !vSgVm.annotations().value(S_TAG).equals(serviceVlan)) {
+            log.debug("Invalid vSG updates for {}", serviceVlan);
+            return;
+        }
+
+        log.info("Updates vSGs in {} with {}", vSgVm.id(), vSgIps.toString());
+        ruleInstaller.populateSubscriberGatewayRules(vSgVm, vSgIps);
+    }
+
     /**
      * Returns CordService by service ID.
      *
@@ -357,10 +380,11 @@
      * Returns IP address for tunneling for a given host.
      *
      * @param host host
-     * @return ip address
+     * @return ip address, or null
      */
     private IpAddress getTunnelIp(Host host) {
-        return IpAddress.valueOf(host.annotations().value(LOCATION_IP));
+        String ip = host.annotations().value(DATA_PLANE_IP);
+        return ip == null ? null : IpAddress.valueOf(ip);
     }
 
     /**
@@ -374,6 +398,22 @@
     }
 
     /**
+     * Returns s-tag from a given OpenStack port.
+     *
+     * @param vPort openstack port
+     * @return s-tag string
+     */
+    private String getServiceVlan(OpenstackPort vPort) {
+        checkNotNull(vPort);
+
+        if (vPort.name() != null && vPort.name().startsWith(S_TAG)) {
+            return vPort.name().split("-")[1];
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Returns hosts associated with a given OpenStack network.
      *
      * @param vNet openstack network
@@ -395,6 +435,30 @@
     }
 
     /**
+     * Returns public ip addresses of vSGs running inside a give vSG host.
+     *
+     * @param vSgHost vSG host
+     * @return set of ip address, or empty set
+     */
+    private Set<IpAddress> getSubscriberGatewayIps(Host vSgHost) {
+        String vPortId = vSgHost.annotations().value(OPENSTACK_PORT_ID);
+        String serviceVlan = vSgHost.annotations().value(S_TAG);
+
+        OpenstackPort vPort = openstackService.port(vPortId);
+        if (vPort == null) {
+            log.warn("Failed to get OpenStack port {} for VM {}", vPortId, vSgHost.id());
+            return Sets.newHashSet();
+        }
+
+        if (!serviceVlan.equals(getServiceVlan(vPort))) {
+            log.error("Host({}) s-tag does not match with vPort s-tag", vSgHost.id());
+            return Sets.newHashSet();
+        }
+
+        return vPort.allowedAddressPairs().keySet();
+    }
+
+    /**
      * Registers static DHCP lease for a given host.
      *
      * @param host host
@@ -452,8 +516,13 @@
             arpProxy.sendGratuitousArp(service.serviceIp(), gatewayMac, Sets.newHashSet(host));
         }
 
-        ruleInstaller.populateBasicConnectionRules(host, getTunnelIp(host), vNet);
         registerDhcpLease(host, service);
+        ruleInstaller.populateBasicConnectionRules(host, getTunnelIp(host), vNet);
+
+        if (host.annotations().value(S_TAG) != null) {
+            log.debug("vSG VM detected {}", host.id());
+            ruleInstaller.populateSubscriberGatewayRules(host, getSubscriberGatewayIps(host));
+        }
     }
 
     /**
@@ -468,7 +537,7 @@
         }
 
         String vNetId = host.annotations().value(SERVICE_ID);
-        OpenstackNetwork vNet = openstackService.network(host.annotations().value(SERVICE_ID));
+        OpenstackNetwork vNet = openstackService.network(vNetId);
         if (vNet == null) {
             log.warn("Failed to get OpenStack network {} for VM {}({}).",
                      vNetId,
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
index d1a0f24..877ec30 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnRuleInstaller.java
@@ -25,6 +25,7 @@
 import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
 import org.onlab.util.ItemNotFoundException;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.DefaultGroupId;
@@ -59,6 +60,7 @@
 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.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
 import org.onosproject.net.group.DefaultGroupBucket;
 import org.onosproject.net.group.DefaultGroupDescription;
@@ -87,6 +89,7 @@
 import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
 import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.ETH_DST;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType.VLAN_PUSH;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -96,21 +99,27 @@
 
     protected final Logger log = getLogger(getClass());
 
-    private static final String PORT_NAME = "portName";
     private static final int TABLE_FIRST = 0;
     private static final int TABLE_IN_PORT = 1;
     private static final int TABLE_ACCESS_TYPE = 2;
     private static final int TABLE_IN_SERVICE = 3;
     private static final int TABLE_DST_IP = 4;
     private static final int TABLE_TUNNEL_IN = 5;
+    private static final int TABLE_Q_IN_Q = 6;
 
     private static final int MANAGEMENT_PRIORITY = 55000;
+    private static final int VSG_PRIORITY = 55000;
     private static final int HIGH_PRIORITY = 50000;
     private static final int DEFAULT_PRIORITY = 5000;
     private static final int LOW_PRIORITY = 4000;
     private static final int LOWEST_PRIORITY = 0;
 
     private static final int VXLAN_UDP_PORT = 4789;
+    private static final VlanId VLAN_WAN = VlanId.vlanId((short) 500);
+
+    private static final String PORT_NAME = "portName";
+    private static final String DATA_PLANE_INTF = "dataPlaneIntf";
+    private static final String S_TAG = "stag";
 
     private final ApplicationId appId;
     private final FlowRuleService flowRuleService;
@@ -163,6 +172,7 @@
         processFirstTable(deviceId, dpPort, dpIp);
         processInPortTable(deviceId, tunnelPort, dpPort);
         processAccessTypeTable(deviceId, dpPort);
+        processQInQTable(deviceId, dpPort);
     }
 
     /**
@@ -406,6 +416,10 @@
         DeviceId deviceId = host.location().deviceId();
         IpAddress hostIp = host.ipAddresses().stream().findFirst().get();
 
+        if (!mastershipService.isLocalMaster(deviceId)) {
+            return;
+        }
+
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_ARP)
                 .matchArpTpa(mService.serviceIp().getIp4Address())
@@ -502,6 +516,10 @@
     public void removeManagementNetworkRules(Host host, CordService mService) {
         checkNotNull(mService);
 
+        if (!mastershipService.isLocalMaster(host.location().deviceId())) {
+            return;
+        }
+
         for (FlowRule flowRule : flowRuleService.getFlowRulesById(appId)) {
             if (flowRule.deviceId().equals(host.location().deviceId())) {
                 PortNumber port = getOutputFromTreatment(flowRule);
@@ -515,6 +533,113 @@
     }
 
     /**
+     * Populates rules for vSG VM.
+     *
+     * @param vSgHost vSG host
+     * @param vSgIps set of ip addresses of vSGs running inside the vSG VM
+     */
+    public void populateSubscriberGatewayRules(Host vSgHost, Set<IpAddress> vSgIps) {
+        VlanId serviceVlan = getServiceVlan(vSgHost);
+        PortNumber dpPort = getDpPort(vSgHost);
+
+        if (serviceVlan == null || dpPort == null) {
+            log.warn("Failed to populate rules for vSG VM {}", vSgHost.id());
+            return;
+        }
+
+        // for traffics with s-tag, strip the tag and take through the vSG VM
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(serviceVlan)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setOutput(vSgHost.location().port())
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(DEFAULT_PRIORITY)
+                .forDevice(vSgHost.location().deviceId())
+                .forTable(TABLE_Q_IN_Q)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, 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(vSgHost.location().port())
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .pushVlan()
+                .setVlanId(serviceVlan)
+                .setOutput(dpPort)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(LOW_PRIORITY)
+                .forDevice(vSgHost.location().deviceId())
+                .forTable(TABLE_Q_IN_Q)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        // for traffic coming from WAN, tag 500 and take through the vSG VM
+        // based on destination ip
+        vSgIps.stream().forEach(ip -> {
+            TrafficSelector downstream = DefaultTrafficSelector.builder()
+                    .matchEthType(Ethernet.TYPE_IPV4)
+                    .matchIPDst(ip.toIpPrefix())
+                    .build();
+
+            TrafficTreatment downstreamTreatment = DefaultTrafficTreatment.builder()
+                    .pushVlan()
+                    .setVlanId(VLAN_WAN)
+                    .setEthDst(vSgHost.mac())
+                    .setOutput(vSgHost.location().port())
+                    .build();
+
+            FlowRule downstreamFlowRule = DefaultFlowRule.builder()
+                    .fromApp(appId)
+                    .withSelector(downstream)
+                    .withTreatment(downstreamTreatment)
+                    .withPriority(DEFAULT_PRIORITY)
+                    .forDevice(vSgHost.location().deviceId())
+                    .forTable(TABLE_DST_IP)
+                    .makePermanent()
+                    .build();
+
+            processFlowRule(true, downstreamFlowRule);
+        });
+
+        // remove downstream flow rules for the vSG not shown in vSgIps
+        for (FlowRule rule : flowRuleService.getFlowRulesById(appId)) {
+            if (!rule.deviceId().equals(vSgHost.location().deviceId())) {
+                continue;
+            }
+            PortNumber output = getOutputFromTreatment(rule);
+            if (output == null || !output.equals(vSgHost.location().port()) ||
+                    !isVlanPushFromTreatment(rule)) {
+                continue;
+            }
+
+            IpPrefix dstIp = getDstIpFromSelector(rule);
+            if (dstIp != null && !vSgIps.contains(dstIp.address())) {
+                processFlowRule(false, rule);
+            }
+        }
+    }
+
+    /**
      * Populates default rules on the first table.
      * It includes the rules for shuttling vxlan-encapped packets between ovs and
      * linux stack,and external network connectivity.
@@ -596,6 +721,7 @@
         selector = DefaultTrafficSelector.builder()
                 .matchInPort(dpPort)
                 .matchEthType(Ethernet.TYPE_ARP)
+                .matchArpTpa(dpIp.getIp4Address())
                 .build();
 
         treatment = DefaultTrafficTreatment.builder()
@@ -633,6 +759,27 @@
                 .build();
 
         processFlowRule(true, flowRule);
+
+        // take all vlan tagged packet to the Q_IN_Q table
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VlanId.ANY)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .transition(TABLE_Q_IN_Q)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(VSG_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_FIRST)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
     }
 
     /**
@@ -716,6 +863,57 @@
     }
 
     /**
+     * Populates default rules for Q_IN_Q table.
+     *
+     * @param deviceId device id
+     * @param dpPort data plane interface port number
+     */
+    private void processQInQTable(DeviceId deviceId, PortNumber dpPort) {
+        // for traffic going out to WAN, strip vid 500 and take through data plane interface
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_WAN)
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .popVlan()
+                .setOutput(dpPort)
+                .build();
+
+        FlowRule flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(DEFAULT_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_Q_IN_Q)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+
+        selector = DefaultTrafficSelector.builder()
+                .matchVlanId(VLAN_WAN)
+                .matchEthType(Ethernet.TYPE_ARP)
+                .build();
+
+        treatment = DefaultTrafficTreatment.builder()
+                .setOutput(PortNumber.CONTROLLER)
+                .build();
+
+        flowRule = DefaultFlowRule.builder()
+                .fromApp(appId)
+                .withSelector(selector)
+                .withTreatment(treatment)
+                .withPriority(HIGH_PRIORITY)
+                .forDevice(deviceId)
+                .forTable(TABLE_Q_IN_Q)
+                .makePermanent()
+                .build();
+
+        processFlowRule(true, flowRule);
+    }
+
+    /**
      * Populates rules for local in port in IN_PORT table.
      * Flows from a given in port, whose source IP is service IP transition
      * to DST_TYPE table. Other flows transition to IN_SERVICE table.
@@ -1033,8 +1231,8 @@
      */
     private PortNumber getTunnelPort(DeviceId deviceId) {
         Port port = deviceService.getPorts(deviceId).stream()
-                    .filter(p -> p.annotations().value(PORT_NAME).contains(tunnelType))
-                    .findFirst().orElse(null);
+                .filter(p -> p.annotations().value(PORT_NAME).contains(tunnelType))
+                .findFirst().orElse(null);
 
         return port == null ? null : port.number();
     }
@@ -1048,13 +1246,34 @@
      */
     private PortNumber getDpPort(DeviceId deviceId, String dpIntf) {
         Port port = deviceService.getPorts(deviceId).stream()
-                    .filter(p -> p.annotations().value(PORT_NAME).contains(dpIntf) &&
-                            p.isEnabled())
-                    .findFirst().orElse(null);
+                .filter(p -> p.annotations().value(PORT_NAME).contains(dpIntf) &&
+                        p.isEnabled())
+                .findFirst().orElse(null);
 
         return port == null ? null : port.number();
     }
 
+    /** Returns data plane interface port number of a given host.
+     *
+     * @param host host
+     * @return port number, or null
+     */
+    private PortNumber getDpPort(Host host) {
+        String portName = host.annotations().value(DATA_PLANE_INTF);
+        return portName == null ? null : getDpPort(host.location().deviceId(), portName);
+    }
+
+    /**
+     * Returns service vlan from a given host.
+     *
+     * @param host host
+     * @return vlan id, or null
+     */
+    private VlanId getServiceVlan(Host host) {
+        String serviceVlan = host.annotations().value(S_TAG);
+        return serviceVlan == null ? null : VlanId.vlanId(Short.parseShort(serviceVlan));
+    }
+
     /**
      * Returns the inport from a given flow rule if the rule contains the match of it.
      *
@@ -1171,7 +1390,7 @@
      */
     private PortNumber getOutputFromTreatment(FlowRule flowRule) {
         Instruction instruction = flowRule.treatment().allInstructions().stream()
-                .filter(inst -> inst instanceof  Instructions.OutputInstruction)
+                .filter(inst -> inst instanceof Instructions.OutputInstruction)
                 .findFirst()
                 .orElse(null);
 
@@ -1183,6 +1402,22 @@
     }
 
     /**
+     * Returns if a given flow rule has vlan push instruction or not.
+     *
+     * @param flowRule flow rule
+     * @return true if it includes vlan push, or false
+     */
+    private boolean isVlanPushFromTreatment(FlowRule flowRule) {
+        Instruction instruction = flowRule.treatment().allInstructions().stream()
+                .filter(inst -> inst instanceof L2ModificationInstruction)
+                .filter(inst -> ((L2ModificationInstruction) inst).subtype().equals(VLAN_PUSH))
+                .findAny()
+                .orElse(null);
+
+        return instruction != null;
+    }
+
+    /**
      * Creates a new group for a given service.
      *
      * @param deviceId device id to create a group
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnService.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnService.java
index 87125d4..ead644f 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnService.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/CordVtnService.java
@@ -15,7 +15,11 @@
  */
 package org.onosproject.cordvtn;
 
+import org.onlab.packet.IpAddress;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+
+import java.util.Set;
 
 /**
  * Service for provisioning overlay virtual networks on compute nodes.
@@ -57,4 +61,14 @@
      * @param pServiceId id of the service which provide dependency
      */
     void removeServiceDependency(CordServiceId tServiceId, CordServiceId pServiceId);
+
+    /**
+     * Updates virtual service gateways.
+     *
+     * @param vSgHost host id of vSG host
+     * @param serviceVlan service vlan id
+     * @param vSgIps set of ip address of vSGs running in this vSG host
+     */
+    void updateVirtualSubscriberGateways(HostId vSgHost, String serviceVlan,
+                                         Set<IpAddress> vSgIps);
 }
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/RemoteIpCommandUtil.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/RemoteIpCommandUtil.java
index 29c920f..eeb7b54 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/RemoteIpCommandUtil.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/RemoteIpCommandUtil.java
@@ -220,7 +220,7 @@
             return null;
         }
 
-        log.debug("Execute command {} to {}", command, session.getHost());
+        log.trace("Execute command {} to {}", command, session.getHost());
 
         try {
             Channel channel = session.openChannel("exec");
diff --git a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
index 5fa60e5..cd8f555 100644
--- a/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
+++ b/apps/cordvtn/src/main/java/org/onosproject/cordvtn/rest/NeutronMl2PortsWebResource.java
@@ -15,6 +15,13 @@
  */
 package org.onosproject.cordvtn.rest;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.Sets;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onosproject.cordvtn.CordVtnService;
+import org.onosproject.net.HostId;
 import org.onosproject.rest.AbstractWebResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -29,16 +36,29 @@
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import java.io.InputStream;
+import java.util.Set;
+
 
 /**
  * Dummy Neutron ML2 mechanism driver.
- * It just returns OK for ports resource requests.
+ * It just returns OK for ports resource requests except for the port update.
  */
 @Path("ports")
 public class NeutronMl2PortsWebResource extends AbstractWebResource {
     protected final Logger log = LoggerFactory.getLogger(getClass());
     private static final String PORTS_MESSAGE = "Received ports %s";
 
+    private static final String PORT = "port";
+    private static final String DEVICE_ID = "device_id";
+    private static final String NAME = "name";
+    private static final String MAC_ADDRESS = "mac_address";
+    private static final String ADDRESS_PAIRS = "allowed_address_pairs";
+    private static final String IP_ADDERSS = "ip_address";
+    private static final String STAG_PREFIX = "stag-";
+    private static final int STAG_BEGIN_INDEX = 5;
+
+    private final CordVtnService service = get(CordVtnService.class);
+
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
@@ -53,6 +73,35 @@
     @Produces(MediaType.APPLICATION_JSON)
     public Response updatePorts(@PathParam("id") String id, InputStream input) {
         log.debug(String.format(PORTS_MESSAGE, "update"));
+
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode jsonNode = mapper.readTree(input).get(PORT);
+            log.trace("{}", jsonNode.toString());
+
+            String deviceId = jsonNode.path(DEVICE_ID).asText();
+            String name = jsonNode.path(NAME).asText();
+            if (deviceId.isEmpty() || name.isEmpty() || !name.startsWith(STAG_PREFIX)) {
+                // ignore all updates other than allowed address pairs
+                return Response.status(Response.Status.OK).build();
+            }
+
+            // this is allowed address pairs updates
+            MacAddress mac = MacAddress.valueOf(jsonNode.path(MAC_ADDRESS).asText());
+            Set<IpAddress> vSgIps = Sets.newHashSet();
+            jsonNode.path(ADDRESS_PAIRS).forEach(addrPair -> {
+                IpAddress ip = IpAddress.valueOf(addrPair.path(IP_ADDERSS).asText());
+                vSgIps.add(ip);
+            });
+
+            service.updateVirtualSubscriberGateways(
+                    HostId.hostId(mac),
+                    name.substring(STAG_BEGIN_INDEX),
+                    vSgIps);
+        } catch (Exception e) {
+            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+        }
+
         return Response.status(Response.Status.OK).build();
     }