[SEBA-110] Validation location

Change-Id: Idd155c05fa4cac5eb0733576c5c0fae74925e1ef
diff --git a/docs/README.md b/docs/README.md
index 35ce2ae..064ba4e 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -46,6 +46,8 @@
       type: tosca.nodes.AttWorkflowDriverWhiteListEntry
       properties:
         serial_number: BRCM22222222
+        pon_port_id: 536870912
+        olt_logical_device_id: of:000000000a5a0072
       requirements:
         - owner:
             node: service#att
diff --git a/samples/whitelist.yaml b/samples/whitelist.yaml
index 5cc364a..4c0782a 100644
--- a/samples/whitelist.yaml
+++ b/samples/whitelist.yaml
@@ -32,6 +32,8 @@
       type: tosca.nodes.AttWorkflowDriverWhiteListEntry
       properties:
         serial_number: BRCM22222222
+        pon_port_id: 536870912
+        olt_logical_device_id: of:000000000a5a0072
       requirements:
         - owner:
             node: service#att
diff --git a/xos/synchronizer/event_steps/onu_event.py b/xos/synchronizer/event_steps/onu_event.py
index 64197d2..045198f 100644
--- a/xos/synchronizer/event_steps/onu_event.py
+++ b/xos/synchronizer/event_steps/onu_event.py
@@ -44,7 +44,8 @@
             # create an AttWorkflowDriverServiceInstance, the validation will be triggered in the corresponding sync step
             att_si = AttWorkflowDriverServiceInstance(
                 serial_number=event["serial_number"],
-                of_dpid=event["of_dpid"]
+                of_dpid=event["of_dpid"],
+                uni_port_id=event["uni_port_id"]
             )
             self.log.debug("onu.events: Created new AttWorkflowDriverServiceInstance", si=att_si)
         att_si.save()
diff --git a/xos/synchronizer/event_steps/test_onu_events.py b/xos/synchronizer/event_steps/test_onu_events.py
index e4e227c..e81f876 100644
--- a/xos/synchronizer/event_steps/test_onu_events.py
+++ b/xos/synchronizer/event_steps/test_onu_events.py
@@ -80,7 +80,8 @@
         self.event_dict = {
             'status': 'activated',
             'serial_number': 'BRCM1234',
-            'of_dpid': 'of:109299321'
+            'of_dpid': 'of:109299321',
+            'uni_port_id': 16
         }
         self.event.value = json.dumps(self.event_dict)
 
@@ -103,3 +104,4 @@
 
             self.assertEqual(att_si.serial_number, self.event_dict['serial_number'])
             self.assertEqual(att_si.of_dpid, self.event_dict['of_dpid'])
+            self.assertEqual(att_si.uni_port_id, self.event_dict['uni_port_id'])
diff --git a/xos/synchronizer/model_policies/model_policy_att_workflow_driver_serviceinstance.py b/xos/synchronizer/model_policies/model_policy_att_workflow_driver_serviceinstance.py
index 25d824a..37946ed 100644
--- a/xos/synchronizer/model_policies/model_policy_att_workflow_driver_serviceinstance.py
+++ b/xos/synchronizer/model_policies/model_policy_att_workflow_driver_serviceinstance.py
@@ -34,10 +34,6 @@
         elif si.authentication_state == "DENIED":
             subscriber.status = "auth-failed"
 
-        # If the OSS returns a c_tag use that one
-        if si.c_tag:
-            subscriber.c_tag = si.c_tag
-
         subscriber.save(always_update_timestamp=update_timestamp)
 
     def create_subscriber(self, si):
diff --git a/xos/synchronizer/model_policies/test_model_policy_att_workflow_driver_serviceinstance.py b/xos/synchronizer/model_policies/test_model_policy_att_workflow_driver_serviceinstance.py
index a34fedc..4386eb3 100644
--- a/xos/synchronizer/model_policies/test_model_policy_att_workflow_driver_serviceinstance.py
+++ b/xos/synchronizer/model_policies/test_model_policy_att_workflow_driver_serviceinstance.py
@@ -221,64 +221,6 @@
 
             onu_save.assert_not_called()
 
