Tunnel loadbalancing policy: phase1 support
diff --git a/src/main/java/net/floodlightcontroller/core/IOF13Switch.java b/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
index 04e2a8d..3eaf58d 100644
--- a/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
+++ b/src/main/java/net/floodlightcontroller/core/IOF13Switch.java
@@ -1,7 +1,9 @@
 package net.floodlightcontroller.core;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -214,6 +216,89 @@
      * @return group identifier
      */
     public List<Integer> createGroup(List<Integer> labelStack, List<PortNumber> ports);
+    
+    public class GroupChainParams {
+    	private String id;
+    	private List<PortNumber> ports;
+    	private List<Integer> labelStack;
+    	
+    	public GroupChainParams(String id, List<Integer> labelStack, 
+    			List<PortNumber> ports) {
+    		this.setId(id);
+    		this.setPorts(ports);
+    		this.setLabelStack(labelStack);
+    	}
+
+		public String getId() {
+			return id;
+		}
+
+		public void setId(String id) {
+			this.id = id;
+		}
+
+		public List<PortNumber> getPorts() {
+			return ports;
+		}
+
+		public void setPorts(List<PortNumber> ports) {
+			this.ports = ports;
+		}
+
+		public List<Integer> getLabelStack() {
+			return labelStack;
+		}
+
+		public void setLabelStack(List<Integer> labelStack) {
+			this.labelStack = labelStack;
+		}
+    }
+    
+    public class GroupChain {
+    	private String id;
+    	private HashMap<PortNumber, List<Integer>> groupChain;
+    	private int innermostGroupId;
+    	
+    	public GroupChain(String id) {
+    		this.setId(id);
+    		groupChain = new HashMap<PortNumber,List<Integer>>();
+    	}
+    	
+    	public void addGroupChain(PortNumber port, List<Integer> chain) {
+    		this.groupChain.put(port, chain);
+    	}
+    	
+    	public void addGroupToChain(PortNumber port, int groupId) {
+    		List<Integer> chain = groupChain.get(port);
+    		if (chain == null) {
+    			chain = new ArrayList<Integer>();
+    			groupChain.put(port, chain);
+    		}
+    		chain.add(groupId);
+    	}
+    	
+    	public HashMap<PortNumber, List<Integer>> getGroupChain() {
+    		return groupChain;
+    	}
+
+		public String getId() {
+			return id;
+		}
+
+		public void setId(String id) {
+			this.id = id;
+		}
+
+		public int getInnermostGroupId() {
+			return innermostGroupId;
+		}
+
+		public void setInnermostGroupId(int innermostGroupId) {
+			this.innermostGroupId = innermostGroupId;
+		}
+    }
+    
+    public List<GroupChain> createGroupChain(List<GroupChainParams> groupChainParams);
 
     /**
      * Remove the specified group
diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/OFFlowStatsEntryModSerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/OFFlowStatsEntryModSerializer.java
index 42108d2..12187eb 100644
--- a/src/main/java/net/floodlightcontroller/core/web/serializers/OFFlowStatsEntryModSerializer.java
+++ b/src/main/java/net/floodlightcontroller/core/web/serializers/OFFlowStatsEntryModSerializer.java
@@ -88,7 +88,7 @@
                         +(matchGeneric.isMasked() ?
                                 OFFlowStatsEntryModSerializer.covertToMask(
                                         IPv4.toIPv4Address(
-                                                matchGeneric.getMask().toString())):"0"));
+                                                matchGeneric.getMask().toString())):"32"));
             }
             else if (matchGeneric.getMatchField().id == MatchFields.ETH_DST){
                 jGen.writeStringField("dataLayerDestination", matchGeneric.getValue().toString());
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 9924ad7..9c49eba 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/ISegmentRoutingService.java
@@ -7,6 +7,8 @@
 import net.floodlightcontroller.core.module.IFloodlightService;
 import net.floodlightcontroller.util.MACAddress;
 import net.onrc.onos.apps.segmentrouting.SegmentRoutingManager.removeTunnelMessages;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelRESTParams;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelsetRESTParams;
 import net.onrc.onos.core.topology.Link;
 import net.onrc.onos.core.util.IPv4Net;
 
@@ -27,6 +29,17 @@
     public boolean createTunnel(String tunnelId, List<Integer> labelIds);
 
     /**
+     * Create a tunnelset for policy routing.
+     *
+     * @param tunnelId ID for the tunnel
+     * @param labelIds Node label IDs for the tunnel
+     *
+     * @return "true/false" depending tunnel creation status
+     */
+    public boolean createTunnelset(String tunnelsetId, 
+    			SegmentRouterTunnelsetRESTParams tunnelsetParams);
+
+    /**
      * Remove a Segment Routing tunnel given a tunnel Id.
      *
      * @param tunnelId ID for the tunnel
@@ -51,7 +64,7 @@
      */
     public boolean createPolicy(String pid, MACAddress srcMac, MACAddress dstMac,
             Short etherType, IPv4Net srcIp, IPv4Net dstIp, Byte ipProto,
-            Short srcPort, Short dstPort, int priority, String tid);
+            Short srcPort, Short dstPort, int priority, String tid, boolean isTunnelsetId);
 
 
     /**
@@ -93,6 +106,7 @@
      */
 
     public Collection<SegmentRoutingTunnel> getTunnelTable();
