[SEBA-548] Optimimzation of random generation of tags

Change-Id: I655d68fb984cba9867f43677db5f1b7006187ec7
diff --git a/VERSION b/VERSION
index 3a3cd8c..866aba3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.3.1
+1.3.2.dev
diff --git a/xos/synchronizer/models/models.py b/xos/synchronizer/models/models.py
old mode 100644
new mode 100755
index f5ac079..5a42ce5
--- a/xos/synchronizer/models/models.py
+++ b/xos/synchronizer/models/models.py
@@ -16,9 +16,10 @@
 import socket
 import random
 
-from xos.exceptions import XOSValidationError, XOSProgrammingError
+from xos.exceptions import XOSValidationError, XOSProgrammingError, XOSPermissionDenied, XOSConfigurationError
 from models_decl import RCORDService_decl, RCORDSubscriber_decl, RCORDIpAddress_decl, BandwidthProfile_decl
 
+range_of_tags = list(range(16,4096))
 
 class BandwidthProfile(BandwidthProfile_decl):
     class Meta:
@@ -67,8 +68,10 @@
                 inner_service_instance.save(update_fields=["updated"])
 
     def generate_s_tag(self):
-        # NOTE what's the right way to generate an s_tag?
-        tag = random.randint(16, 4096)
+
+        # unused_s_tags_for_c_tag() this function will return a list of unused s_tags for that c_tag
+
+        tag = random.choice(self.unused_s_tags_for_c_tag())
  
         # Check that combination(c_tag,s_tag) is unique.
         # If the combination is not unique it will keep on calling this function recursively till it gets a unique combination.
@@ -81,8 +84,10 @@
 
 
     def generate_c_tag(self):
-        # NOTE this method will loop if available c_tags are ended
-        tag = random.randint(16, 4096)
+
+        # unused_c_tags_for_s_tag() this function will return a list of unused c_tags for the given s_tag
+
+        tag = random.choice(self.unused_c_tags_for_s_tag())
 
         # Check that this random generated value is valid across given ONU-first check.
         # If the value is valid it will assign c_tag wth it and do second check.
@@ -104,6 +109,41 @@
         else:
             return tag
 
+    def unused_c_tags_for_s_tag(self):
+
+        #This function will return a list of unused c_tags
+
+        #If there is no s_tag
+        global range_of_tags
+
+        #If there's a s_tag, then return unused c_tags for that s_tag
+        subscribers_with_same_s_tag = RCORDSubscriber.objects.filter(s_tag = self.s_tag)
+        used_c_tags = [s.c_tag for s in subscribers_with_same_s_tag]
+        if len(used_c_tags) == 0:
+            return range_of_tags
+        else:
+            list_of_unused_c_tags_for_s_tag = list(set(range_of_tags) - set(used_c_tags))
+            if len(list_of_unused_c_tags_for_s_tag) == 0:
+                raise XOSConfigurationError("All the c_tags are exhausted for this s_tag: %s"% self.s_tag)
+            else:
+                return list_of_unused_c_tags_for_s_tag
+
+    def unused_s_tags_for_c_tag(self):
+
+        #This function will return a list of unused s_tags
+
+        global range_of_tags
+        #If there's a c_tag, then return unused s_tags for that c_tag
+        subscribers_with_same_c_tag = RCORDSubscriber.objects.filter(c_tag = self.c_tag)
+        used_s_tags = [s.s_tag for s in subscribers_with_same_c_tag]
+        if len(used_s_tags) == 0:
+            return range_of_tags
+        else:
+            list_of_unused_s_tags_for_c_tag = list(set(range_of_tags) - set(used_s_tags))
+            if len(list_of_unused_s_tags_for_c_tag) == 0:
+                raise XOSConfigurationError("All the s_tags are exhausted for this c_tag: %s"% self.c_tag)
+            else:
+                return list_of_unused_s_tags_for_c_tag
     def get_same_onu_subscribers(self):
         return RCORDSubscriber.objects.filter(onu_device=self.onu_device)
 
@@ -170,6 +210,7 @@
             id = self.get_used_s_c_tag_subscriber_id()
             if None != id and not is_update_with_same_tag:
                 raise XOSValidationError("The c_tag(%s) and s_tag(%s) pair you specified,has already been used by Subscriber with Id (%s)" % (self.c_tag,self.s_tag,id))
+                
 
         if not self.c_tag:
             self.c_tag = self.generate_c_tag()
@@ -194,3 +235,4 @@
         super(RCORDSubscriber, self).save(*args, **kwargs)
         self.invalidate_related_objects()
         return
+                     
diff --git a/xos/synchronizer/models/test_models.py b/xos/synchronizer/models/test_models.py
index 4fe767f..a0e5a4d 100644
--- a/xos/synchronizer/models/test_models.py
+++ b/xos/synchronizer/models/test_models.py
@@ -31,6 +31,7 @@
     XOSValidationError = Exception
     XOSProgrammingError = Exception
     XOSPermissionDenied = Exception
