Merge "[CORD-2704] Adding documentation for progran"
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..ec62517
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,5 @@
+[gerrit]
+host=gerrit.opencord.org
+port=29418
+project=progran.git
+defaultremote=origin
diff --git a/samples/docker-compose.yml b/samples/docker-compose.yml
index eca4229..24f57d3 100644
--- a/samples/docker-compose.yml
+++ b/samples/docker-compose.yml
@@ -1,13 +1,27 @@
+# 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.
+
 # Progran for docker-compose
 version: '2'
 
 services:
    progran:
-      image: cemturker/prograncontrollermcord:0.1.2
+      image: cemturker/prograncontrollermcord:0.1.8
       ports:
        - "221:22"
        - "6655:6653"
        - "8103:8101"
        - "8183:8181"
-       - "9877:9876"
+       - "9878:9876"
        - "4010:4010"
diff --git a/samples/handover.json b/samples/handover.json
index 0fad7f0..e060397 100644
--- a/samples/handover.json
+++ b/samples/handover.json
@@ -1,9 +1,9 @@
 {
   "A3offset":2,
-  "A5Thresh1Rsrp":-97,
-  "A5Thresh1Rsrq":-10,
-  "A5Thresh2Rsrp":-95,
-  "A5Thresh2Rsrq":-8,
+  "A5Thresh1Rsrp":97,
+  "A5Thresh1Rsrq":10,
+  "A5Thresh2Rsrp":95,
+  "A5Thresh2Rsrq":8,
   "A5TriggerType":0,
   "HysteresisA3":1,
   "HysteresisA5":1,
diff --git a/samples/imsi_to_profile.json b/samples/imsi_to_profile.json
index a14f040..202b70c 100644
--- a/samples/imsi_to_profile.json
+++ b/samples/imsi_to_profile.json
@@ -1,4 +1,4 @@
 {
-  "provider_service_instance_id": 4,
-  "subscriber_service_instance_id": 3
+  "provider_service_instance_id": 12,
+  "subscriber_service_instance_id": 15
 }
\ No newline at end of file
diff --git a/samples/profile.json b/samples/profile.json
index ffc3e22..3c3b6b4 100644
--- a/samples/profile.json
+++ b/samples/profile.json
@@ -2,8 +2,8 @@
   "name":"testcem",
   "DlSchedType":"RR",
   "UlSchedType":"RR",
-  "DlAllocRBRate":16,
-  "UlAllocRBRate":15,
+  "DlAllocRBRate":20,
+  "UlAllocRBRate":20,
   "AdmControl":1,
   "CellIndividualOffset":1,
   "mmeip":"192.168.28.35",
diff --git a/samples/progran-curl.sh b/samples/progran-curl.sh
index 7353fd0..6f1b6be 100644
--- a/samples/progran-curl.sh
+++ b/samples/progran-curl.sh
@@ -1,5 +1,19 @@
 #!/usr/bin/env bash
 
+# 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.
+
 XOS_URL=127.0.0.1
 XOS_PORT=9101
 XOS_USERNAME=xosadmin@opencord.org
diff --git a/test/test.py b/test/test.py
new file mode 100644
index 0000000..e40cb08
--- /dev/null
+++ b/test/test.py
@@ -0,0 +1,185 @@
+# 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.
+
+import requests
+from requests.auth import HTTPBasicAuth
+import unittest
+import json
+from time import sleep
+
+timeout = 10
+
+baseUrl = 'http://10.128.22.3'
+username = 'xosadmin@opencord.org'
+password = '1UvlBSNmBCbqaHzTC8qV'
+
+onosUrl = 'http://10.128.22.3:8183'
+onosUser = 'karaf'
+onosPass = 'karaf'
+
+class ProgranTest(unittest.TestCase):
+
+    # def test_connection(self):
+    #     url = "%s/xosapi/v1/progran/progranserviceinstances" % baseUrl
+    #     r = requests.get(url,  auth=HTTPBasicAuth(username, password))
+    #     res = r.json()
+    #     self.assertTrue(isinstance(res['items'], list))
+
+    def test_create_profile(self):
+
+        handover_id = None
+        profile_id = None
+        imsi_id = None
+        enodeb_id = None
+
+        print "\nCreate profile"
+
+        # Create handover
+        handover_data = {
+            "A3offset": 2,
+            "A5Thresh1Rsrp": 97,
+            "A5Thresh1Rsrq": 10,
+            "A5Thresh2Rsrp": 95,
+            "A5Thresh2Rsrq": 8,
+            "A5TriggerType": 0,
+            "HysteresisA3": 1,
+            "HysteresisA5": 1,
+            "A3TriggerQuantity": 10,
+            "A5TriggerQuantity": 20
+        }
+
+        handover_url = "%s/xosapi/v1/progran/handovers" % baseUrl
+        hr = requests.post(handover_url, data=json.dumps(handover_data), auth=HTTPBasicAuth(username, password))
+        handover_res = hr.json()
+        handover_id = handover_res['id']
+        self.assertEqual(handover_res['backend_code'], 0)
+
+        # Create profile
+        profile_data = {
+            "name": "automated_test_1",
+            "DlSchedType": "RR",
+            "UlSchedType": "RR",
+            "DlAllocRBRate": 20,
+            "UlAllocRBRate": 20,
+            "AdmControl": 1,
+            "CellIndividualOffset": 1,
+            "mmeip": "192.168.61.30",
+            "mmeport": "36412",
+            "SubsProfile": "",
+            "DlWifiRate": 13,
+            "DlUeAllocRbRate": 12,
+            "handover_id": handover_id
+        }
+
+        profile_url = "%s/xosapi/v1/progran/progranserviceinstances" % baseUrl
+        pr = requests.post(profile_url, data=json.dumps(profile_data), auth=HTTPBasicAuth(username, password))
+        profile_res = pr.json()
+        profile_id = profile_res['id']
+        self.assertEqual(profile_res['backend_code'], 0)
+
+        # Read profile from ONOS
+        print "\nRead profile from ONOS"
+        sleep(timeout)
+
+        profile_read_url = "%s/onos/progran/profile/%s" % (onosUrl, profile_data['name'])
+        pr = requests.get(profile_read_url, auth=HTTPBasicAuth(username, password))
+        profile_read_res = pr.json()['ProfileArray'][0]
+        self.assertEqual(profile_read_res['Name'], profile_data['name'])
+        # TODO check for mme details
+
+        print "\nAdd IMSI"
+        # Add IMSI
+        imsi_data = {
+            "imsi_number": "302720100000421",
+            "apn_number": "",
+            "ue_status": 0
+        }
+        imsi_url = "%s/xosapi/v1/mcord/mcordsubscriberinstances" % baseUrl
+        ir = requests.post(imsi_url, data=json.dumps(imsi_data), auth=HTTPBasicAuth(username, password))
+        imsi_res = ir.json()
+        imsi_id = imsi_res['id']
+        self.assertEqual(imsi_res['backend_code'], 0)
+
+        # Read IMSI and check status
+        print "\nRead IMSI from ONOS"
+        sleep(timeout)
+
+        imsi_read_url = "%s/onos/progran/imsi/%s" % (onosUrl, imsi_data['imsi_number'])
+        pr = requests.get(imsi_read_url, auth=HTTPBasicAuth(username, password))
+        imsi_res = pr.json()['ImsiArray'][0]
+        self.assertEqual(imsi_res['IMSI'], imsi_data['imsi_number'])
+
+        # Add link from Profile to IMSI
+        print "\nAdding imsi to profile"
+        link_data = {
+            "provider_service_instance_id": profile_id,
+            "subscriber_service_instance_id": imsi_id,
+        }
+        link_url = "%s/xosapi/v1/core/serviceinstancelinks" % baseUrl
+        ir = requests.post(link_url, data=json.dumps(link_data), auth=HTTPBasicAuth(username, password))
+        link_res = ir.json()
+        self.assertEqual(link_res['backend_code'], 0)
+
+        # check link in ONOS
+        print "\nRead IMSI Profiles from ONOS"
+        sleep(timeout)
+        imsi_profile_url = "%s/onos/progran/imsi/%s/profile" % (onosUrl, imsi_data['imsi_number'])
+        pr = requests.get(imsi_profile_url, auth=HTTPBasicAuth(username, password))
+        imsi_profile_res = pr.json()['ProfileArray'][0]
+        self.assertEqual(imsi_profile_res['Name'], profile_data['name'])
+
+        # Add EnodeB
+        print "\nAdding Enodeb"
+        enodeb_data = {
+            "description": "testcem",
+            "enbId": "402",
+            "ipAddr": "192.168.32.1"
+        }
+        enodeb_url = "%s/xosapi/v1/progran/enodebs" % baseUrl
+        r = requests.post(enodeb_url, data=json.dumps(enodeb_data), auth=HTTPBasicAuth(username, password))
+        enodeb_res = r.json()
+        enodeb_id = enodeb_res['id']
+        self.assertEqual(enodeb_res['backend_code'], 0)
+
+        # Check EnodeB in ONOS
+        print "\nRead Enodeb from ONOS"
+        sleep(timeout)
+
+        enodeb_onos_url = "%s/onos/progran/enodeb/%s" % (onosUrl, enodeb_data['enbId'])
+        pr = requests.get(enodeb_onos_url, auth=HTTPBasicAuth(username, password))
+        enodeb_onos_res = pr.json()['EnodeBArray'][0]
+        self.assertEqual(enodeb_onos_res['Description'], enodeb_data['description'])
+
+        # Add enodeb to profile
+        print "\n Adding EnodeB to profile"
+
+        profile_data["enodeb_id"] = enodeb_id
+        profile_update_url = "%s/%s" % (profile_url, profile_id)
+        r = requests.put(profile_update_url, data=json.dumps(profile_data), auth=HTTPBasicAuth(username, password))
+        update_res = r.json()
+
+        # Check that enodeb has been added to profile
+        print "\nRead Enodeb to profile from ONOS"
+        sleep(timeout)
+
+        enodeb_onos_url = "%s/onos/progran/enodeb/%s" % (onosUrl, enodeb_data['enbId'])
+        pr = requests.get(enodeb_onos_url, auth=HTTPBasicAuth(username, password))
+        enodeb_onos_res = pr.json()['EnodeBArray'][0]
+        self.assertIn(profile_data['name'], enodeb_onos_res['ProfileArray'])
+
+        # TODO change profile UL/DL rates in ONOS and check in XOS (how??)
+
+
+if __name__ == "__main__":
+    unittest.main()
\ No newline at end of file
diff --git a/xos/models/models.py b/xos/models/models.py
deleted file mode 100644
index babf8b5..0000000
--- a/xos/models/models.py
+++ /dev/null
@@ -1,96 +0,0 @@
-from xos.exceptions import XOSValidationError
-
-from models_decl import ProgranService_decl
-from models_decl import ENodeB_decl
-from models_decl import Handover_decl
-from models_decl import ProgranServiceInstance_decl
-
-
-
-
-
-
-
-class ProgranService(ProgranService_decl):
-    class Meta:
-        proxy = True
-    def save(self, *args, **kwargs):
-
-        existing_services = ProgranService.objects.all()
-
-        if len(existing_services) > 0 and not self.delete:
-            raise XOSValidationError("A ProgranService already exists, you should not have more than one")
-
-        super(ProgranService, self).save(*args, **kwargs)
-
-
-class ENodeB(ENodeB_decl):
-    class Meta:
-        proxy = True
-
-    def save(self, *args, **kwargs):
-
-        # remove all the profiles related to this enodeb (clearing the relation, not the models)
-        if self.deleted:
-            self.profiles.clear()
-
-        # prevent enbId duplicates
-        try:
-            instance_with_same_id = ENodeB.objects.get(enbId=self.enbId)
-
-            if (not self.pk and instance_with_same_id) or (self.pk and self.pk != instance_with_same_id.pk):
-                raise XOSValidationError("A ENodeB with enbId '%s' already exists" % self.enbId)
-        except self.DoesNotExist:
-            pass
-
-        super(ENodeB, self).save(*args, **kwargs)
-
-
-class Handover(Handover_decl):
-    class Meta:
-        proxy = True
-
-
-class ProgranServiceInstance(ProgranServiceInstance_decl):
-    class Meta:
-        proxy = True
-
-    def save(self, *args, **kwargs):
-        # NOTE someone is setting owner_id, so just override it for now
-        try:
-            # NOTE we allow just one ProgranService
-            progran_service = ProgranService.objects.all()[0]
-            self.owner_id = progran_service.id
-        except IndexError:
-            raise XOSValidationError("Service Progran cannot be found, please make sure that the model exists.")
-
-        # prevent name duplicates
-        try:
-            instances_with_same_name = ProgranServiceInstance.objects.get(name=self.name)
-
-            if (not self.pk and instances_with_same_name) or (self.pk and self.pk != instances_with_same_name.pk):
-                raise XOSValidationError("A ProgranServiceInstance with name '%s' already exists" % self.name)
-        except self.DoesNotExist:
-            pass
-
-        # check that the sum of upload and download rate for a single enodeb is not greater than 95
-        limit = 95
-        same_enodeb = ProgranServiceInstance.objects.filter(enodeb_id=self.enodeb_id)
-
-        total_up = self.UlAllocRBRate
-        total_down = self.DlAllocRBRate
-
-        for p in same_enodeb:
-            total_up = total_up + p.UlAllocRBRate
-            total_down = total_down + p.DlAllocRBRate
-
-        if total_up > limit:
-            raise XOSValidationError("UlAllocRBRate for the enodeb associated with this profile is greater than %s" % limit)
-
-        if total_down > limit:
-            raise XOSValidationError("DlAllocRBRate for the enodeb associated with this profile is greater than %s" % limit)
-
-        # TODO when saving set status to "in progress"
-        super(ProgranServiceInstance, self).save(*args, **kwargs)
-
-
diff --git a/xos/progran-onboard.yaml b/xos/progran-onboard.yaml
deleted file mode 100644
index 87424a3..0000000
--- a/xos/progran-onboard.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-# 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.
-
-
-tosca_definitions_version: tosca_simple_yaml_1_0
-
-description: Onboard the Progran Service
-
-imports:
-   - custom_types/xos.yaml
-
-topology_template:
-  node_templates:
-    servicecontroller#progran:
-      type: tosca.nodes.ServiceController
-      properties:
-          base_url: file:///opt/xos_services/progran/xos/
-          # The following will concatenate with base_url automatically, if
-          # base_url is non-null.
-          xproto: models/
diff --git a/xos/synchronizer/models/models.py b/xos/synchronizer/models/models.py
new file mode 100644
index 0000000..3544d25
--- /dev/null
+++ b/xos/synchronizer/models/models.py
@@ -0,0 +1,135 @@
+# 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.
+
+from xos.exceptions import XOSValidationError
+
+from models_decl import ProgranService_decl
+from models_decl import ENodeB_decl
+from models_decl import Handover_decl
+from models_decl import ProgranServiceInstance_decl
+
+class ProgranService(ProgranService_decl):
+    class Meta:
+        proxy = True
+    def save(self, *args, **kwargs):
+
+        existing_services = ProgranService.objects.all()
+
+        if len(existing_services) > 0 and not self.delete:
+            raise XOSValidationError("A ProgranService already exists, you should not have more than one")
+
+        super(ProgranService, self).save(*args, **kwargs)
+
+
+class ENodeB(ENodeB_decl):
+    class Meta:
+        proxy = True
+
+    def save(self, *args, **kwargs):
+
+        # remove all the profiles related to this enodeb (clearing the relation, not the models)
+        if self.deleted:
+            self.profiles.clear()
+
+        # prevent enbId duplicates
+        try:
+            instance_with_same_id = ENodeB.objects.get(enbId=self.enbId)
+
+            if (not self.pk and instance_with_same_id) or (self.pk and self.pk != instance_with_same_id.pk):
+                raise XOSValidationError("A ENodeB with enbId '%s' already exists" % self.enbId)
+        except self.DoesNotExist:
+            pass
+
+        if self.is_new and not self.created_by:
+            # NOTE if created_by is null it has been created by XOS
+            self.created_by = "XOS"
+
+        super(ENodeB, self).save(*args, **kwargs)
+
+
+class Handover(Handover_decl):
+    class Meta:
+        proxy = True
+
+    def save(self, *args, **kwargs):
+        if self.is_new and not self.created_by:
+            # NOTE if created_by is null it has been created by XOS
+            self.created_by = "XOS"
+        super(Handover, self).save(*args, **kwargs)
+
+
+
+class ProgranServiceInstance(ProgranServiceInstance_decl):
+    class Meta:
+        proxy = True
+
+    def save(self, *args, **kwargs):
+
+        # TODO we should not allow name changes as name is the mapping with the backend
+
+        # name is mandatory
+        if not self.name:
+            raise XOSValidationError("name is mandatory for ProgranServiceInstances")
+
+        # NOTE this check is disabled as when Progran create a profile it fails
+        # if self.DlUeAllocRbRate > self.DlAllocRBRate:
+        #     raise XOSValidationError("DlUeAllocRbRate (%s) cannot be bigger than DlAllocRBRate (%s)" % (self.DlUeAllocRbRate, self.DlAllocRBRate))
+
+        # prevent name duplicates
+        try:
+            instances_with_same_name = ProgranServiceInstance.objects.get(name=self.name)
+
+            if (not self.pk and instances_with_same_name) or (self.pk and self.pk != instances_with_same_name.pk):
+                raise XOSValidationError("A ProgranServiceInstance with name '%s' already exists" % self.name)
+        except self.DoesNotExist:
+            pass
+
+        if self.is_new and not self.created_by:
+            # NOTE if created_by is null it has been created by XOS
+            self.created_by = "XOS"
+
+
+        # check that the sum of upload and download rate for a single enodeb is not greater than 95
+        if not self.deleted:
+            limit = 95
+            same_enodeb = ProgranServiceInstance.objects.filter(enodeb_id=self.enodeb_id)
+
+            total_up = self.UlAllocRBRate
+            total_down = self.DlAllocRBRate
+
+            for p in same_enodeb:
+                if p.pk != self.pk:
+                    total_up = total_up + p.UlAllocRBRate
+                    total_down = total_down + p.DlAllocRBRate
+
+            if total_up > limit:
+                raise XOSValidationError("UlAllocRBRate for the enodeb associated with this profile is greater than %s" % limit)
+
+            if total_down > limit:
+                raise XOSValidationError("DlAllocRBRate for the enodeb associated with this profile is greater than %s" % limit)
+
+        caller_kind = "xos"
+
+        if "caller_kind" in kwargs:
+            caller_kind = kwargs.pop("caller_kind")
+
+        print "Profile %s has been saved by %s" % (self.name, caller_kind)
+
+        if caller_kind == "xos":
+            print "Setting no_sync to false for profile %s" % self.name
+            self.no_sync = False
+
+        super(ProgranServiceInstance, self).save(*args, **kwargs)
+
+
diff --git a/xos/models/progran.xproto b/xos/synchronizer/models/progran.xproto
similarity index 61%
rename from xos/models/progran.xproto
rename to xos/synchronizer/models/progran.xproto
index b7e5c92..47e21db 100644
--- a/xos/models/progran.xproto
+++ b/xos/synchronizer/models/progran.xproto
@@ -15,43 +15,49 @@
     required string description = 1 [db_index = False, max_length = 256, null = False, blank = False];
     required string enbId = 2 [help_text = "ID of this enodeb", db_index = False, max_length = 256, null = False, blank = False];
     required string ipAddr = 3 [help_text = "IP address of this enodeb", db_index = False, max_length = 256, null = False, blank = False];
+    optional string created_by = 4 [null = True, blank = True, gui_hidden = True];
+    optional bool previously_sync = 5 [null = False, blank = True, default=False, gui_hidden = True];
 }
 
 message Handover (XOSBase){
     option verbose_name = "Handover";
-    required int32 A3offset = 1 [default = 2, db_index = False, null = False, blank = False];
-    required int32 HysteresisA3 = 2 [default = 1, db_index = False, null = False, blank = False];
+    required int32 A3offset = 1 [default = 1, db_index = False, null = False, blank = False];
+    required int32 HysteresisA3 = 2 [default = 0, db_index = False, null = False, blank = False];
     required int32 A3TriggerQuantity = 3 [default = 0, db_index = False, null = False, blank = False];
     required int32 A5TriggerType = 4 [default = 0, db_index = False, null = False, blank = False];
-    required int32 A5Thresh1Rsrp = 5 [default = -97, db_index = False, null = False, blank = False];
-    required int32 A5Thresh1Rsrq = 6 [default = -10, db_index = False, null = False, blank = False];
-    required int32 A5Thresh2Rsrp = 7 [default = -95, db_index = False, null = False, blank = False];
-    required int32 A5Thresh2Rsrq = 8 [default = -8, db_index = False, null = False, blank = False];
+    required int32 A5Thresh1Rsrp = 5 [default = 74, db_index = False, null = False, blank = False];
+    required int32 A5Thresh1Rsrq = 6 [default = 10, db_index = False, null = False, blank = False];
+    required int32 A5Thresh2Rsrp = 7 [default = 78, db_index = False, null = False, blank = False];
+    required int32 A5Thresh2Rsrq = 8 [default = 10, db_index = False, null = False, blank = False];
     required int32 HysteresisA5 = 9 [default = 1, db_index = False, null = False, blank = False];
     required int32 A5TriggerQuantity = 10 [default = 0, db_index = False, null = False, blank = False];
+    optional string created_by = 11 [null = True, blank = True, gui_hidden = True];
 }
 
 message ProgranServiceInstance (ServiceInstance){
     option verbose_name = "Progran Service Instance";
     option description = "Represent a Profile in the Progran ONOS Application";
+    option owner_class_name="ProgranService";
 
-    required string DlSchedType = 1 [default = "vm", choices = "(('RR', 'Round Robin'),)", max_length = 30, blank = False, null = False, db_index = False];
-    required int32 DlAllocRBRate = 2 [db_index = False, null = False, blank = False];
-    required string UlSchedType = 3 [default = "vm", choices = "(('RR', 'Round Robin'),)", max_length = 30, blank = False, null = False, db_index = False];
-    required int32 UlAllocRBRate = 4 [db_index = False, null = False, blank = False];
+    required string DlSchedType = 1 [default = "RR", choices = "(('RR', 'Round Robin'), ('PF', 'Proportional Fairness'), ('MAXCI', 'Maximum C/I'))", max_length = 30, blank = False, null = False, db_index = False];
+    required int32 DlAllocRBRate = 2 [default = "0", db_index = False, null = False, blank = False];
+    required string UlSchedType = 3 [default = "RR", choices = "(('RR', 'Round Robin'), ('PF', 'Proportional Fairness'), ('MAXCI', 'Maximum C/I'))", max_length = 30, blank = False, null = False, db_index = False];
+    required int32 UlAllocRBRate = 4 [default = "0", db_index = False, null = False, blank = False];
     required string start = 5 [content_type = "date", null = True, blank = True];
     required string end = 6 [content_type = "date", null = True, blank = True];
     required int32 AdmControl = 7 [default = "0", choices = "(('0', 'ALL'), ('1', 'Voice Only'), ('2', 'Data Only'))", blank = False, null = False, db_index = False];
     required int32 CellIndividualOffset = 8 [db_index = False, null = False, blank = False];
-    required string mmeip = 9 [db_index = False, max_length = 256, null = False, blank = False];
-    required string mmeport = 10 [db_index = False, max_length = 256, null = False, blank = False];
+    required string mmeip = 9 [db_index = False, default = "0.0.0.0", max_length = 256, null = False, blank = False];
+    required string mmeport = 10 [db_index = False, default = "8080", max_length = 256, null = False, blank = False];
     required int32 DlWifiRate = 11 [default = 100, db_index = False, null = False, blank = False];
-    required int32 DlUeAllocRbRate = 12 [default = 100, db_index = False, null = False, blank = False];
+    required int32 DlUeAllocRbRate = 12 [default = "0", help_text = "DL Per UE allocation", db_index = False, null = True, blank = True];
     required string SubsProfile = 13 [ db_index = False, null = True, blank = True];
     optional bool Status = 14 [ db_index = False, null = False, blank = True, default = False];
     optional manytoone enodeb->ENodeB:profiles = 15 [null = True, blank = True];
     required manytoone handover->Handover:profiles = 16 [null = False, blank = False];
     optional int32 active_enodeb_id = 17 [null = True, blank = True, gui_hidden = True];
+    optional string created_by = 17 [null = True, blank = True, gui_hidden = True];
+    optional bool previously_sync = 18 [null = False, blank = True, default=False, gui_hidden = True];
 }
 
 