+    public Collection<SegmentRoutingTunnelset> getTunnelsetTable();
     /**
      * Get the first group ID for the tunnel for specific source router
      * If Segment Stitching was required to create the tunnel, there are
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/PolicyNotification.java b/src/main/java/net/onrc/onos/apps/segmentrouting/PolicyNotification.java
index 6fbcc07..4bcf1bc 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/PolicyNotification.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/PolicyNotification.java
@@ -12,6 +12,7 @@
     private int priority;
     private PacketMatch match;
     private String tunnelId;  // XXX need to define PolicyTunnelNotification
+    private boolean isSetId;
     /**
      *
      */
@@ -34,6 +35,7 @@
         // XXX need to be processed in PolicyTunnelNotification
         if (PolicyType.valueOf(policyType) == PolicyType.TUNNEL_FLOW) {
             this.tunnelId = ((SegmentRoutingPolicyTunnel)srPolicy).getTunnelId();
+            this.isSetId = ((SegmentRoutingPolicyTunnel)srPolicy).isTunnelsetId();
         }
     }
 
@@ -61,10 +63,20 @@
 
     public void setTunnelId(String tid) {
         this.tunnelId = tid;
+        this.isSetId = false;
+    }
+
+    public void setTunnelId(String tid, boolean isSetId) {
+        this.tunnelId = tid;
+        this.isSetId = isSetId;
     }
 
     public String getTunnelId() {
         return tunnelId;
     }
+    
+    public boolean isTunnelsetId() {
+    	return this.isSetId;
+    }
 
 }
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 8851420..9a5aaea 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingManager.java
@@ -34,6 +34,8 @@
 import net.onrc.onos.api.packet.IPacketListener;
 import net.onrc.onos.api.packet.IPacketService;
 import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicy.PolicyType;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelRESTParams;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelsetRESTParams;
 import net.onrc.onos.apps.segmentrouting.web.SegmentRoutingWebRoutable;
 import net.onrc.onos.core.datagrid.IDatagridService;
 import net.onrc.onos.core.datagrid.IEventChannel;
@@ -129,6 +131,7 @@
     private ConcurrentLinkedQueue<TopologyEvents> topologyEventQueue;
     private HashMap<String, SegmentRoutingPolicy> policyTable;
     private HashMap<String, SegmentRoutingTunnel> tunnelTable;
+    private HashMap<String, SegmentRoutingTunnelset> tunnelsetTable;
     private HashMap<Integer, HashMap<Integer, List<Integer>>> adjacencySidTable;
     private HashMap<String, HashMap<Integer, Integer>> adjcencyGroupIdTable;
     private PolicyEventHandler policyEventHandler;
@@ -225,6 +228,7 @@
         restApi = context.getServiceImpl(IRestApiService.class);
         policyTable = new HashMap<String, SegmentRoutingPolicy>();
         tunnelTable = new HashMap<String, SegmentRoutingTunnel>();
+        tunnelsetTable = new HashMap<String, SegmentRoutingTunnelset>();
         adjacencySidTable = new HashMap<Integer,HashMap<Integer, List<Integer>>>();
         adjcencyGroupIdTable = new HashMap<String, HashMap<Integer, Integer>>();
         switchDpidListWithMastership = new ArrayList<String>();
@@ -1288,6 +1292,19 @@
     }
 
     /**
+     * Return the Tunnel table
+     *
+     * @return collection of TunnelInfo
+     */
+    public Collection<SegmentRoutingTunnelset> getTunnelsetTable() {
+        return this.tunnelsetTable.values();
+    }
+
+    public SegmentRoutingTunnelset getTunnelsetInfo(String tsid) {
+        return tunnelsetTable.get(tsid);
+    }
+
+    /**
      * Return the Policy Table
      */
     public Collection<SegmentRoutingPolicy> getPoclicyTable() {
@@ -1348,6 +1365,24 @@
             return false;
         }
     }
+    
+    public boolean createTunnelset(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");
+            return false;
+        }
+    }
 
     /**
      *  Create a policy with tunnel type
@@ -1355,14 +1390,32 @@
     @Override
     public boolean createPolicy(String pid, MACAddress srcMac, MACAddress dstMac,
             Short etherType, IPv4Net srcIp, IPv4Net dstIp, Byte ipProto,
-            Short srcPort, Short dstPort, int priority, String tid) {
+            Short srcPort, Short dstPort, int priority, String tid, boolean isTunnelsetId) {
 
         // Sanity check
-        SegmentRoutingTunnel tunnelInfo = tunnelTable.get(tid);
-        if (tunnelInfo == null) {
-            log.warn("Tunnel {} is not defined", tid);
-            return false;
-        }
+    	if (isTunnelsetId) {
+    		SegmentRoutingTunnelset tunnelsetInfo = tunnelsetTable.get(tid);
+	        if (tunnelsetInfo == null) {
+	            log.warn("Tunnelset {} is not defined", tid);
+	            return false;
+	        }
+    	}
+    	else {
+	        SegmentRoutingTunnel tunnelInfo = tunnelTable.get(tid);
+	        if (tunnelInfo == null) {
+	            log.warn("Tunnel {} is not defined", tid);
+	            return false;
+	        }
+	        else {
+	        	/* Check if the tunnel is part of a tunnelset, 
+	        	 * if so decline the request */
+	        	if (tunnelInfo.getTunnelsetId() != null) {
+		            log.warn("Tunnel {} is part of a tunnelset, "
+		            		+ "a policy can not point to such tunnels", tid);
+		            return false;
+		        }
+	        }
+    	}
 
         PacketMatch policyMatch = buildPacketMatch(srcMac, dstMac, etherType,
                 srcIp, dstIp, ipProto, srcPort, dstPort);
