Tunnel loadbalancing policy: phase1 support
diff --git a/cli/c_actions.py b/cli/c_actions.py
index 2273709..b2ab1f8 100755
--- a/cli/c_actions.py
+++ b/cli/c_actions.py
@@ -93,6 +93,66 @@
         if error_type:
             raise error.CommandRestError(result, message)
 
+tunnelset_id=None
+tunnelset_dict=[]
+def tunnelset_create(data=None):
+    global tunnelset_id,tunnelset_dict
+    if sdnsh.description:   # description debugging
+        print "tunnelset_create:" , data
+    if data.has_key('tunnelset-id'):
+        if (tunnelset_id != None):
+            if sdnsh.description:   # description debugging
+                print "tunnelset_create: previous data is not cleaned up"
+            tunnelset_id=None
+            tunnelset_dict=[]
+        tunnelset_id=data['tunnelset-id']
+        tunnelset_dict=[]
+    if sdnsh.description:   # description debugging
+        print "tunnelset_create:" , tunnelset_id
+
+def tunnelset_config_exit():
+    global tunnelset_id,tunnelset_dict
+    if sdnsh.description:   # description debugging
+        print "tunnelset_config_exit entered", tunnelset_dict
+    if tunnelset_dict:
+        url_str = ""
+        entries = tunnelset_dict
+        url_str = "http://%s/rest/v1/tunnelset/" % (sdnsh.controller)
+        obj_data = {}
+        obj_data['tunnelset_id']=tunnelset_id
+        obj_data['tunnel_params']=entries
+        result = "fail"
+        try:
+            result = sdnsh.store.rest_post_request(url_str,obj_data)
+        except Exception, e:
+            errors = sdnsh.rest_error_to_dict(e)
+            print sdnsh.rest_error_dict_to_message(errors)
+        # LOOK! successful stuff should be returned in json too.
+        tunnelset_dict = []
+        tunnelset_id = None
+        curr_tunnel_id = None
+        if result != "success":
+            print "command failed"
+    else:
+        print "empty command"
+    #Clear the transit information    
+            
+def tunnelset_remove(data=None):
+    if sdnsh.description:   # description debugging
+        print "tunnelset_remove:" , data
+    tunnelset_id=data['tunnelset-id']
+    url_str = "http://%s/rest/v1/tunnel/" % (sdnsh.controller)
+    obj_data = {}
+    obj_data['tunnelset_id']=data['tunnelset-id']
+    result = "fail"
+    try:
+        result = sdnsh.store.rest_post_request(url_str,obj_data,'DELETE')
+    except Exception, e:
+        errors = sdnsh.rest_error_to_dict(e)
+        print sdnsh.rest_error_dict_to_message(errors)
+    if not result.startswith("SUCCESS"):
+        print result
+
 tunnel_id=None
 tunnel_dict={}
 def tunnel_create(data=None):
@@ -118,13 +178,18 @@
     global tunnel_id,tunnel_dict
     if sdnsh.description:   # description debugging
         print "tunnel_config_exit entered", tunnel_dict
-    if tunnel_dict:
+        
+    entries = tunnel_dict[tunnel_id]
+    obj_data = {}
+    obj_data['tunnel_id']=tunnel_id
+    obj_data['label_path']=entries
+    if tunnelset_id:
+        tunnelset_dict.append(obj_data)
+        tunnel_dict = {}
+        tunnel_id = None
+    elif tunnel_dict:
         url_str = ""
-        entries = tunnel_dict[tunnel_id]
         url_str = "http://%s/rest/v1/tunnel/" % (sdnsh.controller)
-        obj_data = {}
-        obj_data['tunnel_id']=tunnel_id
-        obj_data['label_path']=entries
         result = "fail"
         try:
             result = sdnsh.store.rest_post_request(url_str,obj_data)
@@ -156,6 +221,7 @@
     if not result.startswith("SUCCESS"):
         print result
 
+
 policy_obj_data = {}
 def policy_create(data=None):
     global policy_obj_data
@@ -174,7 +240,15 @@
     if data.has_key('priority'):
         policy_obj_data['priority'] = data['priority']
     if data.has_key('tunnel-id'):
