[CORD-3091] Pulling NNI and PON ports

Change-Id: I001782ffd8a092371f4939869c9c6dda2360bf98
diff --git a/samples/olt_device.yaml b/samples/olt_device.yaml
index c83b65c..8277a9b 100644
--- a/samples/olt_device.yaml
+++ b/samples/olt_device.yaml
@@ -44,25 +44,3 @@
         - volt_service:
             node: service#volt
             relationship: tosca.relationships.BelongsToOne
-
-    pon_port:
-      type: tosca.nodes.PONPort
-      properties:
-        name: test_olt_port_1
-        port_id: ff00ff
-        s_tag: 111
-      requirements:
-        - olt_device:
-            node: device#olt
-            relationship: tosca.relationships.BelongsToOne
-
-    onu:
-      type: tosca.nodes.ONUDevice
-      properties:
-        serial_number: BRCM1234
-        vendor: Broadcom
-        device_type: broadcom_onu
-      requirements:
-        - pon_port:
-            node: pon_port
-            relationship: tosca.relationships.BelongsToOne
diff --git a/samples/pon_port.yaml b/samples/pon_port.yaml
new file mode 100644
index 0000000..d35d0b8
--- /dev/null
+++ b/samples/pon_port.yaml
@@ -0,0 +1,44 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @pon_port.yaml http://192.168.99.100:30007/run
+
+tosca_definitions_version: tosca_simple_yaml_1_0
+imports:
+  - custom_types/oltdevice.yaml
+  - custom_types/onudevice.yaml
+  - custom_types/ponport.yaml
+  - custom_types/voltservice.yaml
+description: Create a simulated OLT Device in VOLTHA
+topology_template:
+  node_templates:
+
+    device#olt:
+      type: tosca.nodes.OLTDevice
+      properties:
+        device_type: simulated_olt
+        host: 172.17.0.1
+        port: 50060
+        must-exist: true
+
+    pon_port:
+      type: tosca.nodes.PONPort
+      properties:
+        name: test_olt_port_1
+        port_no: 2
+        s_tag: 222
+      requirements:
+        - olt_device:
+            node: device#olt
+            relationship: tosca.relationships.BelongsToOne
diff --git a/xos/synchronizer/models/models.py b/xos/synchronizer/models/models.py
index eeb5e74..9431488 100644
--- a/xos/synchronizer/models/models.py
+++ b/xos/synchronizer/models/models.py
@@ -12,12 +12,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import random
+
 from core.models.xosbase import *
+from xos.exceptions import XOSValidationError
 
 from models_decl import VOLTService_decl
 from models_decl import VOLTServiceInstance_decl
 from models_decl import OLTDevice_decl
 from models_decl import PONPort_decl
+from models_decl import NNIPort_decl
 from models_decl import ONUDevice_decl
 
 class VOLTService(VOLTService_decl):
@@ -45,7 +49,44 @@
 
 class PONPort(PONPort_decl):
     class Meta:
-        proxy = True 
+        proxy = True
+
+    def generate_tag(self):
+        # NOTE this method will loop if available c_tags are ended
+        tag = random.randint(16, 4096)
+        if tag in self.get_used_s_tags():
+            return self.generate_tag()
+        return tag
+
+    def get_used_s_tags(self):
+        same_olt_device = OLTDevice.objects.filter(device_id=self.olt_device)
+        return [s.c_tag for s in same_olt_device]
+
+    def save(self, *args, **kwargs):
+        # validate s_tag
+        if hasattr(self, 's_tag') and self.s_tag is not None:
+            is_update_with_same_tag = False
+
+            if not self.is_new:
+                # if it is an update, but the tag is the same, skip validation
+                existing = PONPort.objects.filter(s_tag=self.s_tag)
+
+                if len(existing) > 0 and existing[0].s_tag == self.s_tag and existing[0].id == self.id:
+                    is_update_with_same_tag = True
+
+            if self.s_tag in self.get_used_s_tags() and not is_update_with_same_tag:
+                raise XOSValidationError(
+                    "The s_tag you specified (%s) has already been used on device %s" % (self.s_tag, self.onu_device))
+
+        if not hasattr(self, "s_tag") or self.s_tag is None:
+            self.s_tag = self.generate_tag()
+
+        super(PONPort, self).save(*args, **kwargs)
+
+
+class NNIPort(NNIPort_decl):
+    class Meta:
+        proxy = True
 
 
 class ONUDevice(ONUDevice_decl):