@@ -1370,6 +1423,7 @@
         SegmentRoutingPolicy srPolicy =
                 new SegmentRoutingPolicyTunnel(this,pid, PolicyType.TUNNEL_FLOW,
                         policyMatch, priority, tid);
+        ((SegmentRoutingPolicyTunnel)srPolicy).setIsTunnelsetId(isTunnelsetId);
         if (srPolicy.createPolicy()) {
             policyTable.put(pid, srPolicy);
             PolicyNotification policyNotification =
@@ -2121,7 +2175,7 @@
                 log.debug("Set the policy 1");
                 this.createPolicy("1", null, null, Ethernet.TYPE_IPV4, srcIp,
                         dstIp, IPv4.PROTOCOL_ICMP, (short)-1, (short)-1, 10000,
-                        "1");
+                        "1", false);
                 testMode = TEST_MODE.POLICY_ADD2;
                 testTask.reschedule(5, TimeUnit.SECONDS);
             }
@@ -2140,7 +2194,7 @@
                 log.debug("Set the policy 2");
                 this.createPolicy("2", null, null, Ethernet.TYPE_IPV4, srcIp,
                         dstIp, IPv4.PROTOCOL_ICMP, (short)-1, (short)-1, 20000,
-                        "2");
+                        "2", false);
                 //testMode = POLICY_REMOVE2;
                 //testTask.reschedule(5, TimeUnit.SECONDS);
             }
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 ab3f653..e7e2026 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingPolicyTunnel.java
@@ -1,9 +1,26 @@
 package net.onrc.onos.apps.segmentrouting;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
+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.Operator;
+import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.DecNwTtlAction;
+import net.onrc.onos.core.matchaction.action.GroupAction;
+import net.onrc.onos.core.matchaction.action.OutputAction;
+import net.onrc.onos.core.matchaction.action.SetDAAction;
+import net.onrc.onos.core.matchaction.action.SetSAAction;
 import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
 
+import org.projectfloodlight.openflow.types.MacAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -13,27 +30,37 @@
             .getLogger(SegmentRoutingPolicyTunnel.class);
 
     private String tunnelId;
+    private boolean isSetId;
 
     public SegmentRoutingPolicyTunnel(SegmentRoutingManager srm, String pid,
             PolicyType type, PacketMatch match, int priority, String tid) {
         super(srm, pid, type, match, priority);
         this.tunnelId = tid;
+        this.setIsTunnelsetId(false);
     }
 
     public SegmentRoutingPolicyTunnel(SegmentRoutingManager srm, PolicyNotification policyNotication) {
         super(policyNotication);
         this.srManager = srm;
         this.tunnelId = policyNotication.getTunnelId();
+        this.isSetId = policyNotication.isTunnelsetId();
     }
 
     @Override
     public boolean createPolicy() {
 
-        SegmentRoutingTunnel tunnelInfo = srManager.getTunnelInfo(tunnelId);
-
-        List<TunnelRouteInfo> routes = tunnelInfo.getRoutes();
-
-        populateAclRule(routes);
+    	if (isSetId) {
+    		SegmentRoutingTunnelset tunnelset = 
+    				srManager.getTunnelsetInfo(tunnelId);
+    		populateAclRuleToTunnelset(tunnelset);
+    	}
+    	else {
+	        SegmentRoutingTunnel tunnelInfo = srManager.getTunnelInfo(tunnelId);
+	
+	        List<TunnelRouteInfo> routes = tunnelInfo.getRoutes();
+	
+	        populateAclRule(routes);
+    	}
 
         return true;
     }
@@ -61,4 +88,32 @@
         return this.tunnelId;
     }
 
+	public boolean isTunnelsetId() {
+		return isSetId;
+	}
+
+	public void setIsTunnelsetId(boolean isSetId) {
+		this.isSetId = isSetId;
+	}
+
+    protected void populateAclRuleToTunnelset(SegmentRoutingTunnelset tunnelset) {
+    	HashMap<String,List<GroupChain>> tunnelsetGroupChain = 
+    			tunnelset.getTunnelsetGroupChain();
+    	for (String targetSwDpid: tunnelsetGroupChain.keySet()) {
+            List<Action> actions = new ArrayList<>();
+            GroupAction groupAction = new GroupAction();
+            groupAction.setGroupId(tunnelsetGroupChain.
+            		get(targetSwDpid).get(0).getInnermostGroupId());
+            actions.add(groupAction);
+    		
+            MatchAction matchAction = new MatchAction(
+                    srManager.getMatchActionId(),
+                    new SwitchPort((new Dpid(targetSwDpid)).value(), (long)0), match, priority,
+                    actions);
+            MatchActionOperationEntry maEntry =
+                    new MatchActionOperationEntry(Operator.ADD, matchAction);
+
+            srManager.executeMatchActionOpEntry(maEntry);
+    	}
+    }
 }
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnel.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnel.java
index a5a0f2b..47351b7 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnel.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnel.java
@@ -1,9 +1,11 @@
 package net.onrc.onos.apps.segmentrouting;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import net.floodlightcontroller.core.IOF13Switch;