+        if policy_obj_data.has_key('tunnelset_id'):
+            print "ERROR: Policy can not point to both tunnelset and tunnel"
+            return
         policy_obj_data['tunnel_id'] = data['tunnel-id']
+    if data.has_key('tunnelset-id'):
+        if policy_obj_data.has_key('tunnel_id'):
+            print "ERROR: Policy can not point to both tunnelset and tunnel"
+            return
+        policy_obj_data['tunnelset_id'] = data['tunnelset-id']
     
     if sdnsh.description:   # description debugging
         print policy_obj_data
@@ -744,6 +818,9 @@
     # and additional config modes must also have the same prefix as the
     # current mode.
     current_mode = sdnsh.current_mode()
+    if (mode_name == 'config-tunnel'):
+        if (current_mode == 'config-tunnelset'):
+            mode_name = 'config-tunnelset-tunnel'
 
     if sdnsh.description:   # description debugging
         print "push_mode: ", mode_name, obj_type, data, parent_field, parent_id
@@ -855,7 +932,9 @@
     if sdnsh.description:   # description debugging
         print "push_mode: ", mode_name, obj_type, pk_name, key
     exitCallback = None
-    if (mode_name == 'config-tunnel'):
+    if (mode_name == 'config-tunnelset'):
+        exitCallback = tunnelset_config_exit
+    if ((mode_name == 'config-tunnel') or (mode_name == 'config-tunnelset-tunnel')):
         exitCallback = tunnel_config_exit
     if (mode_name == 'config-policy'):
         exitCallback = policy_config_exit
@@ -2218,6 +2297,7 @@
             #    labelStackString = labelStackString[:-1]
             #    labelStackString += ']'
             tunnelId = tunnel.get('tunnelId')
+            tunnelsetId = tunnel.get('tunnelsetId')
             tunnelPath = tunnel.get('tunnelPath')
             dpidGroup = str(tunnel.get('dpidGroup'))
             dpidGroup= remove_unicodes(dpidGroup)
@@ -2228,9 +2308,37 @@
                                'dpidGroup'      : dpidGroup,
                                'tunnelPath'     : tunnelPath,
                                'policies'       : policies,
+                               'tunnelset'      : tunnelsetId,
                                })
         entries = combResult
 
+    if 'showtunnelset' in data  and (data['showtunnelset'] == 'tunnelset' or data['detail'] == 'details'):
+        #eraise error.ArgumentValidationError('\n\n\n %s' % (entries))
+        combResult = []
+        tunnelsetList = entries
+        for tunnelset in tunnelsetList:
+            tunnelsetId = tunnelset.get('tunnelsetId')
+            policies = tunnelset.get('policies')
+            tunnelList = tunnelset.get('constituentTunnels')
+            for tunnel in tunnelList:
+                labelStackList = (tunnel.get('labelStack'))
+                labelStackString = str(labelStackList)
+                labelStackString = remove_unicodes(labelStackString)
+                tunnelId = tunnel.get('tunnelId')
+                tunnelPath = tunnel.get('tunnelPath')
+                dpidGroup = str(tunnel.get('dpidGroup'))
+                dpidGroup= remove_unicodes(dpidGroup)
+                combResult.append({
+                                   'tunnelsetId'    : tunnelsetId,
+                                   'policies'       : policies,
+                                   'tunnelId'       : tunnelId,
+                                   'labelStack'     : labelStackString,
+                                   'dpidGroup'      : dpidGroup,
+                                   'tunnelPath'     : tunnelPath,
+                                   'tunnelset'      : tunnelsetId,
+                                   })
+        entries = combResult
+
     if 'showpolicy' in data  and data['showpolicy'] == 'policy':
         #raise error.ArgumentValidationError('\n\n\n %s' % (data))
         combResult = []
@@ -3882,6 +3990,14 @@
                        tunnel_remove,
                        {'kwargs': {'data' : '$data',}})
 
+    command.add_action('create-tunnelset',
+                       tunnelset_create,
+                       {'kwargs': {'data' : '$data',}})
+
+    command.add_action('remove-tunnelset',
+                       tunnelset_remove,
+                       {'kwargs': {'data' : '$data',}})
+
     command.add_action('create-policy',
                        policy_create,
                        {'kwargs': {'data' : '$data',}})