diff --git a/xos/synchronizer/models/volt.xproto b/xos/synchronizer/models/volt.xproto
index 79c753e..0eda4e0 100644
--- a/xos/synchronizer/models/volt.xproto
+++ b/xos/synchronizer/models/volt.xproto
@@ -48,13 +48,26 @@
     optional string outer_tpid = 19 [help_text = "Outer VLAN id field EtherType", null = False, db_index = False, blank = False];
 }
 
-message PONPort (XOSBase){
+message PortBase (XOSBase){
+    option gui_hidden = True;
+
+    required string name = 1 [db_index = True, null = False, blank = False];
+    required int32 port_no = 3 [help_text = "Port ID", null = False, db_index = False, blank = False];
+
+    optional string admin_state = 4 [help_text = "admin_state", null = True, db_index = False, blank = False, feedback_state = True];
+    optional string oper_status = 5 [help_text = "oper_status", null = True, db_index = False, blank = False, feedback_state = True];
+}
+
+message PONPort (PortBase){
     option verbose_name = "PON Port";
 
-    required string name = 1 [db_index = True, null = False, blank = False, unique_with="olt_device"];
-    required manytoone olt_device->OLTDevice:ports = 2 [db_index = True, null = False, blank = False];
-    required string port_id = 3 [help_text = "Port ID", max_length = 254, null = False, db_index = False, blank = False];
-    required int32 s_tag = 4 [help_text = "S Tag", null = False, db_index = False, blank = False];
+    required manytoone olt_device->OLTDevice:pon_ports = 1 [db_index = True, null = False, blank = False];
+    required int32 s_tag = 2 [help_text = "S Tag", null = False, db_index = False, blank = False];
+}
+
+message NNIPort (PortBase) {
+    option verbose_name = "NNI Port";
+    required manytoone olt_device->OLTDevice:nni_ports = 1 [db_index = True, null = False, blank = False];
 }
 
 message ONUDevice (XOSBase){
diff --git a/xos/synchronizer/pull_steps/pull_olts.py b/xos/synchronizer/pull_steps/pull_olts.py
index 36cac45..370250d 100644
--- a/xos/synchronizer/pull_steps/pull_olts.py
+++ b/xos/synchronizer/pull_steps/pull_olts.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from synchronizers.new_base.pullstep import PullStep
-from synchronizers.new_base.modelaccessor import model_accessor, OLTDevice, VOLTService
+from synchronizers.new_base.modelaccessor import model_accessor, OLTDevice, VOLTService, PONPort, NNIPort
 
 from xosconfig import Config
 from multistructlog import create_logger
@@ -79,9 +79,6 @@
             # TODO
             # [ ] delete OLTS as OLTDevice.objects.all() - updated OLTs
 
-            if r.status_code != 200:
-                log.info("It was not possible to fetch devices from VOLTHA")
-
             olts_in_voltha = self.create_or_update_olts(devices)
 
         except ConnectionError, e:
@@ -102,10 +99,13 @@
                 else:
                     [host, port] = olt["host_and_port"].split(":")
                 model = OLTDevice.objects.filter(device_type=olt["type"], host=host, port=port)[0]
+
                 log.debug("OLTDevice already exists, updating it", device_type=olt["type"], host=host, port=port)
 
                 if model.enacted < model.updated:
                     log.info("Skipping pull on OLTDevice %s as enacted < updated" % model.name, name=model.name, id=model.id, enacted=model.enacted, updated=model.updated)
+                    # if we are not updating the device we still need to pull ports
+                    self.fetch_olt_ports(model)
                     return
 
             except IndexError:
@@ -115,6 +115,10 @@
                 if olt["type"] == "simulated_olt":
                     model.host = "172.17.0.1"
                     model.port = 50060
+                else:
+                    [host, port] = olt["host_and_port"].split(":")
+                    model.host = host
+                    model.port = int(port)
 
                 log.debug("OLTDevice is new, creating it", device_type=olt["type"], host=host, port=port)
 
@@ -131,10 +135,85 @@
 
             model.save()
 
+            self.fetch_olt_ports(model)
+
             updated_olts.append(model)
 
         return updated_olts
 
+    def fetch_olt_ports(self, olt):
+        voltha_url = Helpers.get_voltha_info(self.volt_service)['url']
+        voltha_port = Helpers.get_voltha_info(self.volt_service)['port']
+
+        try:
+            r = requests.get("%s:%s/api/v1/devices/%s/ports" % (voltha_url, voltha_port, olt.device_id))
+
+            if r.status_code != 200:
+                log.info("It was not possible to fetch ports from VOLTHA for device %s" % olt.device_id)
+
+            ports = r.json()['items']
+
+            log.debug("received ports", ports=ports, olt=olt.device_id)
+
+            self.create_or_update_ports(ports, olt)
+
+        except ConnectionError, e:
+            log.warn("It was not possible to connect to VOLTHA", reason=e)
+            return
+        except InvalidURL, e:
+            log.warn("VOLTHA url is invalid, is it configured in the VOLTService?", reason=e)
+            return
+        return
+
+    def create_or_update_ports(self, ports, olt):
+        nni_ports = [p for p in ports if "ETHERNET_NNI" in p["type"]]
+        pon_ports = [p for p in ports if "PON_OLT" in p["type"]]
+
+        self.create_or_update_nni_port(nni_ports, olt)
+        self.create_or_update_pon_port(pon_ports, olt)
+
+    def create_or_update_pon_port(self, pon_ports, olt):
+
+        update_ports = []
+
+        for port in pon_ports:
+            try:
+                model = PONPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
+                log.debug("PONPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
+            except IndexError:
+                model = PONPort()
+                model.port_no = port["port_no"]
+                model.olt_device_id = olt.id
+                model.name = port["label"]
+                log.debug("PONPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
+
+            model.admin_state = port["admin_state"]
+            model.oper_status = port["oper_status"]
+            model.save()
+            update_ports.append(model)
+        return update_ports
+
+    def create_or_update_nni_port(self, nni_ports, olt):
+        update_ports = []
+
+        for port in nni_ports:
+            try:
+                model = NNIPort.objects.filter(port_no=port["port_no"], olt_device_id=olt.id)[0]
+                model.xos_managed = False
+                log.debug("NNIPort is new, creating it", port_no=port["port_no"], olt_device_id=olt.id)
+            except IndexError:
+                model = NNIPort()
+                model.port_no = port["port_no"]
+                model.olt_device_id = olt.id
+                model.name = port["label"]
+                model.xos_managed = False
+                log.debug("NNIPort already exists, updating it", port_no=port["port_no"], olt_device_id=olt.id)
+
+            model.admin_state = port["admin_state"]
+            model.oper_status = port["oper_status"]
+            model.save()
+            update_ports.append(model)
+        return update_ports
 
 
 
diff --git a/xos/synchronizer/pull_steps/pull_onus.py b/xos/synchronizer/pull_steps/pull_onus.py
index cc60ca3..b762297 100644
--- a/xos/synchronizer/pull_steps/pull_onus.py
+++ b/xos/synchronizer/pull_steps/pull_onus.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from synchronizers.new_base.pullstep import PullStep
-from synchronizers.new_base.modelaccessor import model_accessor, ONUDevice, VOLTService, OLTDevice
+from synchronizers.new_base.modelaccessor import model_accessor, ONUDevice, VOLTService, OLTDevice, PONPort
 
 from xosconfig import Config
 from multistructlog import create_logger
@@ -34,8 +34,6 @@
         super(ONUDevicePullStep, self).__init__(observed_model=ONUDevice)
 
     def pull_records(self):
-        return
-        # FIXME we need to pull PON Ports before
         log.info("pulling ONU devices from VOLTHA")
 
         try:
@@ -84,7 +82,7 @@
                 log.debug("ONUDevice already exists, updating it", serial_number=onu["serial_number"])
 
                 if model.enacted < model.updated:
-                    log.info("Skipping pull on ONUDevice %s as enacted < updated" % model.name, name=model.name, id=model.id, enacted=model.enacted, updated=model.updated)
+                    log.info("Skipping pull on ONUDevice %s as enacted < updated" % model.serial_number, serial_number=model.serial_number, id=model.id, enacted=model.enacted, updated=model.updated)
                     return
 
             except IndexError:
@@ -101,11 +99,13 @@
             model.admin_state = onu["admin_state"]
             model.oper_status = onu["oper_status"]
             model.connect_status = onu["connect_status"]
+            model.xos_managed = False
 
-            # olt = OLTDevice.objects.get(device_id=onu["proxy_address"]["device_id"])
-            #
-            # model.olt_device = olt
-            # model.olt_device_id = olt.id
+            olt = OLTDevice.objects.get(device_id=onu["parent_id"])
+            pon_port = PONPort.objects.get(port_no=onu["parent_port_no"], olt_device_id=olt.id)
+
+            model.pon_port = pon_port
+            model.pon_port_id = pon_port.id
 
             model.save()
 
diff --git a/xos/synchronizer/pull_steps/test_pull_olts.py b/xos/synchronizer/pull_steps/test_pull_olts.py
index a0ef017..91b64e3 100644
--- a/xos/synchronizer/pull_steps/test_pull_olts.py
+++ b/xos/synchronizer/pull_steps/test_pull_olts.py
@@ -104,6 +104,25 @@
             ]
         }
 
+        self.ports = {
+            "items": [
+                {
+                    "label": "PON port",
+                    "port_no": 1,
+                    "type": "PON_OLT",
+                    "admin_state": "ENABLED",
+                    "oper_status": "ACTIVE"
+                },
+                {
+                    "label": "NNI facing Ethernet port",
+                    "port_no": 2,
+                    "type": "ETHERNET_NNI",
+                    "admin_state": "ENABLED",
+                    "oper_status": "ACTIVE"
+                }
+            ]
+        }
+
     def tearDown(self):
         sys.path = self.sys_path_save
 
@@ -115,10 +134,13 @@
     def test_pull(self, m):
 
         with patch.object(VOLTService.objects, "all") as olt_service_mock, \
-                patch.object(OLTDevice, "save") as mock_save:
+                patch.object(OLTDevice, "save") as mock_olt_save, \
+                patch.object(PONPort, "save") as mock_pon_save, \
+                patch.object(NNIPort, "save") as mock_nni_save:
             olt_service_mock.return_value = [self.volt_service]
 
             m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+            m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
             m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
 
             self.sync_step().pull_records()
@@ -131,7 +153,9 @@
             # self.assertEqual(existing_olt.of_id, "of_id")
             # self.assertEqual(existing_olt.dp_id, "of:0000000ce2314000")
 
-            mock_save.assert_called()
+            mock_olt_save.assert_called()
+            mock_pon_save.assert_called()
+            mock_nni_save.assert_called()
 
     @requests_mock.Mocker()
     def test_pull_existing(self, m):
@@ -141,12 +165,15 @@
         existing_olt.updated = 1
 
         with patch.object(VOLTService.objects, "all") as olt_service_mock, \
-        patch.object(OLTDevice.objects, "filter") as mock_get, \
-        patch.object(existing_olt, "save") as  mock_save:
+                patch.object(OLTDevice.objects, "filter") as mock_get, \
+                patch.object(PONPort, "save") as mock_pon_save, \
+                patch.object(NNIPort, "save") as mock_nni_save, \
+                patch.object(existing_olt, "save") as  mock_olt_save:
             olt_service_mock.return_value = [self.volt_service]
             mock_get.return_value = [existing_olt]
 
             m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+            m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
             m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
 
             self.sync_step().pull_records()
@@ -158,26 +185,35 @@
             self.assertEqual(existing_olt.of_id, "of_id")
             self.assertEqual(existing_olt.dp_id, "of:0000000ce2314000")
 
-            mock_save.assert_called()
+            mock_olt_save.assert_called()
+            mock_pon_save.assert_called()
+            mock_nni_save.assert_called()
 
     @requests_mock.Mocker()
     def test_pull_existing_do_not_sync(self, m):
         existing_olt = Mock()
         existing_olt.enacted = 1
         existing_olt.updated = 2
+        existing_olt.device_id = "test_id"
 
         with patch.object(VOLTService.objects, "all") as olt_service_mock, \
-                patch.object(OLTDevice.objects, "get") as mock_get, \
-                patch.object(existing_olt, "save") as  mock_save:
+                patch.object(OLTDevice.objects, "filter") as mock_get, \
+                patch.object(PONPort, "save") as mock_pon_save, \
+                patch.object(NNIPort, "save") as mock_nni_save, \
+                patch.object(existing_olt, "save") as mock_olt_save:
+
             olt_service_mock.return_value = [self.volt_service]
-            mock_get.return_value = existing_olt
+            mock_get.return_value = [existing_olt]
 
             m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
+            m.get("http://voltha_url:1234/api/v1/devices/test_id/ports", status_code=200, json=self.ports)
             m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=self.logical_devices)
 
             self.sync_step().pull_records()
 
-            mock_save.assert_not_called()
+            mock_olt_save.assert_not_called()
+            mock_pon_save.assert_called()
+            mock_nni_save.assert_called()
 
 if __name__ == "__main__":
     unittest.main()
\ No newline at end of file
diff --git a/xos/synchronizer/pull_steps/test_pull_onus.py b/xos/synchronizer/pull_steps/test_pull_onus.py
index b8ffa5f..a2b948b 100644
--- a/xos/synchronizer/pull_steps/test_pull_onus.py
+++ b/xos/synchronizer/pull_steps/test_pull_onus.py
@@ -85,6 +85,10 @@
         self.olt = Mock()
         self.olt.id = 1
 
+        # mock pon port
+        self.pon_port = Mock()
+        self.pon_port.id = 1
+
         # mock voltha responses
         self.devices = {
             "items": [
@@ -99,9 +103,8 @@
                     "admin_state": "ENABLED",
                     "oper_status": "ACTIVE",
                     "connect_status": "REACHABLE",
-                    "proxy_address": {
-                        "device_id": "00010fc93996afea"
-                    }
+                    "parent_id": "00010fc93996afea",
+                    "parent_port_no": 1
                 }
             ]
         }
