Tunnel loadbalancing policy: phase2 support
diff --git a/src/main/java/net/floodlightcontroller/core/IOF13Switch.java b/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
index 3eaf58d..63e90c4 100644
--- a/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
+++ b/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
@@ -9,6 +9,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import net.floodlightcontroller.core.IOF13Switch.GroupChain;
+import net.floodlightcontroller.core.IOF13Switch.GroupChainParams;
 import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
 import net.onrc.onos.core.util.Dpid;
 import net.onrc.onos.core.util.PortNumber;
@@ -205,7 +207,7 @@
      * ports. This API can be used by user to create groups for a tunnel based
      * policy routing scenario. NOTE: This API can not be used if a group to be
      * created with different label stacks for each port in the given set of
-     * ports. Use XXX API for this purpose
+     * ports. Use createGroupChain API for this purpose
      *
      * @param labelStack list of router segment Ids to be pushed. Can be empty.
      *        labelStack is processed from left to right with leftmost
@@ -298,7 +300,51 @@
 		}
     }
     
-    public List<GroupChain> createGroupChain(List<GroupChainParams> groupChainParams);
+    /**
+     * Create a group chain with the specified label stack for each given set of
+     * ports. This API can be used by user to create groups for a tunnel based
+     * policy routing scenario. 
+     *
+     * @param groupChainParams list of ports along with label stacks to be 
+     *        applied. label stack can be empty.
+     *        labelStack is processed from left to right with leftmost
+     *        representing the outermost label and rightmost representing
+     *        innermost label to be pushed
+     *        List of ports on this switch to get to the first router in
+     *        the labelStack
+     * @return List of GroupChain objects
+     */
+    public List<GroupChain> createGroupChain(
+    		List<GroupChainParams> groupChainParams);
+    /**
+     * Add a new entry to an existing group chain
+     *
+     * @param currentGroupChainList Existing group chain to which this 
+     *        new entry to be added
+     * @param groupChainParams list of ports along with label stacks to be 
+     *        applied. label stack can be empty.
+     *        labelStack is processed from left to right with leftmost
+     *        representing the outermost label and rightmost representing
+     *        innermost label to be pushed
+     *        List of ports on this switch to get to the first router in
+     *        the labelStack
+     * @return Modified list of GroupChain objects
+     */
+    public List<GroupChain> addNewEntryToGroupChain(
+    		List<GroupChain> currentGroupChainList, 
+    		List<GroupChainParams> groupChainParams);
+    /**
+     * Remove one or more buckes from the given innermost group those 
+     * point to the list of groups specified in chainedGroups list
+     *
+     * @param innermostGroupId The innermost group from which buckets, 
+     *        pointing to the groupIds in the chainedGroups list param, 
+     *        to be deleted
+     * @param chainedGroups list of groupIds
+     * @return True or False depending on the result of the removal
+     */
+    public boolean removeOutGroupBucketsFromGroup(
+    		int innermostGroupId, List<Integer> chainedGroups);
 
     /**
      * Remove the specified group
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java b/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java
index 9c49eba..10d81e4 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java
@@ -31,12 +31,12 @@
     /**
      * Create a tunnelset for policy routing.
      *
-     * @param tunnelId ID for the tunnel
-     * @param labelIds Node label IDs for the tunnel
+     * @param tunnelsetId ID for the tunnelset
+     * @param tunnelsetParams tunnelset REST params
      *
-     * @return "true/false" depending tunnel creation status
+     * @return "true/false" depending tunnelset creation status
      */