diff --git a/cli/climodelinfo.py b/cli/climodelinfo.py
index 438eda8..523ece1 100755
--- a/cli/climodelinfo.py
+++ b/cli/climodelinfo.py
@@ -265,6 +265,24 @@
                                        },
                         },
         },
+
+        'tunnelset-config' : {
+            'source'          : 'user-config',
+            'source'          : 'display',
+            'url'             : 'tunnelset-config',
+            'config-obj-type' : 'tunnelset-config',
+
+            'fields' : {
+                'tunnelset-id'               : {
+                                         'edit' : False,
+                                         'max_length': 32,
+                                         'null': False,
+                                         'primary_key': True,
+                                         'type': 'CharField',
+                                         'edit' : False,
+                                       },
+                        },
+        },
                                       
         'policy-config' : {
             'source'          : 'user-config',
diff --git a/cli/command.py b/cli/command.py
index b1fa227..d99ed41 100755
--- a/cli/command.py
+++ b/cli/command.py
@@ -1750,16 +1750,34 @@
                     command_mode = command.get('mode')
                     if command_mode and command_mode[-1] == '*':
                         command_mode = command_mode[:-1]
-                    if command_mode and sdnsh.current_mode() != command_mode:
+                    if (command_mode == 'config-tunnel' and
+                       sdnsh.current_mode() == 'config-tunnelset-tunnel'):
+                        command_mode = 'config-tunnelset-tunnel'
+                    if (command_mode and 
+                        ((_is_list(command_mode) and not sdnsh.current_mode() in command_mode) 
+                         or (sdnsh.current_mode() != command_mode))):
+
                         # this is completing a different item on the stack.
                         # XXX needs better api's here.
                         found_in_mode_stack = False
-                        for x in sdnsh.mode_stack:
-                            if x['mode_name'] == command_mode:
-                                found_in_mode_stack = True
-                                curr_obj_type = x['obj_type']
-                                curr_obj_id   = x['obj']
-                                break
+                        
+                        if _is_list(command_mode):
+                            for cmd_mode in command_mode:
+                                for x in sdnsh.mode_stack:
+                                    if x['mode_name'] == cmd_mode:
+                                        found_in_mode_stack = True
+                                        curr_obj_type = x['obj_type']
+                                        curr_obj_id   = x['obj']
+                                        break
+                                if found_in_mode_stack == True:
+                                    break
+                        else:
+                            for x in sdnsh.mode_stack:
+                                if x['mode_name'] == command_mode:
+                                    found_in_mode_stack = True
+                                    curr_obj_type = x['obj_type']
+                                    curr_obj_id   = x['obj']
+                                    break
                         if not found_in_mode_stack:
                             raise error.CommandDescriptionError(
                                     'Unable to find mode %s' % command_mode, 
@@ -3865,6 +3883,14 @@
         'no-action': 'delete-objects',
     })
 
+    add_command_type('create-tunnelset', {
+        'action': 'create-tunnelset'
+    })
+    
+    add_command_type('remove-tunnelset', {
+        'action': 'remove-tunnelset'
+    })
+    
     add_command_type('create-tunnel', {
         'action': 'create-tunnel'
     })