@@ -114,12 +117,14 @@
             self.assertFalse(m.called)
 
     @requests_mock.Mocker()
-    def _test_pull(self, m):
+    def test_pull(self, m):
 
         with patch.object(VOLTService.objects, "all") as olt_service_mock, \
                 patch.object(OLTDevice.objects, "get") as mock_olt_device, \
+                patch.object(PONPort.objects, "get") as mock_pon_port, \
                 patch.object(ONUDevice, "save") as mock_save:
             olt_service_mock.return_value = [self.volt_service]
+            mock_pon_port.return_value = self.pon_port
             mock_olt_device.return_value = self.olt
 
             m.get("http://voltha_url:1234/api/v1/devices", status_code=200, json=self.devices)
@@ -134,7 +139,7 @@
             # self.assertEqual(existing_olt.of_id, "of_id")
             # self.assertEqual(existing_olt.dp_id, "of:0000000ce2314000")
 
-            mock_save.assert_called()
+            mock_save.assert_called_with()
 
     @requests_mock.Mocker()
     def _test_pull_existing(self, m):
diff --git a/xos/synchronizer/steps/sync_olt_device.py b/xos/synchronizer/steps/sync_olt_device.py
index 5997de5..8ebef89 100644
--- a/xos/synchronizer/steps/sync_olt_device.py
+++ b/xos/synchronizer/steps/sync_olt_device.py
@@ -17,7 +17,7 @@
 import requests
 from multistructlog import create_logger
 from requests.auth import HTTPBasicAuth
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
+from synchronizers.new_base.syncstep import SyncStep, DeferredException
 from synchronizers.new_base.modelaccessor import OLTDevice, model_accessor
 from xosconfig import Config
 