-    public boolean createTunnelset(String tunnelsetId, 
+    public boolean createUpdateTunnelset(String tunnelsetId, 
     			SegmentRouterTunnelsetRESTParams tunnelsetParams);
 
     /**
@@ -49,6 +49,15 @@
     public removeTunnelMessages removeTunnel(String tunnelId);
 
     /**
+     * Remove a Segment Routing tunnelset given a tunnel Id.
+     *
+     * @param tunnelsetId ID for the tunnelset
+     *
+     * @return "true/false" depending tunnelset deletion status
+     */
+    public removeTunnelMessages removeTunnelset(String tunnelsetId);
+
+    /**
      * Create a policy for policy based segment routing
      *
      * @param pid Unique Policy Identifier
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java
index 921fd35..857a43e 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java
@@ -1380,22 +1380,114 @@
         }
     }
     
-    public boolean createTunnelset(String tunnelsetId, 
+    public boolean createUpdateTunnelset(String tunnelsetId, 
 			SegmentRouterTunnelsetRESTParams tunnelsetParams) {
-        SegmentRoutingTunnelset srTunnelset =
-                new SegmentRoutingTunnelset(this, tunnelsetParams);
-        if (srTunnelset.createTunnelSet()) {
-        	tunnelsetTable.put(tunnelsetId, srTunnelset);
-        	HashMap<String,SegmentRoutingTunnel> tunnelsetTunnels = 
-        						srTunnelset.getTunnels();
-        	for (String tunnelId:tunnelsetTunnels.keySet())
-        		tunnelTable.put(tunnelId, tunnelsetTunnels.get(tunnelId));
-            return true;
-        }
-        else {
-            log.warn("Failed to create a tunnel");
+    	SegmentRoutingTunnelset srTunnelset = tunnelsetTable.get(tunnelsetId);
+		if (((tunnelsetParams.getTunnelParams() == null) &&
+			  (tunnelsetParams.getRemove_tunnel_params() == null)) ||
+			((tunnelsetParams.getRemove_tunnel_params() != null) &&
+						(srTunnelset == null))){
+            log.warn("Invalid input to create/update tunnelset");
             return false;
-        }
+		}
+
+		if (tunnelsetParams.getTunnelParams() != null) {
+        	for (SegmentRouterTunnelRESTParams tunnelParams:tunnelsetParams.getTunnelParams()) {
+        		if (tunnelTable.get(tunnelParams.getTunnel_id()) != null) {
+        			/* A tunnel already exists with this Id */
+                    log.warn("Invalid input to create/update tunnelset: "
+                    		+ "A tunnel already exist with the same Id");
+                    return false;
+        		}
+        	}
+		}
+		if (tunnelsetParams.getRemove_tunnel_params() != null) {
+        	for (String tunnelId:tunnelsetParams.getRemove_tunnel_params()) {
+    			if (srTunnelset.getTunnels().get(tunnelId) == null) {
+        			/* tunnel does not exist in this tunnel set */
+                    log.warn("Invalid input to create/update tunnelset: "
+                    		+ "tunnelId {} is not part of tunnelset {}",
+                    		tunnelId, tunnelsetId);
+                    return false;
+        		}
+        	}
+		}
+    	
+    	if (srTunnelset == null) {
+	        srTunnelset =
+	                new SegmentRoutingTunnelset(this, tunnelsetParams);
+	        if (srTunnelset.createTunnelSet()) {
+	        	tunnelsetTable.put(tunnelsetId, srTunnelset);
+	        	HashMap<String,SegmentRoutingTunnel> tunnelsetTunnels = 
+	        						srTunnelset.getTunnels();
+	        	for (String tunnelId:tunnelsetTunnels.keySet())
+	        		tunnelTable.put(tunnelId, tunnelsetTunnels.get(tunnelId));
+	            return true;
+	        }
+	        else {
+	            log.warn("Failed to create a tunnel");
+	            return false;
+	        }
+    	}
+    	else {
+    		/* Verify if this is an update operation on the existing tunnelset 
+    		 * - Check if it is constituent tunnel addition or deletion 
+    		 */
+    		boolean tunnelsetUpdated = false;
+    		if (tunnelsetParams.getTunnelParams() != null) {
+    			/* add new tunnels */
+                HashMap<String,SegmentRoutingTunnel> newTunnelMap = 
+                		srTunnelset.addNewTunnelsToTunnelset(tunnelsetParams);
+                if (newTunnelMap == null) {
+    	            log.warn("Failed to add new tunnels "
+    	            		+ "to tunnelset {}",tunnelsetId);
+    	            return false;
+            	}
+	        	for (String tunnelId:newTunnelMap.keySet())
+	        		tunnelTable.put(tunnelId, newTunnelMap.get(tunnelId));
+	        	tunnelsetUpdated = true;
+    		}
+    		if (tunnelsetParams.getRemove_tunnel_params() != null) {
+    			/* remove tunnels from tunnelset */
+                if (srTunnelset.removeConstituentTunnelFromTunnelset(
+                		tunnelsetParams) == false) {
+    	            log.warn("Failed to remove tunnels "
+    	            		+ "from tunnelset {}",tunnelsetId);
+    	            return false;
+            	}
+	        	tunnelsetUpdated = true;
+    		}
+    		
+            if (tunnelsetUpdated) {
+	        	/* Update the policies pointing to this tunnelset */
+	            Collection<SegmentRoutingPolicy> policies = getPoclicyTable();
+	            Iterator<SegmentRoutingPolicy> piI = policies.iterator();
+	            while(piI.hasNext()){
+	                SegmentRoutingPolicy policy = piI.next();
+	                if(policy.getType() == PolicyType.LOADBALANCE &&
+	                  (((SegmentRoutingPolicyTunnel)policy).isTunnelsetId() &&
+	                   ((SegmentRoutingPolicyTunnel)policy).
+	                   getTunnelId().equals(tunnelsetId))){
+	                	if (((SegmentRoutingPolicyTunnel)policy).
+	                			removePolicy() == true) {
+	                		if (((SegmentRoutingPolicyTunnel)policy).
+	                				createPolicy() != true) {
+		        	            log.warn("Failed to update policies "
+		        	            		+ "pointing to this tunnelset");
+		        	            return false;
+	                		}
+	                	}
+	                	else {
+	        	            log.warn("Failed to update policies "
+	        	            		+ "pointing to this tunnelset");
+	        	            return false;
+	                	}
+	                }
+	            }
+	    		return true;
+            }
+    		return false;
+    	}
     }
 
     /**
@@ -1435,7 +1527,8 @@
                 srcIp, dstIp, ipProto, srcPort, dstPort);
 
         SegmentRoutingPolicy srPolicy =
-                new SegmentRoutingPolicyTunnel(this,pid, PolicyType.TUNNEL_FLOW,
+                new SegmentRoutingPolicyTunnel(this,pid, 
+                		(isTunnelsetId)?PolicyType.LOADBALANCE:PolicyType.TUNNEL_FLOW,
                         policyMatch, priority, tid);
         ((SegmentRoutingPolicyTunnel)srPolicy).setIsTunnelsetId(isTunnelsetId);
         if (srPolicy.createPolicy()) {
@@ -1588,6 +1681,51 @@
         }
     }
 
+    /**
+     * Remove a tunnelset
+     * It removes all groups for the tunnel if the tunnelset is not used for any
+     * policy.
+     *
+     * @param tunnelId tunnel ID to remove
+     */
+    public removeTunnelMessages removeTunnelset(String tunnelsetId) {
+
+        // Check if the tunnel is used for any policy
+        for (SegmentRoutingPolicy policy: policyTable.values()) {
+            if (policy.getType() == PolicyType.LOADBALANCE) {
+            	if (((SegmentRoutingPolicyTunnel)policy).isTunnelsetId()) {
+	                String tid = ((SegmentRoutingPolicyTunnel)policy).getTunnelId();
+	                if (tid.equals(tunnelsetId)) {
+	                    log.debug("Tunnelset {} is still used for the policy {}.",
+	                    policy.getPolicyId(), tunnelsetId);
+	                    return removeTunnelMessages.ERROR_REFERENCED;
+	                }
+            	}
+            }
+        }
+
+        SegmentRoutingTunnelset tunnelset = tunnelsetTable.get(tunnelsetId);
+        if (tunnelset == null) {
+            log.warn("Tunnulset object does not exist {}", tunnelsetId);
+            return removeTunnelMessages.ERROR_TUNNEL;
+        }
+        else {
+        	HashMap<String, SegmentRoutingTunnel> constituentTunnels = tunnelset.getTunnels();
+            if (tunnelset.removeTunnelset()) {
+            	for (String tunnelId:constituentTunnels.keySet())
+            		tunnelTable.remove(tunnelId);
+                tunnelsetTable.remove(tunnelsetId);
+                log.debug("Tunnelset {} was removed successfully.", tunnelsetId);
+                //tunnelEventChannel.removeEntry(tunnelId);
+                return removeTunnelMessages.SUCCESS;
+            }
+            else {
+                log.warn("Faild in removing the tunnelset {}", tunnelsetId);
+                return removeTunnelMessages.ERROR_DRIVER;
+            }
+        }
+    }
+
 
     private void UpdatePolicyRules() {
 
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicy.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicy.java
index 7645bad..1e9b357 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicy.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicy.java
@@ -197,8 +197,8 @@
     /**
      * Update the policy rules if necessary according to the topology changes
      */
-    public void updatePolicy() {
-
+    public boolean updatePolicy() {
+    	return false;
     }
 
 }
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyAvoid.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyAvoid.java
index 835c770..2e3aeca 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyAvoid.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyAvoid.java
@@ -114,7 +114,7 @@
     }
 
     @Override