diff --git a/cli/desc/version200/core.py b/cli/desc/version200/core.py
index 0e0f3c1..9a8b8bc 100755
--- a/cli/desc/version200/core.py
+++ b/cli/desc/version200/core.py
@@ -2399,12 +2399,37 @@
 SHOW_TUNNEL_FORMAT = {
     'show_tunnel' : {
         'field-orderings' : {
-            'default' : [ 'Idx', 'tunnelId', 'policies','tunnelPath','labelStack',],
-            'details' : [ 'Idx', 'tunnelId', 'policies','tunnelPath','labelStack', 'dpidGroup',],
+            'default' : [ 'Idx', 'tunnelId', 'policies','tunnelset','tunnelPath','labelStack',],
+            'details' : [ 'Idx', 'tunnelId', 'policies','tunnelset','tunnelPath','labelStack', 'dpidGroup',],
             },
         'fields': {
             'tunnelId'         : { 'verbose-name' : 'Id',
                                },
+            'tunnelset'         : { 'verbose-name' : 'tunnelset',
+                               },
+            'dpidGroup'         : { 'verbose-name' : 'Dpid(Node Id)/Group',
+                               },
+            'labelStack'         : { 'verbose-name' : 'Label Stack [Outer-->Inner]',
+                               },
+            'tunnelPath'         : { 'verbose-name' : 'Tunnel Path [Head-->Tail]',
+                               },
+                   }
+        },
+}
+
+SHOW_TUNNELSET_FORMAT = {
+    'show_tunnelset' : {
+        'field-orderings' : {
+            'default' : [ 'Idx', 'tunnelsetId', 'policies','tunnelId','tunnelPath','labelStack',],
+            'details' : [ 'Idx', 'tunnelsetId', 'policies','tunnelId','tunnelPath','labelStack', 'dpidGroup',],
+            },
+        'fields': {
+            'tunnelsetId'         : { 'verbose-name' : 'Id',
+                               },
+            'tunnelId'         : { 'verbose-name' : 'tunnelId',
+                               },
+            'tunnelset'         : { 'verbose-name' : 'tunnelset',
+                               },
             'dpidGroup'         : { 'verbose-name' : 'Dpid(Node Id)/Group',
                                },
             'labelStack'         : { 'verbose-name' : 'Label Stack [Outer-->Inner]',
diff --git a/cli/desc/version200/policy.py b/cli/desc/version200/policy.py
index eac0209..ad99363 100644
--- a/cli/desc/version200/policy.py
+++ b/cli/desc/version200/policy.py
@@ -254,7 +254,7 @@
                                 'proc' : 'create-policy',
                             },
                          ),
-        'completion'   : 'tunnelid-completion',
+        'completion'   : 'tunnel-id-completion',
         'field'        : 'tunnel-id',
         'type'         : 'identifier',
         'syntax-help'  : 'Enter tunnel id',
@@ -263,6 +263,30 @@
     }
 }
 
+POLICY_TUNNELSET_ID_COMMAND_DESCRIPTION = {
+    'name'            : 'tunnelset',
+    'mode'            : 'config-policy',
+    #'obj-type'        : 'policy-config',
+    'command-type'    : 'config',
+    'short-help'      : 'Configure tunnelset id',
+    #'doc'             : 'policy|tunnel',
+    #'doc-example'     : 'policy|policy-tunnel-example',
+    'parent-field'    : 'policy',
+    'args' : {
+        'action'       : (
+                            {
+                                'proc' : 'create-policy',
+                            },
+                         ),
+        'completion'   : 'tunnelset-id-completion',
+        'field'        : 'tunnelset-id',
+        'type'         : 'identifier',
+        'syntax-help'  : 'Enter tunnelset id',
+        'doc'          : 'policy|tunnelset-id',
+        'doc-include'  : [ 'type-doc' ],
+    }
+}
+
 POLICY_PRIORITY_COMMAND_DESCRIPTION = {
     'name'            : 'priority',
     'mode'            : 'config-policy',
@@ -322,6 +346,19 @@
                                     'completions'  : '$completions',
                                     }})
 
+def tunnelset_id_completion(prefix, completions):
+    query_url = "http://127.0.0.1:8000/rest/v1/showtunnelset"
+    result = command.sdnsh.store.rest_simple_request(query_url)
+    entries = json.loads(result)
+    for entry in entries:
+        if entry['tunnelsetId'].startswith(prefix):
+            completions[entry['tunnelsetId']+' '] = entry['tunnelsetId']
+    return
+
+command.add_completion('tunnelset-id-completion', tunnelset_id_completion,
+                       {'kwargs': { 'prefix'       : '$text',
+                                    'completions'  : '$completions',
+                                    }})
 
 def policy_id_completion(prefix, completions):
     query_url = "http://127.0.0.1:8000/rest/v1/showpolicy"
diff --git a/cli/desc/version200/tunnel.py b/cli/desc/version200/tunnel.py
index d79b012..a2bc0cd 100644
--- a/cli/desc/version200/tunnel.py
+++ b/cli/desc/version200/tunnel.py
@@ -2,12 +2,56 @@
 import json
 import fmtcnv
 