-    def test_create_subscriber_with_ctag(self):
-        self.si.valid = "valid"
-        self.si.serial_number = "BRCM1234"
-        self.si.c_tag = 111
-        self.si.backend_code = 1
-
-        onu = ONUDevice(
-            serial_number=self.si.serial_number,
-            admin_state="ENABLED"
-        )
-
-        with patch.object(ONUDevice.objects, "get_items") as onu_objects, \
-                patch.object(RCORDSubscriber, "save", autospec=True) as subscriber_save, \
-                patch.object(ONUDevice, "save") as onu_save:
-
-            onu_objects.return_value = [onu]
-
-            self.policy.handle_update(self.si)
-            self.assertEqual(subscriber_save.call_count, 1)
-
-            subscriber = subscriber_save.call_args[0][0]
-            self.assertEqual(subscriber.onu_device, self.si.serial_number)
-            self.assertEqual(subscriber.c_tag, self.si.c_tag)
-
-            onu_save.assert_not_called()
-
-    def _test_add_c_tag_to_pre_provisioned_subscriber(self):
-        self.si.valid = "valid"
-        self.si.serial_number = "BRCM1234"
-        self.si.c_tag = 111
-        self.si.backend_code = 1
-
-        onu = ONUDevice(
-            serial_number=self.si.serial_number,
-            admin_state="ENABLED"
-        )
-
-        subscriber = RCORDSubscriber(
-            onu_device=self.si.serial_number,
-        )
-
-        with patch.object(ONUDevice.objects, "get_items") as onu_objects, \
-                patch.object(RCORDSubscriber.objects, "get_items") as subscriber_objects, \
-                patch.object(RCORDSubscriber, "save", autospec=True) as subscriber_save, \
-                patch.object(ONUDevice, "save") as onu_save:
-
-            onu_objects.return_value = [onu]
-            subscriber_objects.return_value = [subscriber]
-
-            self.policy.handle_update(self.si)
-            self.assertEqual(subscriber_save.call_count, 1)
-
-            subscriber = subscriber_save.call_args[0][0]
-            self.assertEqual(subscriber.onu_device, self.si.serial_number)
-            self.assertEqual(subscriber.c_tag, self.si.c_tag)
-
-            onu_save.assert_not_called()
-
 if __name__ == '__main__':
     unittest.main()
 
diff --git a/xos/synchronizer/models/att-workflow-driver.xproto b/xos/synchronizer/models/att-workflow-driver.xproto
index 046e7f5..f1a161a 100644
--- a/xos/synchronizer/models/att-workflow-driver.xproto
+++ b/xos/synchronizer/models/att-workflow-driver.xproto
@@ -5,24 +5,26 @@
     option verbose_name = "AttWorkflowDriver Service";
     option kind = "OSS";
 