-    public void updatePolicy() {
+	public boolean updatePolicy() {
         Switch srcSwitch = srManager.getSwitch(srcDpid);
         Switch dstSwitch = srManager.getSwitch(dstDpid);
         ECMPShortestPathGraph graph = new ECMPShortestPathGraph(srcSwitch,
@@ -147,6 +147,8 @@
         else {
             log.debug("No need to update the policy {}, policyId");
         }
+        
+        return true;
     }
 
     private boolean checkIfIncluded(List<Integer> labelStack) {
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java
index e7e2026..b47617f 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java
@@ -7,6 +7,7 @@
 import net.floodlightcontroller.core.IOF13Switch.GroupChain;
 import net.onrc.onos.core.matchaction.MatchAction;
 import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
+import net.onrc.onos.core.matchaction.MatchActionOperations;
 import net.onrc.onos.core.matchaction.MatchActionOperations.Operator;
 import net.onrc.onos.core.matchaction.action.Action;
 import net.onrc.onos.core.matchaction.action.DecNwTtlAction;
@@ -52,7 +53,7 @@
     	if (isSetId) {
     		SegmentRoutingTunnelset tunnelset = 
     				srManager.getTunnelsetInfo(tunnelId);
-    		populateAclRuleToTunnelset(tunnelset);
+    		populateAclRuleToTunnelset(tunnelset, Operator.ADD);
     	}
     	else {
 	        SegmentRoutingTunnel tunnelInfo = srManager.getTunnelInfo(tunnelId);
@@ -68,18 +69,40 @@
     @Override
     public boolean removePolicy() {
 
-        SegmentRoutingTunnel tunnel = srManager.getTunnelInfo(tunnelId);
-        if (tunnel == null) {
-            log.warn("Cannot find the tunnel {} for the policy {}", tunnelId,
-                    policyId);
-            return false;
-        }
-        List<TunnelRouteInfo> routes = tunnel.getRoutes();
-        removeAclRules(routes);
+    	if (isSetId) {
+    		SegmentRoutingTunnelset tunnelset = 
+    				srManager.getTunnelsetInfo(tunnelId);
+    		/* Remove flow rules pointing to tunnelset */
+    		populateAclRuleToTunnelset(tunnelset, Operator.REMOVE);
+    	}
+    	else {
+	        SegmentRoutingTunnel tunnel = srManager.getTunnelInfo(tunnelId);
+	        if (tunnel == null) {
+	            log.warn("Cannot find the tunnel {} for the policy {}", tunnelId,
+	                    policyId);
+	            return false;
+	        }
+	        List<TunnelRouteInfo> routes = tunnel.getRoutes();
+	        removeAclRules(routes);
+    	}
 
         return true;
     }
 
+    @Override
+    public boolean updatePolicy() {
+    	if (isSetId) {
+    		/* Properties of tunnelset might have been changed. 
+    		 * Update the policy pointing to it 
+    		 */
+    		SegmentRoutingTunnelset tunnelset = 
+    				srManager.getTunnelsetInfo(tunnelId);
+    		populateAclRuleToTunnelset(tunnelset, Operator.ADD);
+    	}
+        return true;
+
+    }
+    
     /**
      * Get the Tunnel ID
      * @return tunnel ID
@@ -96,7 +119,8 @@
 		this.isSetId = isSetId;
 	}
 
-    protected void populateAclRuleToTunnelset(SegmentRoutingTunnelset tunnelset) {
+    private void populateAclRuleToTunnelset(SegmentRoutingTunnelset tunnelset, 
+    		MatchActionOperations.Operator operator) {
     	HashMap<String,List<GroupChain>> tunnelsetGroupChain = 
     			tunnelset.getTunnelsetGroupChain();
     	for (String targetSwDpid: tunnelsetGroupChain.keySet()) {
@@ -106,12 +130,15 @@
             		get(targetSwDpid).get(0).getInnermostGroupId());
             actions.add(groupAction);
     		
+            log.warn("populateAclRuleToTunnelset to tunnelset {} "
+            		+ "in sw {} for the policy {}", tunnelId,
+                    targetSwDpid, policyId);
             MatchAction matchAction = new MatchAction(
                     srManager.getMatchActionId(),
                     new SwitchPort((new Dpid(targetSwDpid)).value(), (long)0), match, priority,
                     actions);
             MatchActionOperationEntry maEntry =
-                    new MatchActionOperationEntry(Operator.ADD, matchAction);
+                    new MatchActionOperationEntry(operator, matchAction);
 
             srManager.executeMatchActionOpEntry(maEntry);
     	}
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java
index 8848714..d3e5a88 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java
@@ -88,6 +88,189 @@
 		
 		return true;
     }
+    
+    public HashMap<String,SegmentRoutingTunnel> addNewTunnelsToTunnelset(
+    		SegmentRouterTunnelsetRESTParams tunnelsetParams) {
+    	
+    	HashMap<String,SegmentRoutingTunnel> newTunnelMap = null;
+    	
+    	for (SegmentRouterTunnelRESTParams tunnelParams:tunnelsetParams.getTunnelParams()) {
+    		SegmentRoutingTunnel tunnel = new 
+    				SegmentRoutingTunnel(srManager, tunnelParams.getTunnel_id(),
+    						tunnelParams.getLabel_path(), tunnelsetId);
+    		tunnel.computeTunnelLabelStack();
+
+    	
+    		HashMap<String, GroupChainParams> tunnelGroupChainParams = 
+    				tunnel.getGroupChainParams();
+    		for (String targetSwDpid: tunnelGroupChainParams.keySet()) {
+    	        IOF13Switch targetSw = srManager.getIOF13Switch(targetSwDpid);
+
+    	        if (targetSw == null) {
+    	            log.debug("Switch {} is gone.", targetSwDpid);
+    	            continue;
+    	        }
+    	        
+    	        List<GroupChainParams> groupChainParamsList = new 
+    	        		ArrayList<IOF13Switch.GroupChainParams>();
+    	        groupChainParamsList.add(tunnelGroupChainParams.get(
+						targetSwDpid));
+    	        List<GroupChain> currentGroupChainList = tunnelsetGroupChain.get(targetSwDpid);
+    	        List<GroupChain> newGroupChainList = null;
+    	        
+    	        if (currentGroupChainList == null) {
+    	        	newGroupChainList = targetSw.
+    	        			createGroupChain(groupChainParamsList);
+    	        }
+    	        else {
+	    	        newGroupChainList = targetSw.addNewEntryToGroupChain(
+	    	        				currentGroupChainList, 
+	    	        				groupChainParamsList);
+    	        }
+    	        
+    	        if (newGroupChainList == null) {
+    	        	log.warn("CreateGroupChain for Switch {} failed at driver", targetSwDpid);
+    	            continue;
+    	        }
+
+    	        for (GroupChain groupChain:newGroupChainList) {
+    	        	HashMap<String,GroupChain> dpidGroupMap = 
+    	        			tunnelIdGroupChainMap.get(groupChain.getId());
+    	        	if (dpidGroupMap == null)
+    	        	{
+    	        		dpidGroupMap = new HashMap<String, IOF13Switch.GroupChain>();
+    	        		tunnelIdGroupChainMap.put(groupChain.getId(), dpidGroupMap);
+    	        	}
+    	        			
+    	        	dpidGroupMap.put(targetSwDpid, groupChain);
+    	        }
+    	        tunnelsetGroupChain.put(targetSwDpid, newGroupChainList);
+    		}    	
+    		tunnelMap.put(tunnelParams.getTunnel_id(), tunnel);
+    		if (newTunnelMap == null)
+    			newTunnelMap = new HashMap<String, SegmentRoutingTunnel>();
+    		newTunnelMap.put(tunnelParams.getTunnel_id(), tunnel);
+    	}
+    	return newTunnelMap;
+    }
+
+    public boolean removeConstituentTunnelFromTunnelset(
+    		SegmentRouterTunnelsetRESTParams tunnelsetParams) {
+    	for (String tunnelId:tunnelsetParams.getRemove_tunnel_params()) {
+    		HashMap<String, GroupChain> tunnelIdGroupChain = 
+    				tunnelIdGroupChainMap.get(tunnelId);
+    		for (String targetSwDpid:tunnelIdGroupChain.keySet()) {
+                IOF13Switch sw13 = srManager.getIOF13Switch(targetSwDpid);
+                if (sw13 != null) {
+                	GroupChain tunnelIdSwGroupChain = tunnelIdGroupChain.get(targetSwDpid);
+                	List<Integer> groupsPointedByInnermostGroup = new ArrayList<Integer>();
+                	for (PortNumber sp:tunnelIdSwGroupChain.getGroupChain().keySet()) {
+                		List<Integer> portGroupList = 
+                				tunnelIdSwGroupChain.getGroupChain().get(sp);
+                		/* Get the last group in the list. 
+                		 * This will be pointed by innermost group 
+                		 */
+                		int groupId = portGroupList.get(portGroupList.size()-1);
+                		groupsPointedByInnermostGroup.add(groupId);
+                	}
+                	
+                	if (groupsPointedByInnermostGroup.size() >0) {
+	                	if (!sw13.removeOutGroupBucketsFromGroup(
+	                			tunnelIdSwGroupChain.getInnermostGroupId(), 
+	                			groupsPointedByInnermostGroup)) {
+	                		log.warn("Faied to remove outgroup buckets "
+	                				+ "from group {} tunnelset {} at driver",
+	                				tunnelIdSwGroupChain.getInnermostGroupId(), 
+	                				tunnelsetId);
+	                        return false;
+	                	}
+	                	for (List<Integer> groupList: tunnelIdSwGroupChain.getGroupChain().values()) {
+	                        for (int i = groupList.size()-1; i >= 0; i--) {
+	                            int groupId = groupList.get(i);
+	                            if (!sw13.removeGroup(groupId)) {
+	                                log.warn("Faied to remove the tunnelset {} at driver",
+	                                        tunnelsetId);
+	                                return false;
+	                            }
+	                        }
+	                	}
+                	}
+                	else
+                	{
+                		/* No Group chain created for this tunnel in this switch
+                		 * Just remove the innermost group
+                		 */
+	                	if (!sw13.removeGroup(
+	                			tunnelIdSwGroupChain.getInnermostGroupId())) {
+	                		log.warn("Faied to remove innermost group "
+	                				+ "{} of tunnelset {} at driver",
+	                				tunnelIdSwGroupChain.getInnermostGroupId(), 
+	                				tunnelsetId);
+	                        return false;
+	                	}
+                	}
+                	List<GroupChain> swGroupChainList = tunnelsetGroupChain.get(targetSwDpid);
+                	for (int idx=0;idx<swGroupChainList.size();idx++) {
+                		if (swGroupChainList.get(idx).getId().equals(tunnelId)) {
+                			swGroupChainList.remove(idx);
+                		}
+                	}
+                	if (swGroupChainList.size()==0)
+                		tunnelsetGroupChain.remove(targetSwDpid);
+                }
+    		}
+    		tunnelIdGroupChainMap.remove(tunnelId);
+    		tunnelMap.remove(tunnelId);
+    	}
+    	return true;
+    }
+    /**
+     * Remove the tunnelset.
+     * It requests driver to remove all groups for the tunnelset
+     *
+     * @return true if succeeds, false otherwise.
+     */
+    public boolean removeTunnelset() {
+
+    	for (String targetSwDpid:tunnelsetGroupChain.keySet()) {
+            IOF13Switch sw13 = srManager.getIOF13Switch(targetSwDpid);
+            if (sw13 != null) {
+            	List<GroupChain> groupChainList = tunnelsetGroupChain.get(targetSwDpid);
+            	
+            	if (groupChainList != null) {
+                    // Innermost Group needs to be removed first because
+                    // the group being pointed by any other group cannot be removed
+            		int innermostGroupId = groupChainList.
+            				get(0).getInnermostGroupId();
+                    if (!sw13.removeGroup(innermostGroupId)) {
+                        log.warn("Faied to remove the tunnelset {} at driver",
+                                tunnelsetId);
+                        return false;
+                    }
+                    
+                    for (GroupChain groupChain: groupChainList) {
+                    	HashMap<PortNumber,List<Integer>> portGroupChain = 
+                    								groupChain.getGroupChain();
+                    	for (List<Integer> groupList: portGroupChain.values()) {
+	                        for (int i = groupList.size()-1; i >= 0; i--) {
+	                            int groupId = groupList.get(i);
+	                            if (!sw13.removeGroup(groupId)) {
+	                                log.warn("Faied to remove the tunnelset {} at driver",
+	                                        tunnelsetId);
+	                                return false;
+	                            }
+	                        }
+                    	}
+                    }
+            	}
+            }
+    	}
+    	
+    	tunnelsetGroupChain.clear();
+    	tunnelIdGroupChainMap.clear();
+
+        return true;
+    }
 
     public String getTunnelsetId() {
     	return this.tunnelsetId;
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyResource.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyResource.java
index 2aab718..f038867 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyResource.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyResource.java
@@ -48,12 +48,15 @@
             return "fail";
         }
         