+TUNNELSET_SUBMODE_COMMAND_DESCRIPTION = {
+    'name'          : 'tunnelset',
+    'short-help'    : 'Enter tunnelset submode, configure tunnelset details',
+    'mode'          : 'config',
+    'parent-field'  : None,
+    'command-type'  : 'config-submode',
+    'obj-type'      : 'tunnelset-config',
+    'submode-name'  : 'config-tunnelset',
+    'doc'           : 'tunnelset|tunnelset',
+    'doc-example'   : 'tunnelset|tunnelset-example',
+    'args' : (
+        {
+            'field'        : 'tunnelset-id',
+            'type'         : 'identifier',
+            #'completion'   : 'complete-object-field',
+            'syntax-help'  : 'Enter a tunnelset name',
+            'doc'          : 'tunnelset|tunnelset',
+            'doc-include'  : [ 'type-doc' ],
+            #'completion'   : 'tunnelset-id-completion',
+            'action'       : (
+                                {
+                                    'proc' : 'create-tunnelset',
+                                },
+                                {
+                                    'proc' : 'push-mode-stack',
+                                },
+                              ),
+            'no-action': (
+                {
+                    'proc' : 'remove-tunnelset',
+                }
+            ),
+        }
+    )
+}
+
+TUNNELSET_CONFIG_FORMAT = {
+    'tunnelset-config' : {
+        'field-orderings' : {
+            'default' : [
+                          'tunnelset-id',
+                        ],
+        },
+    },
+}
 
 TUNNEL_SUBMODE_COMMAND_DESCRIPTION = {
     'name'          : 'tunnel',
     'short-help'    : 'Enter tunnel submode, configure tunnel details',
-    'mode'          : 'config',
-    'parent-field'  : None,
+    'mode'          : ['config', 'config-tunnelset'],
     'command-type'  : 'config-submode',
     'obj-type'      : 'tunnel-config',
     'submode-name'  : 'config-tunnel',
@@ -115,7 +159,7 @@
 # obj_type flow-entry field hard-timeout
 TUNNEL_NODE_ENTRY_COMMAND_DESCRIPTION = {
     'name'                : 'node',
-    'mode'                : 'config-tunnel',
+    'mode'                : 'config-tunnel*',
     'short-help'          : 'Set node for this tunnel',
     'doc'                 : 'tunnel|node',
     'doc-example'         : 'tunnel|node',
@@ -143,7 +187,7 @@
     )
 }
 
-SWITCH_TUNNEL_COMMAND_DESCRIPTION = {
+SHOW_TUNNEL_COMMAND_DESCRIPTION = {
     'name'                : 'show',
     'mode'                : 'login',
     'command-type'        : 'display-table',
@@ -180,6 +224,42 @@
     )
 }
 
+SHOW_TUNNELSET_COMMAND_DESCRIPTION = {
+    'name'                : 'show',
+    'mode'                : 'login',
+    'command-type'        : 'display-table',
+    'all-help'            : 'Show tunnelset information',
+    'short-help'          : 'Show tunnelset summary',
+    #'obj-type'            : 'switches',
+    'doc'                 : 'tunnelset|show',
+    'doc-example'         : 'tunnelset|show-example',
+    'args' : (
+        {
+            'token'  : 'tunnelset',
+            'field'  : 'showtunnelset',
+            'sort'   : ['tunnelsetId',],
+            'action' : 'display-rest',
+            'doc'    : 'tunnelset|show',
+            'url'    : [
+                        'showtunnelset',
+                       ],
+            'format' : 'show_tunnelset',
+        },
+              { 
+            'optional'   : True,
+            'choices' : (
+                {
+                 'field'      : 'showtunnelset',
+                 'type'       : 'enum',
+                 'values'     : ('details',),
+                 'optional'   : True,
+                 'format' : 'show_tunnelset',
+                 'data'         : { 'detail' : 'details' },
+                },
+                         ),
+               }
+    )
+}
 
 def tunnel_id_completion(prefix, completions):
     query_url = "http://127.0.0.1:8000/rest/v1/showtunnel"
