[SEBA-742] Tech-profile-id as a mandatory field in RCORDSubscriber

Change-Id: I3f7f1e816d7cac457e1e286d809fd2728fb6f0ee
diff --git a/Makefile b/Makefile
index 82c8439..892a527 100644
--- a/Makefile
+++ b/Makefile
@@ -54,13 +54,13 @@
 
 test: test-unit test-migration test-xproto
 
-test-unit:
+test-unit: venv-service
 	tox
 
 venv-service:
 	virtualenv $@;\
     source ./$@/bin/activate ; set -u ;\
-    pip install -r requirements.txt xosmigrate~=3.2.6
+    pip install -r requirements.txt xosmigrate~=3.2.6 django
 
 create-migration: venv-service
 	source ./venv-service/bin/activate; set -u;\
diff --git a/tox.ini b/tox.ini
index 9064a11..73c1e23 100644
--- a/tox.ini
+++ b/tox.ini
@@ -23,6 +23,7 @@
   -r requirements.txt
   requests_mock
   nose2
+  django
 ;  flake8
 
 changedir = xos
diff --git a/xos/synchronizer/migrations/0011_mandatory_tech_profile_id.py b/xos/synchronizer/migrations/0011_mandatory_tech_profile_id.py
new file mode 100644
index 0000000..a1a2fbd
--- /dev/null
+++ b/xos/synchronizer/migrations/0011_mandatory_tech_profile_id.py
@@ -0,0 +1,45 @@
+# 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.
+
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.20 on 2019-06-19 01:31
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    def forwards(apps, schema_editor):
+
+        # set a default value on tech_profile_id for existing subscribers
+        subscribers = apps.get_model('rcord', 'RCORDSubscriber')
+
+        for s in subscribers.objects.all():
+            s.tech_profile_id = "64"
+            s.save()
+
+    dependencies = [
+        ('rcord', '0010_bandwidth_profile_values'),
+    ]
+
+    operations = [
+        migrations.RunPython(forwards),
+        migrations.AlterField(
+            model_name='rcordsubscriber_decl',
+            name='tech_profile_id',
+            field=models.IntegerField(help_text=b'Technology profile id to be used along with Technology type to retreive the profile', validators=[django.core.validators.MaxValueValidator(65535), django.core.validators.MinValueValidator(0)]),
+        ),
+    ]
diff --git a/xos/synchronizer/models/models.py b/xos/synchronizer/models/models.py
index 5a42ce5..c80cb63 100755
--- a/xos/synchronizer/models/models.py
+++ b/xos/synchronizer/models/models.py
@@ -15,7 +15,7 @@
 import re
 import socket
 import random
-
+from django.core.exceptions import ObjectDoesNotExist
 from xos.exceptions import XOSValidationError, XOSProgrammingError, XOSPermissionDenied, XOSConfigurationError
 from models_decl import RCORDService_decl, RCORDSubscriber_decl, RCORDIpAddress_decl, BandwidthProfile_decl
 
@@ -82,7 +82,6 @@
         else:
             return tag
 
-
     def generate_c_tag(self):
 
         # unused_c_tags_for_s_tag() this function will return a list of unused c_tags for the given s_tag
@@ -165,6 +164,26 @@
         else:
             return None 
 
+    def validate_tech_profile_id(self):
+        if self.owner.leaf_model.access != "voltha":
+            # if we're not using VOLTHA we don't need to validate this
+            return True
+
+        volt = None
+        for ps in self.owner.provider_services:
+            provider_service = ps.leaf_model
+            if provider_service.name.lower() == "volt":
+                volt = provider_service
+
+        technology = volt.get_olt_technology_from_unu_sn(self.onu_device)
+
+        try:
+            tp = volt.get_tech_profile(technology, self.tech_profile_id)
+            return True
+        except ObjectDoesNotExist:
+            return False
+
+
     def save(self, *args, **kwargs):
         self.validate_unique_service_specific_id(none_okay=True)
 
@@ -232,6 +251,10 @@
             if not volt_service.has_access_device(self.onu_device):
                 raise XOSValidationError("The onu_device you specified (%s) does not exists" % self.onu_device)
 
+        # validate that the tech_profile_id actually exists
+        if not self.validate_tech_profile_id():
+            raise XOSValidationError("The technology profile you specified [%s] does not exist" % self.tech_profile_id)
+
         super(RCORDSubscriber, self).save(*args, **kwargs)
         self.invalidate_related_objects()
         return
diff --git a/xos/synchronizer/models/rcord.xproto b/xos/synchronizer/models/rcord.xproto
index e1ebb9a..e08cf34 100644
--- a/xos/synchronizer/models/rcord.xproto
+++ b/xos/synchronizer/models/rcord.xproto
@@ -62,6 +62,10 @@
     optional string mac_address = 18 [
         help_text = "Subscriber MAC Address",
         max_length = 256];
+    required int32 tech_profile_id = 23 [
+        help_text = "Technology profile id to be used along with Technology type to retreive the profile",
+        min_value = 0,
+        max_value = 65535];
 
     // operator specific fields
     optional string nas_port_id = 20 [
@@ -73,10 +77,6 @@
     optional string remote_id = 22 [
         help_text = "Option 82 Remote ID for DHCP relay agent",
         max_length = 256];
-    optional int32 tech_profile_id = 23 [
-        help_text = "Technology profile id to be used along with Technology type to retreive the profile",
-        min_value = 0,
-        max_value = 65535];
 
     required manytoone upstream_bps->BandwidthProfile:us_subscriber = 31:1001 [
         help_text = "The subscriber the IP address belongs to"];
diff --git a/xos/synchronizer/models/test_models.py b/xos/synchronizer/models/test_models.py
index a0e5a4d..16b0114 100644
--- a/xos/synchronizer/models/test_models.py
+++ b/xos/synchronizer/models/test_models.py
@@ -65,10 +65,13 @@
         self.module_patcher = patch.dict('sys.modules', modules)
         self.module_patcher.start()
 
-        self.volt = Mock()
-
         from models import RCORDSubscriber, RCORDIpAddress
 
+        self.volt = Mock(name="vOLT")
+        self.volt.leaf_model.name = "vOLT"
+        self.volt.get_olt_technology_from_unu_sn.return_value = "xgspon"
+
+
         self.rcord_subscriber_class = RCORDSubscriber
 
         self.rcord_subscriber = RCORDSubscriber()
@@ -85,9 +88,12 @@
         self.rcord_subscriber.owner.provider_services = [self.volt]
         self.rcord_subscriber.list_of_unused_c_tags_for_s_tag = []
 
+
         self.rcord_ip = RCORDIpAddress()
         self.rcord_ip.subscriber = 1
 
+    # TODO add a test for validate_tech_profile_id
+
     def tearDown(self):
         sys.path = self.sys_path_save