-    required bool create_on_discovery = 2 [help_text = "Whether to create the subscriber when an ONU is discovered", null = False, db_index = False, blank = False, default = True];
+    required bool create_on_discovery = 2 [help_text = "Whether to create the subscriber when an ONU is discovered", db_index = False, default = True];
 }
 
 message AttWorkflowDriverServiceInstance (ServiceInstance){
     option owner_class_name = "AttWorkflowDriverService";
     option verbose_name = "AttWorkflowDriver Service Instance";
 
-    required string valid = 1 [default = "awaiting", choices = "(('awaiting', 'Awaiting Validation'), ('valid', 'Valid'), ('invalid', 'Invalid'))", help_text = "Wether this ONU has been validated by the external OSS", null = False, blank = False];
-    required string serial_number = 2 [max_length = 254, null = False, db_index = False, blank = False, tosca_key=True, unique = True];
-    required string authentication_state = 3 [default = "AWAITING", choices = "(('AWAITING', 'Awaiting'), ('STARTED', 'Started'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('DENIED', 'Denied'), )", max_length = 50, null = False, db_index = False, blank = False];
-    required string of_dpid = 4 [max_length = 254, null = False, db_index = False, blank = False];
-    optional int32 c_tag = 5 [null = True, db_index = False, blank = False, unique = True, feedback_state = True];
+    required string valid = 1 [default = "awaiting", choices = "(('awaiting', 'Awaiting Validation'), ('valid', 'Valid'), ('invalid', 'Invalid'))", help_text = "Wether this ONU has been validated by the external OSS"];
+    required string serial_number = 2 [max_length = 254, db_index = False, tosca_key=True, unique = True];
+    required string authentication_state = 3 [default = "AWAITING", choices = "(('AWAITING', 'Awaiting'), ('STARTED', 'Started'), ('REQUESTED', 'Requested'), ('APPROVED', 'Approved'), ('DENIED', 'Denied'), )", max_length = 50, db_index = False];
+    required string of_dpid = 4 [max_length = 254, db_index = False];
+    required int32 uni_port_id = 5 [db_index = False];
 }
 
 message AttWorkflowDriverWhiteListEntry (XOSBase) {
-    option verbose_name = "Whitelist";
+    option verbose_name = "ONU Whitelist";
     option plural = "attworkflowdriverwhitelistentries";
 
-    required manytoone owner->AttWorkflowDriverService:whitelist_entries = 1 [db_index = True, null = False, blank = False, tosca_key=True];
-    required string serial_number = 2 [max_length = 254, null = False, db_index = False, blank = False, tosca_key=True, unique_with = "owner"];
+    required manytoone owner->AttWorkflowDriverService:whitelist_entries = 1 [db_index = True, tosca_key=True];
+    required string serial_number = 2 [max_length = 254, db_index = False, tosca_key=True, unique_with = "owner"];
+    required int32 pon_port_id = 3 [db_index = False, help_text = "PON Port on which this ONU is expected to show up"];
+    required string device_id = 3 [max_length = 54, db_index = False, help_text = "OLT Device (logical device id) on which this ONU is expected to show up"];
 }
diff --git a/xos/synchronizer/steps/sync_att_workflow_driver_service_instance.py b/xos/synchronizer/steps/sync_att_workflow_driver_service_instance.py
index d2fdeeb..1726e50 100644
--- a/xos/synchronizer/steps/sync_att_workflow_driver_service_instance.py
+++ b/xos/synchronizer/steps/sync_att_workflow_driver_service_instance.py
@@ -14,7 +14,7 @@
 
 import json
 from synchronizers.new_base.syncstep import SyncStep, model_accessor
-from synchronizers.new_base.modelaccessor import AttWorkflowDriverServiceInstance, AttWorkflowDriverWhiteListEntry
+from synchronizers.new_base.modelaccessor import AttWorkflowDriverServiceInstance, AttWorkflowDriverWhiteListEntry, ONUDevice
 
 from xosconfig import Config
 from multistructlog import create_logger
@@ -25,7 +25,7 @@
     provides = [AttWorkflowDriverServiceInstance]
     observes = AttWorkflowDriverServiceInstance
 
-    def validate_in_external_oss(self, si):
+    def validate_onu(self, si):
         # This is where you may want to call your OSS Database to verify if this ONU can be activated
         oss_service = si.owner.leaf_model
 
@@ -34,30 +34,30 @@
         matching_entries = AttWorkflowDriverWhiteListEntry.objects.filter(owner_id=oss_service.id,
                                                                   serial_number=si.serial_number)
 
-        return len(matching_entries)>0
+        # check that it's in the whitelist
+        if len(matching_entries) == 0:
+            log.warn("ONU disable as not in whitelist", object=str(si), serial_number=si.serial_number, **si.tologdict())
+            return False
 
-    def get_suscriber_c_tag(self, serial_number):
-        # If it's up to your OSS to generate c_tags, fetch them here
-        # otherwise XOS will generate one for your subscriber
-        return None
+        whitelisted = matching_entries[0]
+        pon_port = ONUDevice.objects.get().pon_port
+        if pon_port.port_no != whitelisted.pon_port_id or si.of_dpid != whitelisted.device_id:
+            log.warn("ONU disable as location don't match", object=str(si), serial_number=si.serial_number,
+                     **si.tologdict())
+            return False
 
-    def sync_record(self, o):
-        log.info("synching AttWorkflowDriverServiceInstance", object=str(o), **o.tologdict())
+        return True
 
-        if not self.validate_in_external_oss(o):
-            log.error("ONU with serial number %s is not valid in the OSS Database" % o.serial_number)
-            o.valid = "invalid"
+    def sync_record(self, si):
+        log.info("synching AttWorkflowDriverServiceInstance", object=str(si), **si.tologdict())
+
+        if not self.validate_onu(si):
+            log.error("ONU with serial number %s is not valid in the OSS Database" % si.serial_number)
+            si.valid = "invalid"
         else:
-            if self.get_suscriber_c_tag(o.serial_number):
-                self.c_tag = self.get_suscriber_c_tag(o.serial_number)
+            si.valid = "valid"
 
-            o.valid = "valid"
-
-        # Set no_sync=True to prevent the syncstep from running again, and set alway_update_timestamp=True to cause
-        # the model_policy to run again.
-        # TODO(smbaker): Revisit this after fixing this issue in the core.
-        o.no_sync = True
-        o.save(update_fields=["valid", "no_sync", "updated"], always_update_timestamp=True)
+        si.save()
 
     def delete_record(self, o):
         pass
diff --git a/xos/synchronizer/steps/test_sync_att_workflow_driver_service_instance.py b/xos/synchronizer/steps/test_sync_att_workflow_driver_service_instance.py
index db168f7..2b82b1b 100644
--- a/xos/synchronizer/steps/test_sync_att_workflow_driver_service_instance.py
+++ b/xos/synchronizer/steps/test_sync_att_workflow_driver_service_instance.py
@@ -76,7 +76,7 @@
 
         self.sync_step = SyncAttWorkflowDriverServiceInstance
 
-        self.oss = Mock()
+        self.oss = AttWorkflowDriverService()
         self.oss.name = "oss"
         self.oss.id = 5367
 
@@ -84,30 +84,62 @@
         self.o = Mock()
         self.o.serial_number = "BRCM1234"
         self.o.of_dpid = "of:109299321"
+        self.o.pon_port_id = 1
         self.o.owner.leaf_model = self.oss
         self.o.tologdict.return_value = {}
 
+        self.pon_port = PONPort(
+            port_no=1
+        )
+        self.onu = ONUDevice(
+            serial_number=self.o.serial_number,
+            pon_port=self.pon_port
+        )
+
     def tearDown(self):
         self.o = None
         sys.path = self.sys_path_save
 
     def test_sync_valid(self):
-        with patch.object(AttWorkflowDriverWhiteListEntry.objects, "get_items") as whitelist_items:
+        with patch.object(AttWorkflowDriverWhiteListEntry.objects, "get_items") as whitelist_items, \
+            patch.object(ONUDevice.objects, "get_items") as onu_items:
             # Create a whitelist entry for self.o's serial number
-            whitelist_entry = AttWorkflowDriverWhiteListEntry(owner_id=self.oss.id, serial_number=self.o.serial_number)
+            whitelist_entry = AttWorkflowDriverWhiteListEntry(
+                owner_id=self.oss.id,
+                serial_number=self.o.serial_number,
+                device_id=self.o.of_dpid,
+                pon_port_id=1,
+            )
             whitelist_items.return_value = [whitelist_entry]
+            onu_items.return_value = [self.onu]
 
             self.sync_step().sync_record(self.o)
 
             self.assertEqual(self.o.valid, "valid")
-            self.assertTrue(self.o.no_sync)
             self.o.save.assert_called()
 
-    def test_sync_rejected(self):
+    def test_sync_bad_location(self):
+        with patch.object(AttWorkflowDriverWhiteListEntry.objects, "get_items") as whitelist_items, \
+            patch.object(ONUDevice.objects, "get_items") as onu_items:
+            # Create a whitelist entry for self.o's serial number
+            whitelist_entry = AttWorkflowDriverWhiteListEntry(
+                owner_id=self.oss.id,
+                serial_number=self.o.serial_number,
+                device_id="foo",
+                pon_port_id=666
+            )
+            whitelist_items.return_value = [whitelist_entry]
+            onu_items.return_value = [self.onu]
+
+            self.sync_step().sync_record(self.o)
+
+            self.assertEqual(self.o.valid, "invalid")
+            self.o.save.assert_called()
+
+    def test_sync_no_whitelist(self):
         self.sync_step().sync_record(self.o)
 
         self.assertEqual(self.o.valid, "invalid")
-        self.assertTrue(self.o.no_sync)
         self.o.save.assert_called()
 
 if __name__ == '__main__':