+import net.floodlightcontroller.core.IOF13Switch.GroupChainParams;
 import net.floodlightcontroller.core.IOF13Switch.NeighborSet;
 import net.onrc.onos.core.drivermanager.OFSwitchImplDellOSR;
 import net.onrc.onos.core.topology.Link;
@@ -23,6 +25,7 @@
     private List<Integer> labelIds;
     private List<TunnelRouteInfo> routes;
     private SegmentRoutingManager srManager;
+    private String tunnelsetId;
 
     private final int MAX_NUM_LABELS = 3;
 
@@ -38,6 +41,16 @@
         this.srManager = srm;
         this.tunnelId = tid;
         this.labelIds = labelIds;
+        this.setTunnelsetId(null);
+        this.routes = new ArrayList<TunnelRouteInfo>();
+    }
+
+    public SegmentRoutingTunnel(SegmentRoutingManager srm, String tid,
+            List<Integer> labelIds, String tunnelsetId) {
+        this.srManager = srm;
+        this.tunnelId = tid;
+        this.labelIds = labelIds;
+        this.setTunnelsetId(tunnelsetId);
         this.routes = new ArrayList<TunnelRouteInfo>();
     }
 
@@ -114,7 +127,24 @@
     public List<TunnelRouteInfo> getRoutes(){
         return this.routes;
     }
