[CORD-3156] Adding support for mac_address in olt-service

Change-Id: I945257416f527b2d019231c9abc4cdf44a5a6a58
diff --git a/samples/olt_device.yaml b/samples/olt_device_host_and_port.yaml
similarity index 91%
rename from samples/olt_device.yaml
rename to samples/olt_device_host_and_port.yaml
index 93c905f..db74731 100644
--- a/samples/olt_device.yaml
+++ b/samples/olt_device_host_and_port.yaml
@@ -12,7 +12,7 @@
 # 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 @olt_device.yaml http://192.168.99.100:30007/run
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @olt_device_host_and_port.yaml http://192.168.99.100:30007/run
 
 tosca_definitions_version: tosca_simple_yaml_1_0
 imports:
@@ -32,7 +32,7 @@
       type: tosca.nodes.OLTDevice
       properties:
         name: test_olt
-        device_type: simulated_olt
+        device_type: ponsim
         host: 172.17.0.1
         port: 50060
         switch_datapath_id: of:0000000000000001
diff --git a/samples/olt_device.yaml b/samples/olt_device_mac_address.yaml
similarity index 90%
copy from samples/olt_device.yaml
copy to samples/olt_device_mac_address.yaml
index 93c905f..42213bb 100644
--- a/samples/olt_device.yaml
+++ b/samples/olt_device_mac_address.yaml
@@ -12,7 +12,7 @@
 # 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 @olt_device.yaml http://192.168.99.100:30007/run
+# curl -H "xos-username: admin@opencord.org" -H "xos-password: letmein" -X POST --data-binary @olt_device_mac_address.yaml http://192.168.99.100:30007/run
 
 tosca_definitions_version: tosca_simple_yaml_1_0
 imports:
@@ -33,8 +33,7 @@
       properties:
         name: test_olt
         device_type: simulated_olt
-        host: 172.17.0.1
-        port: 50060
+        mac_address: 00:0c:e2:31:40:00
         switch_datapath_id: of:0000000000000001
         switch_port: "1"
         outer_tpid: "0x8100"
diff --git a/xos/synchronizer/models/models.py b/xos/synchronizer/models/models.py
index ed1e062..3dcefae 100644
--- a/xos/synchronizer/models/models.py
+++ b/xos/synchronizer/models/models.py
@@ -46,6 +46,13 @@
     def get_volt_si(self):
         return VOLTServiceInstance.objects.all()
 
+    def save(self, *args, **kwargs):
+
+        if (self.host or self.port) and self.mac_address:
+            raise XOSValidationError("You can't specify both host/port and mac_address for OLTDevice [host=%s, port=%s, mac_address=%s]" % (self.host, self.port, self.mac_address))
+
+        super(OLTDevice, self).save(*args, **kwargs)
+
     def delete(self, *args, **kwargs):
 
         onus = []
diff --git a/xos/synchronizer/models/test_oltdevice_model.py b/xos/synchronizer/models/test_oltdevice_model.py
index 34bf200..5da9583 100644
--- a/xos/synchronizer/models/test_oltdevice_model.py
+++ b/xos/synchronizer/models/test_oltdevice_model.py
@@ -40,13 +40,24 @@
 
         from models import OLTDevice
 
-        print OLTDevice
-
         self.olt_device = OLTDevice()
         self.olt_device.id = None # this is a new model
         self.olt_device.is_new = True
         self.olt_device.device_id = 1234
 
+    def test_create_mac_address(self):
+        from models import OLTDevice
+        olt = OLTDevice()
+
+        olt.host = "1.1.1.1"
+        olt.port = "9101"
+        olt.mac_address = "00:0c:d5:00:05:40"
+
+        with self.assertRaises(Exception) as e:
+            olt.save()
+
+        self.assertEqual(e.exception.message,
+                         "You can't specify both host/port and mac_address for OLTDevice [host=%s, port=%s, mac_address=%s]" % (olt.host, olt.port, olt.mac_address))
 
     def test_delete(self):
         self.olt_device.delete()
diff --git a/xos/synchronizer/models/volt.xproto b/xos/synchronizer/models/volt.xproto
index 39383f4..3106210 100644
--- a/xos/synchronizer/models/volt.xproto
+++ b/xos/synchronizer/models/volt.xproto
@@ -23,8 +23,9 @@
     required manytoone volt_service->VOLTService:volt_devices = 1 [db_index = True, null = False, blank = False];
     optional string name = 2 [help_text = "name of device", max_length = 254, null = True, db_index = False, blank = True, unique = True];
     required string device_type = 3 [help_text = "Device Type", default = "asfvolt16_olt", max_length = 254, null = False, db_index = False, blank = False, tosca_key=True];