@@ -115,11 +115,17 @@
 
     def configure_onos(self, model):
 
+        log.info("Adding OLT device in onos-voltha", object=str(model), **model.tologdict())
+
         onos_voltha = Helpers.get_onos_voltha_info(model.volt_service)
         onos_voltha_basic_auth = HTTPBasicAuth(onos_voltha['user'], onos_voltha['pass'])
 
-        # For now, we assume that each OLT has only one port
-        vlan = model.ports.all()[0].s_tag
+        try:
+            # NOTE For now, we assume that each OLT has only one pon port
+            vlan = model.pon_ports.all()[0].s_tag
+        except Exception as e:
+            raise DeferredException("Waiting for pon_ports to come up")
+
 
         # Add device info to onos-voltha
         data = {
@@ -162,34 +168,34 @@
 
         self.configure_onos(model)
 
-    def delete_record(self, o):
-        log.info("Deleting OLT device", object=str(o), **o.tologdict())
+    def delete_record(self, model):
+        log.info("Deleting OLT device", object=str(model), **model.tologdict())
 
-        voltha = Helpers.get_voltha_info(o.volt_service)
-        onos_voltha = Helpers.get_onos_voltha_info(o.volt_service)
+        voltha = Helpers.get_voltha_info(model.volt_service)
+        onos_voltha = Helpers.get_onos_voltha_info(model.volt_service)
         onos_voltha_basic_auth = HTTPBasicAuth(onos_voltha['user'], onos_voltha['pass'])
 
-        if not o.device_id:
-            log.error("OLTDevice %s has no device_id" % o.name)
+        if not model.device_id:
+            log.error("OLTDevice %s has no device_id" % model.name)
         else:
             # Disable the OLT device
-            request = requests.post("%s:%d/api/v1/devices/%s/disable" % (voltha['url'], voltha['port'], o.device_id))
+            request = requests.post("%s:%d/api/v1/devices/%s/disable" % (voltha['url'], voltha['port'], model.device_id))
 
             if request.status_code != 200:
-                log.error("Failed to disable OLT device in VOLTHA: %s - %s" % (o.name, o.device_id), rest_response=request.text, rest_status_code=request.status_code)
+                log.error("Failed to disable OLT device in VOLTHA: %s - %s" % (model.name, model.device_id), rest_response=request.text, rest_status_code=request.status_code)
                 raise Exception("Failed to disable OLT device in VOLTHA")
 
             # Delete the OLT device
-            request = requests.delete("%s:%d/api/v1/devices/%s/delete" % (voltha['url'], voltha['port'], o.device_id))
+            request = requests.delete("%s:%d/api/v1/devices/%s/delete" % (voltha['url'], voltha['port'], model.device_id))
 
             if request.status_code != 200:
-                log.error("Failed to delete OLT device from VOLTHA: %s - %s" % (o.name, o.device_id), rest_response=request.text, rest_status_code=request.status_code)
+                log.error("Failed to delete OLT device from VOLTHA: %s - %s" % (model.name, model.device_id), rest_response=request.text, rest_status_code=request.status_code)
                 raise Exception("Failed to delete OLT device from VOLTHA")
 
             # Remove the device from ONOS
             request = requests.delete("%s:%d/onos/v1/network/configuration/devices/%s" % (
-            onos_voltha['url'], onos_voltha['port'], o.of_id), auth=onos_voltha_basic_auth)
+                onos_voltha['url'], onos_voltha['port'], model.of_id), auth=onos_voltha_basic_auth)
 
             if request.status_code != 204:
-                log.error("Failed to remove OLT device from ONOS: %s - %s" % (o.name, o.of_id), rest_response=request.text, rest_status_code=request.status_code)
+                log.error("Failed to remove OLT device from ONOS: %s - %s" % (model.name, model.of_id), rest_response=request.text, rest_status_code=request.status_code)
                 raise Exception("Failed to remove OLT device from ONOS")
diff --git a/xos/synchronizer/steps/test_sync_olt_device.py b/xos/synchronizer/steps/test_sync_olt_device.py
index 55d477e..4d785b5 100644
--- a/xos/synchronizer/steps/test_sync_olt_device.py
+++ b/xos/synchronizer/steps/test_sync_olt_device.py
@@ -53,6 +53,7 @@
 
 class TestSyncOLTDevice(unittest.TestCase):
     def setUp(self):
+        global DeferredException
         self.sys_path_save = sys.path
         sys.path.append(xos_dir)
         sys.path.append(os.path.join(xos_dir, 'synchronizers', 'new_base'))
@@ -74,7 +75,7 @@
                                                          get_models_fn("../profiles/rcord", "rcord.xproto")])
 
         import synchronizers.new_base.modelaccessor
-        from sync_olt_device import SyncOLTDevice
+        from sync_olt_device import SyncOLTDevice, DeferredException
         self.sync_step = SyncOLTDevice
 
         pon_port = Mock()
@@ -108,7 +109,7 @@
 
         o.save.return_value = "Saved"
 
-        o.ports.all.return_value = [pon_port]
+        o.pon_ports.all.return_value = [pon_port]
 
         self.o = o
 
@@ -221,5 +222,16 @@
 
         # We don't need to assert here if there are no exceptions happening
 
+    def test_deferred_for_port(self):
+        self.o.pon_ports.all.side_effect = Exception
+        with self.assertRaises(DeferredException) as e:
+            self.sync_step().configure_onos(self.o)
+        self.assertEqual(e.exception.message, "Waiting for pon_ports to come up")
+
+        self.o.pon_ports.all.return_value = []
+        with self.assertRaises(DeferredException) as e:
+            self.sync_step().configure_onos(self.o)
+        self.assertEqual(e.exception.message, "Waiting for pon_ports to come up")
+
 if __name__ == "__main__":
     unittest.main()