diff --git a/xos/synchronizer/progran_config.yml b/xos/synchronizer/progran_config.yml
index a624152..d3b209c 100644
--- a/xos/synchronizer/progran_config.yml
+++ b/xos/synchronizer/progran_config.yml
@@ -27,10 +27,8 @@
 dependency_graph: "/opt/xos/synchronizers/progran/model_deps"
 steps_dir: "/opt/xos/synchronizers/progran/steps"
 sys_dir: "/opt/xos/synchronizers/progran/sys"
-# model_policies_dir: "/opt/xos/synchronizers/progran/model_policies"
-
-
-keep_temp_files: True
+models_dir: "/opt/xos/synchronizers/progran/models"
+#model_policies_dir: "/opt/xos/synchronizers/progran/model_policies"
 
 logging:
   version: 1
@@ -43,7 +41,7 @@
       maxBytes: 10485760
       backupCount: 5
   loggers:
-    '':
+    'multistructlog':
       handlers:
           - console
           - file
diff --git a/xos/synchronizer/steps/helpers.py b/xos/synchronizer/steps/helpers.py
index 8f07a66..333566a 100644
--- a/xos/synchronizer/steps/helpers.py
+++ b/xos/synchronizer/steps/helpers.py
@@ -47,7 +47,8 @@
     @staticmethod
     def get_progran_rest_errors(res):
         res = res.json()