-        String tunnelId = createParams.getTunnel_id();
+        String tunnelId = null;
         boolean isTunnelsetId = false;
-        if (createParams.getTunnelset_id() != null) {
+        if (createParams.getPolicy_type().equals("loadbalance")) {
         	tunnelId = createParams.getTunnelset_id();
         	isTunnelsetId = true;
         }
+        else if (createParams.getPolicy_type().equals("tunnel-flow")) {
+        	tunnelId = createParams.getTunnel_id();
+        }
 
         log.debug("createPolicy of type {} with params id {} src_ip {} dst_ip {}"
                 + "proto {} src_port {} dst_port {} priority {} tunnel_id {} isSet {}",
@@ -127,9 +130,14 @@
             SegmentRoutingPolicy policy = piI.next();
             String policyId = policy.getPolicyId();
             String tunnelId = null;
+            boolean isTunnelset = false;
             if (policy.getType() == PolicyType.TUNNEL_FLOW) {
                 tunnelId = ((SegmentRoutingPolicyTunnel)policy).getTunnelId();
             }
+            else if (policy.getType() == PolicyType.LOADBALANCE) {
+                tunnelId = ((SegmentRoutingPolicyTunnel)policy).getTunnelId();
+                isTunnelset = true;
+            }
             int priority = policy.getPriority();
             String policyType = policy.getType().name();
             PacketMatch flowEntries = policy.getMatch();
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetRESTParams.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetRESTParams.java
index 6faf4e7..639ea82 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetRESTParams.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetRESTParams.java
@@ -12,6 +12,7 @@
     //@JsonDeserialize(contentUsing = SegmentRouterTunnelRESTParams.class)
     private List<SegmentRouterTunnelRESTParams> tunnel_params = 
     				new ArrayList<SegmentRouterTunnelRESTParams>();
+    private List<String> remove_tunnel_params = new ArrayList<String>();
 
     public SegmentRouterTunnelsetRESTParams() {
         //this.tunnelset_id = null;
@@ -31,6 +32,14 @@
     }
 
     public List<SegmentRouterTunnelRESTParams> getTunnelParams() {
-        return this.tunnel_params;
+        return (this.tunnel_params.size() > 0)?this.tunnel_params:null;
     }
+
+	public List<String> getRemove_tunnel_params() {
+		return (remove_tunnel_params.size()>0)?remove_tunnel_params:null;
+	}
+
+	public void setRemove_tunnel_params(List<String> remove_tunnel_params) {
+		this.remove_tunnel_params = remove_tunnel_params;
+	}
 }
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetResource.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetResource.java
index 46e6126..ad9dfda 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetResource.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetResource.java
@@ -49,34 +49,34 @@
             return "fail";
         }
         log.debug("createTunnelset with tunnelsetId {} tunnel params{}",
-                createParams.getTunnelset_id(), createParams.getTunnelParams().get(0));
+                createParams.getTunnelset_id(), createParams);
         boolean result = true;