-
+    
+    public HashMap<String, GroupChainParams> getGroupChainParams() {
+    	HashMap<String, GroupChainParams> groupChainParamList = 
+    			new HashMap<String, GroupChainParams>();
+        for (TunnelRouteInfo route: routes) {
+            List<Integer> Ids = new ArrayList<Integer>();
+            for (String IdStr: route.route)
+                Ids.add(Integer.parseInt(IdStr));
+            NeighborSet ns = new NeighborSet();
+            for (Dpid dpid: route.getFwdSwDpid())
+                ns.addDpid(dpid);
+            List<PortNumber> ports = getPortsFromNeighborSet(route.srcSwDpid, ns);
+        	GroupChainParams groupChainParams = new GroupChainParams(tunnelId, Ids, ports);
+        	groupChainParamList.put(route.srcSwDpid, groupChainParams);
+        }
+        
+        return groupChainParamList;
+    }
     /**
      * Create a tunnel
      * It requests the driver to create a group chaining for the tunnel.
@@ -169,6 +199,38 @@
         return true;
     }
 
+    public boolean computeTunnelLabelStack() {
+
+        if (labelIds.isEmpty() || labelIds.size() < 2) {
+            log.debug("Wrong tunnel information");
+            return false;
+        }
+
+        List<String> Ids = new ArrayList<String>();
+        for (Integer label : labelIds) {
+            Ids.add(label.toString());
+        }
+
+        List<TunnelRouteInfo> stitchingRule = getStitchingRule(Ids);
+        if (stitchingRule == null) {
+            log.debug("Failed to get a tunnel rule.");
+            return false;
+        }
+
+        // Rearrange the tunnels if the last subtunnel does not have any label
+        // NOTE: this is only for DELL switches because all ACL rule needs PUSH
+        // Label Action.
+        checkAndSplitLabels(stitchingRule);
+
+        for (TunnelRouteInfo route: stitchingRule) {
+        	route.setGroupIdList(null);
+        }
+
+        this.routes = stitchingRule;
+
+        return true;
+    }
+
     /**
      * Check if last sub tunnel rule label stack is empty.
      * If so, move a label of previous tunnel to the last sub-tunnel.
@@ -621,5 +683,13 @@
 
     }
 
+	public String getTunnelsetId() {
+		return tunnelsetId;
+	}
+
+	public void setTunnelsetId(String tunnelsetId) {
+		this.tunnelsetId = tunnelsetId;
+	}
+
 
 }
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java
new file mode 100644
index 0000000..8848714
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/SegmentRoutingTunnelset.java
@@ -0,0 +1,107 @@
+package net.onrc.onos.apps.segmentrouting;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import net.floodlightcontroller.core.IOF13Switch;
+import net.floodlightcontroller.core.IOF13Switch.GroupChain;
+import net.floodlightcontroller.core.IOF13Switch.GroupChainParams;
+import net.floodlightcontroller.core.IOF13Switch.NeighborSet;
+import net.onrc.onos.core.drivermanager.OFSwitchImplDellOSR;
+import net.onrc.onos.core.topology.Link;
+import net.onrc.onos.core.topology.Switch;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelRESTParams;
+import net.onrc.onos.apps.segmentrouting.web.SegmentRouterTunnelsetRESTParams;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SegmentRoutingTunnelset {
+
+    private static final Logger log = LoggerFactory
+            .getLogger(SegmentRoutingTunnel.class);
+	private SegmentRoutingManager srManager;
+    private String tunnelsetId;
+    private HashMap<String, SegmentRoutingTunnel> tunnelMap;
+    private HashMap<String, HashMap<String,GroupChain>> tunnelIdGroupChainMap;
+    private HashMap<String, List<GroupChain>> tunnelsetGroupChain;
+    
+	public SegmentRoutingTunnelset(SegmentRoutingManager srm,
+    		SegmentRouterTunnelsetRESTParams tunnelsetParams) {
+    	this.srManager = srm;
+    	this.tunnelsetId = tunnelsetParams.getTunnelset_id();
+    	tunnelsetGroupChain = new HashMap<String, List<GroupChain>>();
+    	tunnelMap = new HashMap<String,SegmentRoutingTunnel>();
+    	tunnelIdGroupChainMap = new HashMap<String, HashMap<String,GroupChain>>();
+    	for (SegmentRouterTunnelRESTParams tunnelParams:tunnelsetParams.getTunnelParams()) {
+    		SegmentRoutingTunnel tunnel = new 
+    				SegmentRoutingTunnel(srm, tunnelParams.getTunnel_id(),
+    						tunnelParams.getLabel_path(), tunnelsetId);
+    		tunnel.computeTunnelLabelStack();
+    		tunnelMap.put(tunnelParams.getTunnel_id(), tunnel);
+    	}
+    }
+    
+    public boolean createTunnelSet() {
+    	HashMap<String, List<GroupChainParams>> tunnelsetGroupChainParamsMap = 
+    			new HashMap<String, List<GroupChainParams>>();
+    	for (SegmentRoutingTunnel tunnel:tunnelMap.values()) {
+    		HashMap<String, GroupChainParams> tunnelGroupChainParams = 
+    				tunnel.getGroupChainParams();
+    		for (String targetSwDpid: tunnelGroupChainParams.keySet()) {
+    			List<GroupChainParams> groupChainParams = 
+    					tunnelsetGroupChainParamsMap.get(targetSwDpid);
+    			if (groupChainParams == null) {
+    				groupChainParams = new ArrayList<GroupChainParams>();
+    				tunnelsetGroupChainParamsMap.put(targetSwDpid, groupChainParams);
+    			}
+    			groupChainParams.add(tunnelGroupChainParams.get(targetSwDpid));
+    		}
+    	}
+
+		for (String targetSwDpid: tunnelsetGroupChainParamsMap.keySet()) {
+	        IOF13Switch targetSw = srManager.getIOF13Switch(targetSwDpid);
+
+	        if (targetSw == null) {
+	            log.debug("Switch {} is gone.", targetSwDpid);
+	            continue;
+	        }
+
+	        List<GroupChain> groupChainList = targetSw.createGroupChain(
+	        		tunnelsetGroupChainParamsMap.get(targetSwDpid));
+	        for (GroupChain groupChain:groupChainList) {
+	        	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, groupChainList);
+		}
+		
+		return true;
+    }
+
+    public String getTunnelsetId() {
+    	return this.tunnelsetId;
+    }
+    
+    public HashMap<String,SegmentRoutingTunnel> getTunnels() {
+    	return this.tunnelMap;
+    }
+
+    public HashMap<String, GroupChain> getTunnelGroupChain(String tunnelId) {
+		return tunnelIdGroupChainMap.get(tunnelId);
+	}
+
+    public HashMap<String, List<GroupChain>> getTunnelsetGroupChain() {
+		return tunnelsetGroupChain;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyRESTParams.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyRESTParams.java
index 8fc9256..03629ac 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyRESTParams.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterPolicyRESTParams.java
@@ -6,6 +6,7 @@
     private String policy_type;
     private int priority;
     private String tunnel_id;
+    private String tunnelset_id;
     private String proto_type;
     private String src_ip;
     private String src_tp_port_op;
@@ -19,6 +20,7 @@
         this.policy_type = null;
         this.priority = 0;
         this.tunnel_id = null;
+        this.setTunnelset_id(null);
         this.proto_type = null;
         this.src_ip = null;
         this.src_tp_port_op = null;
@@ -115,4 +117,12 @@
     public short getDst_tp_port() {
         return this.dst_tp_port;
     }
+
+	public String getTunnelset_id() {
+		return tunnelset_id;
+	}
+
+	public void setTunnelset_id(String tunnelset_id) {
+		this.tunnelset_id = tunnelset_id;
+	}
 }
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 a955440..2aab718 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
@@ -47,14 +47,21 @@
             log.error("Exception occurred parsing inbound JSON", ex);
             return "fail";
         }
+        
+        String tunnelId = createParams.getTunnel_id();
+        boolean isTunnelsetId = false;
+        if (createParams.getTunnelset_id() != null) {
+        	tunnelId = createParams.getTunnelset_id();
+        	isTunnelsetId = true;
+        }
 
         log.debug("createPolicy of type {} with params id {} src_ip {} dst_ip {}"
-                + "proto {} src_port {} dst_port {} priority {} tunnel_id {}",
+                + "proto {} src_port {} dst_port {} priority {} tunnel_id {} isSet {}",
                 createParams.getPolicy_type(),
                 createParams.getPolicy_id(), createParams.getSrc_ip(),
                 createParams.getDst_ip(), createParams.getProto_type(),
                 createParams.getSrc_tp_port(), createParams.getDst_tp_port(),
-                createParams.getPriority(), createParams.getTunnel_id());
+                createParams.getPriority(), tunnelId, isTunnelsetId);
 
         IPv4Net src_ip = (createParams.getSrc_ip() != null) ?
                 new IPv4Net(createParams.getSrc_ip()) : null;
@@ -68,7 +75,7 @@
                 createParams.getSrc_tp_port(),
                 createParams.getDst_tp_port(),
                 createParams.getPriority(),
-                createParams.getTunnel_id());
+                tunnelId, isTunnelsetId);
         return (result == true) ? "success" : "fail";
     }
 
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelInfo.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelInfo.java
index 3808ef8..9cd2cc0 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelInfo.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelInfo.java
@@ -7,23 +7,29 @@
  */
 public class SegmentRouterTunnelInfo {
     private String tunnelId;
+    private String tunnelsetId;
     private List<List<String>> labelStack;
     private List<String> dpidGroup;
     private List<Integer> tunnelPath;
     private String policies;
     
     public SegmentRouterTunnelInfo (String tId,List<List<String>> tunnelRoutes,
-            List<String> dpidsWithGroup,List<Integer> path, String policiesId){
+            List<String> dpidsWithGroup,List<Integer> path, 
+            String policiesId, String tunnelsetId){
         this.tunnelId = tId;
         this.labelStack = tunnelRoutes;
         this.dpidGroup = dpidsWithGroup;
         this.tunnelPath = path;
         this.policies = policiesId;
+        this.tunnelsetId = tunnelsetId;
         
     }
     public String getTunnelId (){
         return this.tunnelId;
     }
+    public String getTunnelsetId (){
+        return this.tunnelsetId;
+    }
     public List<List<String>> getLabelStack (){
         return this.labelStack;
     }
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelResource.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelResource.java
index efaed12..c611d0f 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelResource.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelResource.java
@@ -12,6 +12,7 @@
 import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicy.PolicyType;
 import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicyTunnel;
 import net.onrc.onos.apps.segmentrouting.SegmentRoutingTunnel;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingTunnelset;
 import net.onrc.onos.apps.segmentrouting.TunnelRouteInfo;
 
 import org.codehaus.jackson.map.ObjectMapper;
@@ -88,6 +89,7 @@
            SegmentRoutingTunnel tunnelInfo = ttI.next();
            List<Integer> tunnelPath = tunnelInfo.getLabelids();
            String tunnelId = tunnelInfo.getTunnelId();
+           String tunnelsetId = tunnelInfo.getTunnelsetId();
            Collection<SegmentRoutingPolicy> policies = segmentRoutingService.getPoclicyTable();
            Iterator<SegmentRoutingPolicy> piI = policies.iterator();
            String policiesId = "";
@@ -107,15 +109,31 @@
            while(trI.hasNext()){
                TunnelRouteInfo label = trI.next();
                labelStack.add(label.getRoute());
-               Integer gId = segmentRoutingService.getTunnelGroupId(tunnelId,
-                       label.getSrcSwDpid());
+               Integer gId = -1;
+               if (tunnelsetId != null) {
+                   Iterator<SegmentRoutingTunnelset> tstI = 
+                		   segmentRoutingService.getTunnelsetTable().iterator();
+                   while(tstI.hasNext()){
+                	   SegmentRoutingTunnelset parentTunnelset = tstI.next();
+                	   if (parentTunnelset.getTunnelsetId() == tunnelsetId) {
+                		   gId = parentTunnelset.getTunnelGroupChain(
+                				   tunnelId).get(label.getSrcSwDpid()).
+                				   getInnermostGroupId();
+                		   break;
+                	   }
+                   }
+               }
+               else {
+	               gId = segmentRoutingService.getTunnelGroupId(tunnelId,
+	                       label.getSrcSwDpid());
+               }
                dpidGroup.add(label.getSrcSwDpid()+"("
                        + segmentRoutingService.getMplsLabel(label.getSrcSwDpid())+ ")"
                        + "/"+ gId
                        );
            }
            SegmentRouterTunnelInfo info = new SegmentRouterTunnelInfo(tunnelId,
-                    labelStack, dpidGroup, tunnelPath, policiesId );
+                    labelStack, dpidGroup, tunnelPath, policiesId, tunnelsetId );
            infoList.add(info);
         }
         log.debug("getTunnel with params");
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetInfo.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetInfo.java
new file mode 100644
index 0000000..0e42d0f
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetInfo.java
@@ -0,0 +1,38 @@
+package net.onrc.onos.apps.segmentrouting.web;
+
+import java.util.List;
+/**
+ * This class contains tunnelset info of ONOS Segement Routing App
+ * Used for rest API
+ */
+public class SegmentRouterTunnelsetInfo {
+    private String tunnelsetId;
+    private List<SegmentRouterTunnelInfo> constituentTunnels;
+    private String policies;
+    
+    public SegmentRouterTunnelsetInfo(String tunnelsetId) {
+    	this.tunnelsetId = tunnelsetId;
+    	this.constituentTunnels = null;
+    }
+    
+	public String getTunnelsetId() {
+		return tunnelsetId;
+	}
+	public void setTunnelsetId(String tunnelsetId) {
+		this.tunnelsetId = tunnelsetId;
+	}
+	public List<SegmentRouterTunnelInfo> getConstituentTunnels() {
+		return constituentTunnels;
+	}
+	public void setConstituentTunnels(List<SegmentRouterTunnelInfo> constituentTunnels) {
+		this.constituentTunnels = constituentTunnels;
+	}
+
+	public String getPolicies() {
+		return policies;
+	}
+
+	public void setPolicies(String policies) {
+		this.policies = policies;
+	}
+}
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
new file mode 100644
index 0000000..6faf4e7
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetRESTParams.java
@@ -0,0 +1,36 @@
+package net.onrc.onos.apps.segmentrouting.web;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.codehaus.jackson.map.annotate.JsonDeserialize;
+
+public class SegmentRouterTunnelsetRESTParams {
+    private String tunnelset_id;
+    @JsonProperty("tunnel_params")
+    //@JsonDeserialize(contentUsing = SegmentRouterTunnelRESTParams.class)
+    private List<SegmentRouterTunnelRESTParams> tunnel_params = 
+    				new ArrayList<SegmentRouterTunnelRESTParams>();
+
+    public SegmentRouterTunnelsetRESTParams() {
+        //this.tunnelset_id = null;
+        //this.tunnel_params = null;
+    }
+
+    public void setTunnelset_id(String tunnelset_id) {
+        this.tunnelset_id = tunnelset_id;
+    }
+
+    public String getTunnelset_id() {
+        return this.tunnelset_id;
+    }
+
+    public void setTunnelParams(List<SegmentRouterTunnelRESTParams> tunnel_params) {
+        this.tunnel_params = tunnel_params;
+    }
+
+    public List<SegmentRouterTunnelRESTParams> getTunnelParams() {
+        return this.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
new file mode 100644
index 0000000..46e6126
--- /dev/null
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRouterTunnelsetResource.java
@@ -0,0 +1,141 @@
+package net.onrc.onos.apps.segmentrouting.web;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import net.floodlightcontroller.core.IOF13Switch.GroupChain;
+import net.onrc.onos.apps.segmentrouting.ISegmentRoutingService;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicy;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicyTunnel;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingTunnel;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingTunnelset;
+import net.onrc.onos.apps.segmentrouting.TunnelRouteInfo;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingManager.removeTunnelMessages;
+import net.onrc.onos.apps.segmentrouting.SegmentRoutingPolicy.PolicyType;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.restlet.resource.Delete;
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.restlet.resource.ServerResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SegmentRouterTunnelsetResource extends ServerResource {
+    protected final static Logger log =
+            LoggerFactory.getLogger(SegmentRouterTunnelsetResource.class);
+
+    @Post("json")
+    public String createTunnelset(String tunnelsetParams) {
+        log.debug("createTunnelset with tunnelsetParams {}", tunnelsetParams);
+        ISegmentRoutingService segmentRoutingService =
+                (ISegmentRoutingService) getContext().getAttributes().
+                        get(ISegmentRoutingService.class.getCanonicalName());
+        ObjectMapper mapper = new ObjectMapper();
+        SegmentRouterTunnelsetRESTParams createParams = null;
+        try {
+            if (tunnelsetParams != null) {
+                createParams = mapper.readValue(tunnelsetParams,
+                        SegmentRouterTunnelsetRESTParams.class);
+            }
+            else
+                return "fail";
+        } catch (IOException ex) {
+            log.error("Exception occurred parsing inbound JSON", ex);
+            return "fail";
+        }
+        log.debug("createTunnelset with tunnelsetId {} tunnel params{}",
+                createParams.getTunnelset_id(), createParams.getTunnelParams().get(0));
+        boolean result = true;
+        result = segmentRoutingService.createTunnelset(createParams.getTunnelset_id(),
+                createParams);
+        return (result == true) ? "success" : "fail";
+    }
+/*
+    @Delete("json")
+    public String deleteTunnel(String tunnelParams) {
+        ISegmentRoutingService segmentRoutingService =
+                (ISegmentRoutingService) getContext().getAttributes().
+                        get(ISegmentRoutingService.class.getCanonicalName());
+        ObjectMapper mapper = new ObjectMapper();
+        SegmentRouterTunnelRESTParams createParams = null;
+        try {
+            if (tunnelParams != null) {
+                createParams = mapper.readValue(tunnelParams,
+                        SegmentRouterTunnelRESTParams.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());
+        return result.name()+" "+result.toString();
+    }*/
+
+    @Get("json")
+    public Object getTunnelset() {
+        ISegmentRoutingService segmentRoutingService =
+                (ISegmentRoutingService) getContext().getAttributes().
+                        get(ISegmentRoutingService.class.getCanonicalName());
+        Iterator<SegmentRoutingTunnelset> ttI = 
+        		segmentRoutingService.getTunnelsetTable().iterator();
+        List<SegmentRouterTunnelsetInfo> tunnelsetInfoList = 
+        		new ArrayList<SegmentRouterTunnelsetInfo>();
+        while(ttI.hasNext()){
+           SegmentRoutingTunnelset tunnelset = ttI.next();
+           String tunnelsetId = tunnelset.getTunnelsetId();
+           SegmentRouterTunnelsetInfo tunnelsetInfo = new SegmentRouterTunnelsetInfo(
+        		   tunnelsetId);
+           Collection<SegmentRoutingPolicy> policies = segmentRoutingService.getPoclicyTable();
+           Iterator<SegmentRoutingPolicy> piI = policies.iterator();
+           String policiesId = "";
+           while(piI.hasNext()){
+               SegmentRoutingPolicy policy = piI.next();
+               if(policy.getType() == PolicyType.TUNNEL_FLOW &&
+                 (((SegmentRoutingPolicyTunnel)policy).isTunnelsetId() &&
+                  ((SegmentRoutingPolicyTunnel)policy).getTunnelId().equals(tunnelsetId))){
+                   policiesId += (policy.getPolicyId()+",");
+               }
+           }
+           if (policiesId.endsWith(",")){
+               policiesId = (String) policiesId.subSequence(0, policiesId.length()-1);
+           }
+           HashMap<String, SegmentRoutingTunnel> constituentTunnels = 
+        		   				tunnelset.getTunnels();
+           List<SegmentRouterTunnelInfo> tunnelInfoList = 
+           		new ArrayList<SegmentRouterTunnelInfo>();
+           for (SegmentRoutingTunnel tunnel:constituentTunnels.values()) {
+               String tunnelId = tunnel.getTunnelId();
+               List<Integer> tunnelPath = tunnel.getLabelids();
+               String parentTunnelsetId = tunnel.getTunnelsetId();
+               Iterator<TunnelRouteInfo>trI = tunnel.getRoutes().iterator();
+               List<List<String>> labelStack = new ArrayList<List<String>>();
+               List<String> dpidGroup = new ArrayList<String>();
+               while(trI.hasNext()){
+                   TunnelRouteInfo label = trI.next();
+                   labelStack.add(label.getRoute());
+                   Integer gId = tunnelset.getTunnelGroupChain(tunnelId).
+                		   get(label.getSrcSwDpid()).getInnermostGroupId();
+                   dpidGroup.add(label.getSrcSwDpid()+"("
+                           + segmentRoutingService.getMplsLabel(label.getSrcSwDpid())+ ")"
+                           + "/"+ gId
+                           );
+               }
+               SegmentRouterTunnelInfo info = new SegmentRouterTunnelInfo(tunnelId,
+                       labelStack, dpidGroup, tunnelPath, null, parentTunnelsetId );
+               tunnelInfoList.add(info);
+           }
+           tunnelsetInfo.setPolicies(policiesId);
+           tunnelsetInfo.setConstituentTunnels(tunnelInfoList);
+           tunnelsetInfoList.add(tunnelsetInfo);
+        }
+        log.debug("getTunnelset with params");
+        return tunnelsetInfoList;
+    }
+}
diff --git a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRoutingWebRoutable.java b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRoutingWebRoutable.java
index 6c9db02..c08225c 100644
--- a/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRoutingWebRoutable.java
+++ b/src/main/java/net/onrc/onos/apps/segmentrouting/web/SegmentRoutingWebRoutable.java
@@ -18,6 +18,7 @@
         //TODO: rewrite Router/SwitchesResource for router specific info.
         router.attach("/routers",  SegmentRouterResource.class);
         router.attach("/router/{routerId}/{statsType}",  SegmentRouterResource.class);
+        router.attach("/tunnelset", SegmentRouterTunnelsetResource.class);
         router.attach("/tunnel", SegmentRouterTunnelResource.class);
         router.attach("/policy", SegmentRouterPolicyResource.class);
         return router;
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 1fe9c1b..d45a13b 100644
--- a/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java
+++ b/src/main/java/net/onrc/onos/core/drivermanager/OFSwitchImplSpringOpenTTP.java
@@ -17,6 +17,8 @@
 
 import net.floodlightcontroller.core.IFloodlightProviderService.Role;
 import net.floodlightcontroller.core.IOF13Switch;
+import net.floodlightcontroller.core.IOF13Switch.GroupChain;
+import net.floodlightcontroller.core.IOF13Switch.GroupChainParams;
 import net.floodlightcontroller.core.IOF13Switch.NeighborSet.groupPktType;
 import net.floodlightcontroller.core.IOFSwitch;
 import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted;
@@ -2183,6 +2185,90 @@
                 getStringId(), innermostGroupId);
         return groupIdList;
     }
+    
+    public List<GroupChain> createGroupChain(List<GroupChainParams> groupChainParams) {
+    	List<GroupChain> groupChains = new ArrayList<GroupChain>();
+    	
+        List<BucketInfo> buckets = new ArrayList<BucketInfo>();
+    	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("createGroupChain 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());
+            groupChains.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);
+	    				}
+	    				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);
+                }
+    		}
+    	}
+    	
+    	if (!buckets.isEmpty()) {
+            int innermostGroupId = getNextFreeGroupId();
+    		EcmpInfo ecmpInfo = new EcmpInfo(innermostGroupId,
+    				OFGroupType.SELECT, buckets);
+    		setEcmpGroup(ecmpInfo);
+            userDefinedGroups.put(innermostGroupId, ecmpInfo);
+            for (GroupChain groupChain:groupChains) {
+            	groupChain.setInnermostGroupId(innermostGroupId);
+            }
+    		log.debug(
+    				"createInnermostLabelGroup: Creating select group {} in sw {} "
+    						+ "with: {}", innermostGroupId, getStringId(), ecmpInfo);
+    	}
+    	
+    	return groupChains;
+    }
 
     /**
      * Remove the specified group