-        if res['Result'] == -2:
+        if res['Result'] == -2 or res['Result'] == -1:
+            log.error('Error from ONOS Progran', error=res)
             raise Exception(res['ErrCode'])
 
     @staticmethod
@@ -62,8 +63,8 @@
         for k, v in dict.iteritems():
             if hasattr(model, k):
                 setattr(model, k, v)
-            else:
-                log.warn("%s does not have a '%s' property, not updating it" % (model.model_name, k))
+            # else:
+            #     log.debug("%s does not have a '%s' property, not updating it" % (model.model_name, k))
         return model
 
     @staticmethod
diff --git a/xos/synchronizer/steps/sync_enodeb.py b/xos/synchronizer/steps/sync_enodeb.py
index db5bca9..b0e4172 100644
--- a/xos/synchronizer/steps/sync_enodeb.py
+++ b/xos/synchronizer/steps/sync_enodeb.py
@@ -16,14 +16,14 @@
 
 import os
 import sys
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
-from synchronizers.new_base.ansible_helper import run_template
+from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
 from synchronizers.new_base.modelaccessor import ENodeB
 
 from xosconfig import Config
 from multistructlog import create_logger
 import json
-
+import requests
+from requests.auth import HTTPBasicAuth
 
 log = create_logger(Config().get('logging'))
 