-        result = segmentRoutingService.createTunnelset(createParams.getTunnelset_id(),
+        result = segmentRoutingService.createUpdateTunnelset(createParams.getTunnelset_id(),
                 createParams);
         return (result == true) ? "success" : "fail";
     }
-/*
+
     @Delete("json")
-    public String deleteTunnel(String tunnelParams) {
+    public String deleteTunnelset(String tunnelsetParams) {
         ISegmentRoutingService segmentRoutingService =
                 (ISegmentRoutingService) getContext().getAttributes().
                         get(ISegmentRoutingService.class.getCanonicalName());
         ObjectMapper mapper = new ObjectMapper();
-        SegmentRouterTunnelRESTParams createParams = null;
+        SegmentRouterTunnelsetRESTParams createParams = null;
         try {
-            if (tunnelParams != null) {
-                createParams = mapper.readValue(tunnelParams,
-                        SegmentRouterTunnelRESTParams.class);
+            if (tunnelsetParams != null) {
+                createParams = mapper.readValue(tunnelsetParams,
+                        SegmentRouterTunnelsetRESTParams.class);
             }
         } catch (IOException ex) {
             log.error("Exception occurred parsing inbound JSON", ex);
             return "fail";
         }
-        log.debug("deleteTunnel with Id {}", createParams.getTunnel_id());
-        removeTunnelMessages result = segmentRoutingService.removeTunnel(
-                createParams.getTunnel_id());
+        log.debug("deleteTunnelset with Id {}", createParams.getTunnelset_id());
+        removeTunnelMessages result = segmentRoutingService.removeTunnelset(
+                createParams.getTunnelset_id());
         return result.name()+" "+result.toString();
-    }*/
+    }
 
     @Get("json")
     public Object getTunnelset() {
@@ -97,7 +97,7 @@
            String policiesId = "";
            while(piI.hasNext()){
                SegmentRoutingPolicy policy = piI.next();
-               if(policy.getType() == PolicyType.TUNNEL_FLOW &&
+               if(policy.getType() == PolicyType.LOADBALANCE &&
                  (((SegmentRoutingPolicyTunnel)policy).isTunnelsetId() &&
                   ((SegmentRoutingPolicyTunnel)policy).getTunnelId().equals(tunnelsetId))){
                    policiesId += (policy.getPolicyId()+",");
diff --git a/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java b/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java
index d45a13b..b11ac27 100644
--- a/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java
+++ b/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java
@@ -2263,13 +2263,140 @@
             	groupChain.setInnermostGroupId(innermostGroupId);
             }
     		log.debug(
-    				"createInnermostLabelGroup: Creating select group {} in sw {} "
+    				"createGroupChain: Creating select group {} in sw {} "
     						+ "with: {}", innermostGroupId, getStringId(), ecmpInfo);
     	}
     	
     	return groupChains;
     }
 
+    public List<GroupChain> addNewEntryToGroupChain(List<GroupChain> currentGroupChainList, 
+    		List<GroupChainParams> groupChainParams) {
+
+    	int innermostGroupId = currentGroupChainList.get(0).getInnermostGroupId();
+    	EcmpInfo innermostGroupInfo = userDefinedGroups.get(innermostGroupId);
+    	
+    	if (innermostGroupInfo == null) {
+            log.warn("addNewEntryToGroupChain in sw {} with wrong "
+            		+ "input parameters", getStringId());
+			return null;
+    	}
+    	
+    	List<BucketInfo> buckets = innermostGroupInfo.buckets;
+    	List<GroupChain> newGroupChainList = new ArrayList<IOF13Switch.GroupChain>();
+        boolean groupUpdated = false;
+    	for (GroupChainParams i: groupChainParams) {
+    		List<PortNumber> ports = i.getPorts();
+    		if (ports == null) {
+                log.warn("createGroupChain in sw {} with wrong "
+                		+ "input parameters", getStringId());
+    			return null;
+    		}
+
+    		List<PortNumber> activePorts = new ArrayList<PortNumber>();
+            for (PortNumber port : ports) {
+                if (portEnabled((int) port.value()))
+                    activePorts.add(port);
+            }
+            if (activePorts.isEmpty()) {
+                log.warn("addNewEntryToGroupChain in sw {} with no "
+                		+ "active ports for groupChainParams id {}", 
+                		getStringId(), i.getId());
+                continue;
+            }
+            
+            int labelStackSize = (i.getLabelStack() != null) ? 
+            		i.getLabelStack().size() : 0;
+            GroupChain groupChain = new GroupChain(i.getId());
+            newGroupChainList.add(groupChain);
+            
+    		if (labelStackSize > 0) {
+				for (PortNumber sp : activePorts) {
+					int previousGroupId = -1;
+	    			for (int idx=0; idx < i.getLabelStack().size(); idx++) {
+	    				if (idx == (labelStackSize - 1)) {
+	                        int label = i.getLabelStack().get(idx).intValue();
+	                        Dpid neighborDpid = portToNeighbors.get(sp);
+	                        BucketInfo b = new BucketInfo(neighborDpid,
+	                                MacAddress.of(srConfig.getRouterMac()),
+	                                getNeighborRouterMacAddress(neighborDpid),
+	                                sp, label, true, previousGroupId);
+	                        buckets.add(b);
+	                        groupUpdated = true;
+	    				}
+	    				else {
+							int currGroupId = getNextFreeGroupId();
+							EcmpInfo indirectGroup = createIndirectGroup(currGroupId,
+									null, null, sp, previousGroupId,
+									i.getLabelStack().get(idx).intValue(), false);
+							previousGroupId = currGroupId;
+							userDefinedGroups.put(currGroupId, indirectGroup);
+							groupChain.addGroupToChain(sp, currGroupId);
+	    				}
+    				}
+    			}
+    		}
+    		else
+    		{
+                for (PortNumber sp : activePorts) {
+                    Dpid neighborDpid = portToNeighbors.get(sp);
+                    BucketInfo b = new BucketInfo(neighborDpid,
+                            MacAddress.of(srConfig.getRouterMac()),
+                            getNeighborRouterMacAddress(neighborDpid),
+                            sp, -1, false, -1);
+                    buckets.add(b);
+                    groupUpdated = true;
+                }
+    		}
+    	}
+    	
+    	if (groupUpdated) {
+    		modifyEcmpGroup(innermostGroupInfo);
+            for (GroupChain groupChain:newGroupChainList) {
+            	groupChain.setInnermostGroupId(innermostGroupId);
+            	currentGroupChainList.add(groupChain);
+            }
+    		log.debug(
+    				"addNewEntryToGroupChain: Updating select group {} in sw {} "
+    						+ "with: {}", innermostGroupId, getStringId(), innermostGroupInfo);
+        	return currentGroupChainList;
+    	}
+    	
+    	return null;
+    	
+    }
+    
+    public boolean removeOutGroupBucketsFromGroup(
+    		int innermostGroupId, List<Integer> chainedGroups) {
+        EcmpInfo innermostGroupInfo = userDefinedGroups.get(innermostGroupId);
+        if (innermostGroupInfo == null) {
+            log.warn("removeOutGroupBucketsFromGroup in sw {}: "
+            		+ "with invalid group id", getStringId());
+            return false;
+        }
+        
+        Iterator<BucketInfo> it = innermostGroupInfo.buckets.iterator();
+        log.debug("removeOutGroupBucketsFromGroup: Group {} on Switch {} has {} buckets",
+        		innermostGroupId, getStringId(),
+        		innermostGroupInfo.buckets.size());
+        boolean groupUpdated = false;
+        while (it.hasNext()) {
+            BucketInfo bucket = it.next();
+            if (chainedGroups.contains(bucket.togroupNo)) {
+                it.remove();
+                groupUpdated = true;
+            }
+        }
+        if (groupUpdated) {
+	        log.debug("removeOutGroupBucketsFromGroup: Modifying Group "
+	        		+ "on Switch {} with {}",
+	                getStringId(), innermostGroupInfo);
+	        modifyEcmpGroup(innermostGroupInfo);
+	        return true;
+        }
+        return false;
+    }
+
     /**
      * Remove the specified group
      *