+    XOSConfigurationError = Exception
 
 
 class XOS:
@@ -82,6 +83,7 @@
         self.rcord_subscriber.mac_address = "00:AA:00:00:00:01"
         self.rcord_subscriber.owner.leaf_model.access = "voltha"
         self.rcord_subscriber.owner.provider_services = [self.volt]
+        self.rcord_subscriber.list_of_unused_c_tags_for_s_tag = []
 
         self.rcord_ip = RCORDIpAddress()
         self.rcord_ip.subscriber = 1
@@ -228,7 +230,7 @@
         s.c_tag = "111"
         s.s_tag = "223"
         s.onu_device = "BRCM1234"
-
+        
         self.rcord_subscriber.get_same_onu_subscribers = Mock()
         self.rcord_subscriber.get_same_onu_subscribers.return_value = [s]
 
@@ -244,6 +246,71 @@
         self.assertGreater(self.rcord_subscriber.c_tag, 16)
         self.assertLess(self.rcord_subscriber.c_tag, 4097)
 
+        #Testing whether the random generation choses c_tag from the list provided
+        self.rcord_subscriber.c_tag = None
+        self.rcord_subscriber.s_tag = None
+        self.rcord_subscriber.unused_c_tags_for_s_tag = Mock()
+        self.rcord_subscriber.unused_c_tags_for_s_tag.return_value = [18,19]
+
+        self.rcord_subscriber.save()
+        self.models_decl.RCORDSubscriber_decl.save.assert_called()
+        self.assertGreater(self.rcord_subscriber.c_tag, 17)
+        self.assertLess(self.rcord_subscriber.c_tag, 20)
+
+        self.rcord_subscriber.c_tag = None
+        self.rcord_subscriber.s_tag = None
+
+        self.rcord_subscriber.save()
+        self.models_decl.RCORDSubscriber_decl.save.assert_called()
+        self.assertGreater(self.rcord_subscriber.c_tag, 16)
+        self.assertLess(self.rcord_subscriber.c_tag, 4096)
+
+    def test_unused_c_tags_for_s_tag(self):
+        s=[]
+        for i in range(16,4097):
+            d = Mock()
+            d.c_tag = i
+            d.s_tag = "222"
+            d.onu_device = "BRCM1234"
+            s.append(d)
+
+        self.rcord_subscriber.get_same_onu_subscribers = Mock()
+        self.rcord_subscriber.get_same_onu_subscribers.return_value = []
+        self.rcord_subscriber.get_same_s_c_tag_subscribers = Mock()
+        self.rcord_subscriber.get_same_s_c_tag_subscribers.return_value = []
+
+        self.models_decl.RCORDSubscriber_decl.objects.filter.return_value = s
+        self.rcord_subscriber.s_tag = 222
+        self.rcord_subscriber.c_tag = None
+        with self.assertRaises(Exception) as e:
+            self.rcord_subscriber.save()
+        self.assertEqual(e.exception.message, "All the c_tags are exhausted for this s_tag: 222")
+        self.models_decl.RCORDSubscriber_decl.save.assert_not_called()
+
+    def test_unused_s_tags_for_c_tag(self):
+        s=[]
+        for i in range(16,4097):
+            d = Mock()
+            d.s_tag = i
+            d.c_tag = "111"
+            d.onu_device = "BRCM1234"
+            s.append(d)
+
+        self.rcord_subscriber.get_same_onu_subscribers = Mock()
+        self.rcord_subscriber.get_same_onu_subscribers.return_value = []
+        self.rcord_subscriber.get_same_s_c_tag_subscribers = Mock()
+        self.rcord_subscriber.get_same_s_c_tag_subscribers.return_value = []
+
+        self.models_decl.RCORDSubscriber_decl.objects.filter.return_value = s
+        self.rcord_subscriber.c_tag = 111
+        self.rcord_subscriber.s_tag = None
+        with self.assertRaises(Exception) as e:
+            self.rcord_subscriber.save()
+        self.assertEqual(e.exception.message, "All the s_tags are exhausted for this c_tag: 111")
+        self.models_decl.RCORDSubscriber_decl.save.assert_not_called()
+
+
+
     def test_generate_s_tag(self):
         self.rcord_subscriber.s_tag = None
 
@@ -252,6 +319,7 @@
         self.models_decl.RCORDSubscriber_decl.save.assert_called()
         self.assertNotEqual(self.rcord_subscriber.s_tag, None)
 
+
     def test_provisioned_s_stag(self):
         self.rcord_subscriber.save()
         self.models_decl.RCORDSubscriber_decl.save.assert_called()