@@ -34,16 +34,11 @@
 
 from helpers import ProgranHelpers
 
-class SyncProgranEnodeB(SyncInstanceUsingAnsible):
+class SyncProgranEnodeB(SyncStep):
     provides = [ENodeB]
 
     observes = ENodeB
 
-    def skip_ansible_fields(self, o):
-        # FIXME This model does not have an instance, this is a workaroung to make it work,
-        # but it need to be cleaned up creating a general SyncUsingAnsible base class
-        return True
-
     def get_progran_enodeb_field(self, o):
 
         enodeb = {
@@ -51,37 +46,38 @@
 	        "Description": o.description,
 	        "IpAddr": o.ipAddr
         }
-        enodeb = json.dumps(enodeb)
         return enodeb
 
-    def get_extra_attributes(self, o):
+    def sync_record(self, o):
+        log.info("sync'ing enodeb", object=str(o), **o.tologdict())
+
         onos = ProgranHelpers.get_progran_onos_info()
-        fields = {
-            'onos_url': onos['url'],
-            'onos_username': onos['username'],
-            'onos_password': onos['password'],
-            'onos_port': onos['port'],
-            'endpoint': 'enodeb',
-            'body': self.get_progran_enodeb_field(o),
-            'method': 'POST'
-        }
 
-        return fields
+        enodeb_url = "http://%s:%s/onos/progran/enodeb/" % (onos['url'], onos['port'])
+        data = self.get_progran_enodeb_field(o)
+        log.debug("Sync'ing enodeb with data", request_data=data)
 