diff --git a/sdncon/controller/models.py b/sdncon/controller/models.py
index 88ac7ad..2c1c129 100755
--- a/sdncon/controller/models.py
+++ b/sdncon/controller/models.py
@@ -64,6 +64,36 @@
     """
     return int(time.time()*1000000)
 
+class Tunnelset(models.Model):
+
+    id_max_length = 64
+    #
+    # fields ----------------------------------------
+
+    #
+    # Unique name of the Tunnel
+    #
+    tunnelset_id = models.CharField(
+        primary_key  = True,
+        verbose_name = 'Tunnelset Id',
+        help_text    = 'A unique Id for a Tunnelset',
+        validators   = [ TenantNameValidator() ],
+        max_length   = id_max_length)
+    
+    #
+    # end fields ----------------------------------------
+
+    def __unicode__ (self):
+        return self.tunnelset_id
+    
+    def delete(self):
+        super(Tunnelset, self).delete()
+    class Rest:
+        NAME = 'tunnelset-config'
+        FIELD_INFO = (
+            {'name': 'tunnelset_id',    'rest_name': 'tunnelset-id'},
+            )
+
 class Tunnel(models.Model):
 
     id_max_length = 64
diff --git a/sdncon/rest/views.py b/sdncon/rest/views.py
index 750cc1c..3bcc48a 100755
--- a/sdncon/rest/views.py
+++ b/sdncon/rest/views.py
@@ -2144,6 +2144,25 @@
     return HttpResponse(simplejson.dumps(jsondict), JSON_CONTENT_TYPE)
 
 @safe_rest_view
+def do_sdnplatform_tunnelset_config(request):    
+    if request.method != 'PUT' and request.method != 'DELETE':
+        raise RestInvalidMethodException()
+
+    url = controller_url('onos', 'segmentrouting', 'tunnelset')
+    post_data = request.raw_post_data
+    put_request = urllib2.Request(url, post_data)
+    method = request.method
+    if method == 'PUT':
+        method = 'POST'
+    put_request.get_method = lambda: method
+    put_request.add_header('Content-Type', 'application/json')
+    response = urllib2.urlopen(put_request)
+    response_text = response.read()
+    response = HttpResponse(response_text, JSON_CONTENT_TYPE)
+    
+    return response
+
+@safe_rest_view
 def do_sdnplatform_tunnel_config(request):    
     if request.method != 'PUT' and request.method != 'DELETE':
         raise RestInvalidMethodException()
@@ -2192,6 +2211,16 @@
     return get_sdnplatform_response(url)
 
 @safe_rest_view
+def do_show_tunnelset(request):    
+    #if request.method != 'GET':
+    #    raise RestInvalidMethodException()
+
+    url = controller_url('onos', 'segmentrouting','tunnelset')
+    if request.META['QUERY_STRING']:
+        url += '?' + request.META['QUERY_STRING']
+    return get_sdnplatform_response(url)
+
+@safe_rest_view
 def do_show_policy(request):    
     #if request.method != 'GET':
     #    raise RestInvalidMethodException()
diff --git a/sdncon/sdncon b/sdncon/sdncon
deleted file mode 100644
index e7fcf37..0000000
--- a/sdncon/sdncon
+++ /dev/null
Binary files differ
diff --git a/sdncon/urls.py b/sdncon/urls.py
index c00805d..a0b1605 100755
--- a/sdncon/urls.py
+++ b/sdncon/urls.py
@@ -171,8 +171,10 @@
     (r'^rest/v1/controller/summary$', 'sdncon.rest.views.do_sdnplatform_controller_summary'),
     
     # REST APIs for SR tunnel 
+    (r'^rest/v1/tunnelset/?$', 'sdncon.rest.views.do_sdnplatform_tunnelset_config'),
     (r'^rest/v1/tunnel/?$', 'sdncon.rest.views.do_sdnplatform_tunnel_config'),
     (r'^rest/v1/showtunnel/?$', 'sdncon.rest.views.do_show_tunnel'),
+    (r'^rest/v1/showtunnelset/?$', 'sdncon.rest.views.do_show_tunnelset'),
     # REST APIs for SR policy 
     (r'^rest/v1/showpolicy/?$', 'sdncon.rest.views.do_show_policy'),
     (r'^rest/v1/policy/?$', 'sdncon.rest.views.do_sdnplatform_policy_config'),