-    required string host = 4 [help_text = "Host", max_length = 254, null = False, db_index = False, blank = False, tosca_key=True];
-    required int32 port = 5 [help_text = "Fabric port", null = False, db_index = False, blank = False, tosca_key=True];
+    optional string host = 4 [help_text = "Device IP", max_length = 254, null = True, db_index = False, blank = True];
+    optional int32 port = 5 [help_text = "Device port", null = True, db_index = False, blank = True, tosca_key_one_of=host];
+    optional string mac_address = 6 [help_text = "Device mac address", null = True, db_index = False, blank = True, tosca_key_one_of=host];
 
     optional string device_id = 10 [help_text = "Device ID", null = True, db_index = False, blank = False, feedback_state = True];
     optional string admin_state = 11 [help_text = "admin_state", null = True, db_index = False, blank = False, feedback_state = True];
diff --git a/xos/synchronizer/steps/sync_olt_device.py b/xos/synchronizer/steps/sync_olt_device.py
index 8ebef89..29722f9 100644
--- a/xos/synchronizer/steps/sync_olt_device.py
+++ b/xos/synchronizer/steps/sync_olt_device.py
@@ -57,14 +57,13 @@
         voltha = Helpers.get_voltha_info(model.volt_service)
 
         data = {
-            "type": model.device_type,
-            "host_and_port": "%s:%s" % (model.host, model.port)
+            "type": model.device_type
         }
 
-        if model.device_type == 'simulated_olt':
-            # Simulated devices won't accept host and port. This is to enable tests in voltha without having a real olt or ponsim
-            data.pop('host_and_port')
-            data['mac_address'] = "00:0c:e2:31:40:00"
+        if hasattr(model, "host") and hasattr(model, "port"):
+            data["host_and_port"] = "%s:%s" % (model.host, model.port)
+        elif hasattr(model, "mac_address"):
+            data["mac_address"] = model.mac_address
 
         log.info("Pushing OLT to Voltha", data=data)
 
diff --git a/xos/synchronizer/steps/test_sync_olt_device.py b/xos/synchronizer/steps/test_sync_olt_device.py
index 4d785b5..2ee20ff 100644
--- a/xos/synchronizer/steps/test_sync_olt_device.py
+++ b/xos/synchronizer/steps/test_sync_olt_device.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import unittest
+import functools
 from mock import patch, call, Mock, PropertyMock
 import requests_mock
 
@@ -51,6 +52,12 @@
             return False
     return True
 
+def match_json(desired, req):
+    if desired!=req.json():
+        raise Exception("Got request %s, but body is not matching" % req.url)
+        return False
+    return True
+
 class TestSyncOLTDevice(unittest.TestCase):
     def setUp(self):
         global DeferredException
@@ -82,7 +89,7 @@
         pon_port.port_id = "00ff00"
         pon_port.s_tag = "s_tag"
 
-        # Create a mock service instance
+        # Create a mock OLTDevice
         o = Mock()
         o.volt_service.voltha_url = "voltha_url"
         o.volt_service.voltha_port = 1234
@@ -176,7 +183,13 @@
         """
         If device.enable succed should fetch the state, retrieve the of_id and push it to ONOS
         """
-        m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json={"id": "123"})
+
+        expected_conf = {
+            "type": self.o.device_type,
+            "host_and_port": "%s:%s" % (self.o.host, self.o.port)
+        }
+
+        m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json={"id": "123"}, additional_matcher=functools.partial(match_json, expected_conf))
         m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
         m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ENABLED", "admin_state": "ACTIVE"})
         logical_devices = {
@@ -196,6 +209,42 @@
         self.o.save.assert_called_once()
 
     @requests_mock.Mocker()
+    def test_sync_record_success_mac_address(self, m):
+        """
+        A device should be pre-provisioned via mac_address, the the process is the same
+        """
+
+        del self.o.host
+        del self.o.port
+        self.o.mac_address = "00:0c:e2:31:40:00"
+
+        expected_conf = {
+            "type": self.o.device_type,
+            "mac_address": self.o.mac_address
+        }
+
+        m.post("http://voltha_url:1234/api/v1/devices", status_code=200, json={"id": "123"},
+               additional_matcher=functools.partial(match_json, expected_conf))
+        m.post("http://voltha_url:1234/api/v1/devices/123/enable", status_code=200)
+        m.get("http://voltha_url:1234/api/v1/devices/123", json={"oper_status": "ENABLED", "admin_state": "ACTIVE"})
+        logical_devices = {
+            "items": [
+                {"root_device_id": "123", "id": "0001000ce2314000", "datapath_id": "55334486016"},
+                {"root_device_id": "0001cc4974a62b87", "id": "0001000000000001"}
+            ]
+        }
+        m.get("http://voltha_url:1234/api/v1/logical_devices", status_code=200, json=logical_devices)
+
+        m.post("http://onos_voltha_url:4321/onos/v1/network/configuration/", status_code=200,
+               additional_matcher=match_onos_req, json={})
+
+        self.sync_step().sync_record(self.o)
+        self.assertEqual(self.o.admin_state, "ACTIVE")
+        self.assertEqual(self.o.oper_status, "ENABLED")
+        self.assertEqual(self.o.of_id, "0001000ce2314000")
+        self.o.save.assert_called_once()
+
+    @requests_mock.Mocker()
     def test_sync_record_already_existing_in_voltha(self, m):
         # mock device feedback state
         self.o.device_id = "123"