-    # FIXME we need to override this as the default expect to ssh into a VM
-    def run_playbook(self, o, fields):
-        run_template("progran_curl.yaml", fields, object=o)
+        if o.previously_sync == False:
+            log.debug("Sending POST", url=enodeb_url, data=json.dumps(data))
+            r = requests.post(enodeb_url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
+        else:
+            data = {
+                "EnodeB": data
+            }
+            log.debug("Sending PUT", url=enodeb_url, data=json.dumps(data))
+            r = requests.put(enodeb_url, data=json.dumps(data),
+                              auth=HTTPBasicAuth(onos['username'], onos['password']))
+
+        ProgranHelpers.get_progran_rest_errors(r)
+        log.info("Enodeb synchronized", response=r.json())
+
+        o.previously_sync = True
+        o.save()
 
     def delete_record(self, o):
-        log.info("deleting object", object=str(o), **o.tologdict())
+        log.info("deleting enodeb", object=str(o), **o.tologdict())
         onos = ProgranHelpers.get_progran_onos_info()
-        fields = {
-            'onos_url': onos['url'],
-            'onos_username': onos['username'],
-            'onos_password': onos['password'],
-            'onos_port': onos['port'],
-            'endpoint': 'enodeb/%s' % o.enbId,
-            'profile': '',
-            'method': 'DELETE'
-        }
-        res = self.run_playbook(o, fields)
\ No newline at end of file
+        enode_url = "http://%s:%s/onos/progran/enodeb/%s" % (onos['url'], onos['port'], o.enbId)
+        r = requests.delete(enode_url, auth=HTTPBasicAuth(onos['username'], onos['password']))
+        ProgranHelpers.get_progran_rest_errors(r)
+        log.info("enodeb deleted", response=r.json())
\ No newline at end of file
diff --git a/xos/synchronizer/steps/sync_imsi.py b/xos/synchronizer/steps/sync_imsi.py
index 1cc6338..12918f1 100644
--- a/xos/synchronizer/steps/sync_imsi.py
+++ b/xos/synchronizer/steps/sync_imsi.py
@@ -16,14 +16,14 @@
 
 import os
 import sys
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
-from synchronizers.new_base.ansible_helper import run_template
+from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
 from synchronizers.new_base.modelaccessor import MCordSubscriberInstance
 
 from xosconfig import Config
 from multistructlog import create_logger
 import json
 import requests
+from requests.auth import HTTPBasicAuth
 
 
 log = create_logger(Config().get('logging'))
@@ -33,72 +33,31 @@
 sys.path.insert(0, os.path.dirname(__file__))
 from helpers import ProgranHelpers
 
-class SyncProgranIMSI(SyncInstanceUsingAnsible):
+class SyncProgranIMSI(SyncStep):
     provides = [MCordSubscriberInstance]
 
     observes = MCordSubscriberInstance
 
-    def skip_ansible_fields(self, o):
-        # FIXME This model does not have an instance, this is a workaroung to make it work,
-        # but it need to be cleaned up creating a general SyncUsingAnsible base class
-        return True
-
     def get_progran_imsi_field(self, o):
 
         imsi = {
             "IMSI": o.imsi_number,
         }
-        imsi = json.dumps(imsi)
         return imsi
 
-    def get_fields(self, o):
-        onos = ProgranHelpers.get_progran_onos_info()
-        fields = {
-            'onos_url': onos['url'],
-            'onos_username': onos['username'],
-            'onos_password': onos['password'],
-            'onos_port': onos['port'],
-        }
-
-        return fields
-
     def sync_record(self, o):
-        # NOTE overriding the default method as we need to read from progran
-        base_fields = self.get_fields(o)
+        log.info("sync'ing imsi", object=str(o), **o.tologdict())
+        onos = ProgranHelpers.get_progran_onos_info()
+        imsi_url = "http://%s:%s/onos/progran/imsi/" % (onos['url'], onos['port'])
+        data = self.get_progran_imsi_field(o)
+        r = requests.post(imsi_url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
 
-        create_fields = {
-            'endpoint': 'imsi',
-            'body': self.get_progran_imsi_field(o),
-            'method': 'POST'
-        }
-
-        create_fields["ansible_tag"] = getattr(o, "ansible_tag", o.__class__.__name__ + "_" + str(o.id))
-        create_fields.update(base_fields)
-
-        self.run_playbook(o, create_fields)
-
-        # fetch the IMSI we just created
-        # NOTE we won't need this method once we'll have polling in place
-        # imsi_url = "http://%s:%s/onos/progran/imsi/%s" % (base_fields['onos_url'], base_fields['onos_port'], o.imsi_number)
-        # r = requests.get(imsi_url)
-        # o.ue_status = r.json()['ImsiArray'][0]['UeStatus']
-
-        o.save()
-
-    # FIXME we need to override this as the default expect to ssh into a VM
-    def run_playbook(self, o, fields):
-        return run_template("progran_curl.yaml", fields, object=o)
+        ProgranHelpers.get_progran_rest_errors(r)
+        log.info("Profile synchronized", response=r.json())
 
     def delete_record(self, o):
-        log.info("deleting object", object=str(o), **o.tologdict())
+        log.info("deleting imsi", object=str(o), **o.tologdict())
         onos = ProgranHelpers.get_progran_onos_info()
-        fields = {
-            'onos_url': onos['url'],
-            'onos_username': onos['username'],
-            'onos_password': onos['password'],
-            'onos_port': onos['port'],
-            'endpoint': 'imsi/%s' % o.imsi_number,
-            'body': '',
-            'method': 'DELETE'
-        }
-        res = self.run_playbook(o, fields)
\ No newline at end of file
+        profile_url = "http://%s:%s/onos/progran/imsi/%s" % (onos['url'], onos['port'], o.imsi_number)
+        r = requests.delete(profile_url, auth=HTTPBasicAuth(onos['username'], onos['password']))
+        log.info("IMSI synchronized", response=r.json())
\ No newline at end of file
diff --git a/xos/synchronizer/steps/sync_imsi_back.py b/xos/synchronizer/steps/sync_imsi_back.py
index 9398c76..b5a9a3e 100644
--- a/xos/synchronizer/steps/sync_imsi_back.py
+++ b/xos/synchronizer/steps/sync_imsi_back.py
@@ -53,14 +53,13 @@
             # NOTE we won't it to run only after the delete has completed
             return
 
-        log.info("Reading IMSI from progran")
+        log.debug("Reading IMSI from progran")
         onos = ProgranHelpers.get_progran_onos_info()
         imsi_url = "http://%s:%s/onos/progran/imsi/" % (onos['url'], onos['port'])
         r = requests.get(imsi_url, auth=HTTPBasicAuth(onos['username'], onos['password']))
         res = r.json()['ImsiArray']
 
-        print res
-
+        log.debug("Received IMSIs: ", imsis=res)
 
         field_mapping = {
             'IMSI': 'imsi_number',
@@ -70,18 +69,18 @@
         updated_imsi = []
 
         for i in res:
-            # imsi for profiles
             try:
                 si = MCordSubscriberInstance.objects.get(imsi_number=i['IMSI'])
-                log.info("IMSI %s already exists, updating it" % i['IMSI'])
+                log.debug("IMSI %s already exists, updating it" % i['IMSI'])
             except IndexError:
                 si = MCordSubscriberInstance()
 
                 si.no_sync = True
                 si.backend_code = 1
                 si.backend_status = "OK"
+                si.created_by = "Progran"
 
-                log.info("IMSI %s is new, creating it" % i['IMSI'])
+                log.debug("IMSI %s is new, creating it" % i['IMSI'])
 
             si = ProgranHelpers.update_fields(si, i, field_mapping,)
 
@@ -93,7 +92,10 @@
         deleted_imsi = ProgranHelpers.list_diff(existing_imsi, updated_imsi)
 
         if len(deleted_imsi) > 0:
-            log.info("Profiles %s have been removed in progran, removing them from XOS" % str(deleted_imsi))
+            log.debug("Profiles %s have been removed in progran, removing them from XOS" % str(deleted_imsi))
             for p in deleted_imsi:
                 si = MCordSubscriberInstance.objects.get(imsi_number=p)
+                # if si.created_by == 'XOS' and si.previously_sync == False:
+                    # don't delete if the imsi has been created by XOS and it hasn't been sync'ed yet
+                    # continue
                 si.delete()
diff --git a/xos/synchronizer/steps/sync_imsi_link.py b/xos/synchronizer/steps/sync_imsi_link.py
index 3b1ba98..863af21 100644
--- a/xos/synchronizer/steps/sync_imsi_link.py
+++ b/xos/synchronizer/steps/sync_imsi_link.py
@@ -16,8 +16,7 @@
 
 import os
 import sys
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
-from synchronizers.new_base.ansible_helper import run_template
+from synchronizers.new_base.syncstep import SyncStep
 from synchronizers.new_base.modelaccessor import MCordSubscriberInstance, ServiceInstanceLink, ProgranServiceInstance
 
 from xosconfig import Config
@@ -72,7 +71,7 @@
             url = "http://%s:%s/onos/progran/profile/%s/imsi" % (onos['url'], onos['port'], profile_name)
 
             r = requests.post(url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
-            print r.json()
+            ProgranHelpers.get_progran_rest_errors(r)
 
     def delete_record(self, o):
 
@@ -87,4 +86,4 @@
             url = "http://%s:%s/onos/progran/profile/%s/%s" % (onos['url'], onos['port'], profile_name, imsi_number)
 
             r = requests.delete(url, auth=HTTPBasicAuth(onos['username'], onos['password']))
-            print r.json()
\ No newline at end of file
+            ProgranHelpers.get_progran_rest_errors(r)
\ No newline at end of file
diff --git a/xos/synchronizer/steps/sync_progranserviceinstance.py b/xos/synchronizer/steps/sync_progranserviceinstance.py
index 11d679d..d4e3d70 100644
--- a/xos/synchronizer/steps/sync_progranserviceinstance.py
+++ b/xos/synchronizer/steps/sync_progranserviceinstance.py
@@ -16,7 +16,7 @@
 
 import os
 import sys
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
+from synchronizers.new_base.syncstep import SyncStep
 from synchronizers.new_base.modelaccessor import ProgranServiceInstance, ENodeB, Handover
 
 from xosconfig import Config
@@ -40,9 +40,6 @@
 
     observes = ProgranServiceInstance
 
-    # Poll every 5 loops of self.call
-    poll = 0
-
     def sync_record(self, o):
         onos = ProgranHelpers.get_progran_onos_info()
 
@@ -50,13 +47,19 @@
 
         profile_url = "http://%s:%s/onos/progran/profile/" % (onos['url'], onos['port'])
         data = self.get_progran_profile_field(o)
+        log.debug("Sync'ing profile with data", request_data=data)
 
-        r = requests.post(profile_url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
+        if o.previously_sync == False:
+            log.debug("Sending POST", url=profile_url, data=json.dumps(data))
+            r = requests.post(profile_url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
+        else:
+            log.debug("Sending PUT", url=profile_url, data=json.dumps(data))
+            r = requests.put(profile_url, data=json.dumps(data),
+                              auth=HTTPBasicAuth(onos['username'], onos['password']))
 
         ProgranHelpers.get_progran_rest_errors(r)
         log.info("Profile synchronized", response=r.json())
 
-        log.info("sync'ing enodeb", object=str(o), **o.tologdict())
         if o.enodeb_id:
             log.info("adding profile %s to enodeb %s" % (o.id, o.enodeb.enbId), object=str(o), **o.tologdict())
             enodeb_url = "http://%s:%s/onos/progran/enodeb/%s/profile" % (onos['url'], onos['port'], o.enodeb.enbId)
@@ -65,6 +68,7 @@
                     o.name
                 ]
             }
+            log.debug("Adding enodeb to profile with data", request_data=data)
             r = requests.post(enodeb_url, data=json.dumps(data), auth=HTTPBasicAuth(onos['username'], onos['password']))
             ProgranHelpers.get_progran_rest_errors(r)
             o.active_enodeb_id = o.enodeb_id # storing the value to know when it will be deleted
@@ -78,6 +82,8 @@
             o.active_enodeb_id = 0 # removing the value because it has been deleted
             log.info("EnodeB synchronized", response=r.json())
 
+        o.previously_sync = True
+        o.no_sync = True
         o.save()
 
     def get_handover_for_profile(self, o):
@@ -107,8 +113,10 @@
             "Name": o.name,
             "UlAllocRBRate": o.UlAllocRBRate,
             "Handover": self.get_handover_for_profile(o),
-            'mmeip': o.mmeip,
-            'mmeport': o.mmeport,
+            "MMECfg": {
+                "Port": o.mmeport,
+                "IPAddr": o.mmeip,
+            },
             'DlWifiRate': o.DlWifiRate,
             'DlUeAllocRbRate': o.DlUeAllocRbRate,
         }
@@ -124,103 +132,4 @@
         log.info("Profile synchronized", response=r.json())
 
     def fetch_pending(self, deleted):
-        # self.read_profiles_from_progran()
         return super(SyncProgranServiceInstance, self).fetch_pending(deleted)
-
-    @staticmethod
-    def date_to_time(d):
-        if len(d) == 0:
-            return 0
-        return time.mktime(datetime.datetime.strptime(d, "%d.%m.%Y %H:%S").timetuple())
-
-    @staticmethod
-    def update_fields(model, dict, mapping={}, transformations={}):
-        dict = SyncProgranServiceInstance.convert_keys(dict, mapping, transformations)
-        for k, v in dict.iteritems():
-            if hasattr(model, k):
-                setattr(model, k, v)
-            else:
-                log.warn("%s does not have a '%s' property, not updating it" % (model.model_name, k))
-        return model
-
-    @staticmethod
-    def convert_keys(dict, mapping={}, transformations={}):
-        for k, v in dict.iteritems():
-            if k in mapping:
-                # apply custom transformations to the data
-                if k in transformations:
-                    dict[k] = transformations[k](v)
-
-                # NOTE we may have different names that the field in the dict
-                dict[mapping[k]] = dict[k]
-                del dict[k]
-        return dict
-
-
-    def my_call(self, failed=[], deletion=False):
-        """
-        Read profile from progran and save them in xos
-        """
-        if self.poll < 5:
-            self.poll = self.poll + 1
-        else:
-            self.poll = 0
-            onos = ProgranHelpers.get_progran_onos_info()
-            profile_url = "http://%s:%s/onos/progran/profile/" % (onos['url'], onos['port'])
-            r = requests.get(profile_url, auth=HTTPBasicAuth(onos['username'], onos['password']))
-            res = r.json()['ProfileArray']
-
-            # remove default profiles
-            res = [p for p in res if "Default" not in p['Name']]
-
-            field_mapping = {
-                'Name': 'name',
-                'Start': 'start',
-                'End': 'end'
-            }
-
-            field_transformations = {
-                'Start': SyncProgranServiceInstance.date_to_time,
-                'End': SyncProgranServiceInstance.date_to_time
-            }
-
-            handover_mapping = {
-                'A5Hysteresis': 'HysteresisA5',
-                'A3Hysteresis': 'HysteresisA3'
-            }
-
-            for p in res:
-
-                # checking for handovers
-                handover_dict = p['Handover']
-                handover_dict = SyncProgranServiceInstance.convert_keys(handover_dict, handover_mapping)
-                del p['Handover']
-
-                try:
-                    handover = Handover.objects.get(**handover_dict)
-                    log.info("handover already exists, updating it", handover=handover_dict)
-                except IndexError:
-                    handover = Handover()
-                    handover = SyncProgranServiceInstance.update_fields(handover, handover_dict)
-                    log.info("handover is new, creating it", handover=handover_dict)
-
-                handover.save()
-
-                # checking for profiles
-                try:
-                    si = ProgranServiceInstance.objects.get(name=p['Name'])
-                    log.info("Profile %s already exists, updating it" % p['Name'])
-                except IndexError:
-                    si = ProgranServiceInstance()
-                    si.name = p['Name']
-                    log.info("Profile %s is new, creating it" % p['Name'])
-
-                si = SyncProgranServiceInstance.update_fields(si, p, field_mapping, field_transformations)
-                si.handover = handover
-
-
-
-                # TODO keep track of the deleted profiles
-                # existing profiles - updated profiles = deleted profiles
-
-                si.save()
diff --git a/xos/synchronizer/steps/sync_progranserviceinstance_back.py b/xos/synchronizer/steps/sync_progranserviceinstance_back.py
index a7c2b73..acb8f6e 100644
--- a/xos/synchronizer/steps/sync_progranserviceinstance_back.py
+++ b/xos/synchronizer/steps/sync_progranserviceinstance_back.py
@@ -20,12 +20,11 @@
 import datetime
 import time
 
-from synchronizers.new_base.SyncInstanceUsingAnsible import SyncStep
-from synchronizers.new_base.modelaccessor import ProgranServiceInstance, ENodeB, Handover
+from synchronizers.new_base.syncstep import SyncStep
+from synchronizers.new_base.modelaccessor import ProgranServiceInstance, ENodeB, Handover, ServiceInstanceLink, MCordSubscriberInstance
 
 from xosconfig import Config
 from multistructlog import create_logger
-import json
 import requests
 from requests.auth import HTTPBasicAuth
 
@@ -53,14 +52,17 @@
             # NOTE we won't it to run only after the delete has completed
             return
 
-        log.info("Reading profiles from progran")
+        log.debug("Reading profiles from progran")
         onos = ProgranHelpers.get_progran_onos_info()
         profile_url = "http://%s:%s/onos/progran/profile/" % (onos['url'], onos['port'])
         r = requests.get(profile_url, auth=HTTPBasicAuth(onos['username'], onos['password']))
         res = r.json()['ProfileArray']
 
+
         # remove default profiles
         res = [p for p in res if "Default" not in p['Name']]
+        pnames = [p['Name'] for p in res]
+        log.debug("Received Profiles: ", profiles=pnames)
 
         field_mapping = {
             'Name': 'name',
@@ -82,35 +84,91 @@
 
         for p in res:
 
+
+            # checking for profiles
+            try:
+                si = ProgranServiceInstance.objects.get(name=p['Name'])
+                log.debug("Profile %s already exists, updating it" % p['Name'])
+
+            except IndexError:
+                si = ProgranServiceInstance()
+
+                si.created_by = "Progran"
+
+                log.debug("Profile %s is new, creating it" % p['Name'])
+
+            if not si.is_new:
+                # update IMSI association
+                xos_imsis_for_profile = [i.subscriber_service_instance.leaf_model for i in si.provided_links.all()]
+                progran_imsis_for_profile = p['IMSIRuleArray']
+
+                log.debug("List of imsis for profile %s in XOS" % p["Name"], imsis=xos_imsis_for_profile)
+                log.debug("List of imsis for profile %s in ONOS" % p["Name"], imsis=progran_imsis_for_profile)
+
+                for i in xos_imsis_for_profile:
+                    if not i.imsi_number in progran_imsis_for_profile:
+                        log.debug("Removing Imsi %s from profile %s" % (i.imsi_number, p['Name']))
+
+                        imsi_link = ServiceInstanceLink.objects.get(subscriber_service_instance_id=i.id)
+
+                        # NOTE: this model has already been removed from the backend, no need to synchronize
+                        imsi_link.backend_need_delete = False
+                        imsi_link.no_sync = True
+                        imsi_link.save() # we need to save it to avoid a synchronization loop
+
+                        imsi_link.delete()
+                    else:
+                        # remove from imsi list coming from progran everything we already know about
+                        progran_imsis_for_profile.remove(i.imsi_number)
+
+                for i in progran_imsis_for_profile:
+                    log.debug("Adding Imsi %s to profile %s" % (i, p['Name']))
+                    imsi = MCordSubscriberInstance.objects.get(imsi_number=i)
+                    imsi_to_profile = ServiceInstanceLink(provider_service_instance=si,
+                                                          subscriber_service_instance=imsi)
+                    imsi_to_profile.save()
+
+            # if the model has not been synchronized yet, skip it
+            if not si.is_new and si.no_sync is False:
+                log.debug("Skipping profile %s as not synchronized" % p['Name'])
+                # NOTE add it to the removed profiles to avoid deletion (this is ugly, I know)
+                updated_profiles.append(si.name)
+                continue
+
+            si = ProgranHelpers.update_fields(si, p, field_mapping, field_transformations)
+
             # checking for handovers
             handover_dict = p['Handover']
             handover_dict = ProgranHelpers.convert_keys(handover_dict, handover_mapping)
             del p['Handover']
 
-            try:
-                handover = Handover.objects.get(**handover_dict)
-                log.info("handover already exists, updating it", handover=handover_dict)
-            except IndexError:
+            if si.handover_id:
+                handover = si.handover
+                log.debug("handover already exists, updating it", handover=handover_dict)
+            else:
                 handover = Handover()
                 handover = ProgranHelpers.update_fields(handover, handover_dict)
-                log.info("handover is new, creating it", handover=handover_dict)
+                log.debug("handover is new, creating it", handover=handover_dict)
+                handover.created_by = "Progran"
 
+            handover = ProgranHelpers.update_fields(handover, handover_dict)
             handover.save()
 
-            # checking for profiles
-            try:
-                si = ProgranServiceInstance.objects.get(name=p['Name'])
-                log.info("Profile %s already exists, updating it" % p['Name'])
-            except IndexError:
-                si = ProgranServiceInstance()
-
-                si.no_sync = True
-
-                log.info("Profile %s is new, creating it" % p['Name'])
-
-            si = ProgranHelpers.update_fields(si, p, field_mapping, field_transformations)
+            # Assigning handover to profile
             si.handover = handover
 
+            si.backend_status = "OK"
+            si.backend_code = 1
+
+            si.no_sync = True
+            si.previously_sync = True
+
+            if p["MMECfg"]:
+                si.mmeip = str(p["MMECfg"]["IPAddr"])
+                si.mmeport = str(p["MMECfg"]["Port"])
+
+            si.enacted = time.mktime(datetime.datetime.now().timetuple())
+
             si.save()
 
             updated_profiles.append(si.name)
@@ -119,7 +177,11 @@
         deleted_profiles = ProgranHelpers.list_diff(existing_profiles, updated_profiles)
 
         if len(deleted_profiles) > 0:
-            log.info("Profiles %s have been removed in progran, removing them from XOS" % str(deleted_profiles))
             for p in deleted_profiles:
                 si = ProgranServiceInstance.objects.get(name=p)
+                if si.created_by == 'XOS' and si.previously_sync == False:
+                    # don't delete if the profile has been created by XOS and it hasn't been sync'ed yet
+                    continue
+                # TODO delete also the associated Handover
+                log.debug("Profiles %s have been removed in progran, removing it from XOS" % str(p))
                 si.delete()
diff --git a/xos/synchronizer/xos-synchronizer b/xos/synchronizer/xos-synchronizer
deleted file mode 100644
index e69de29..0000000
--- a/xos/synchronizer/xos-synchronizer
+++ /dev/null