CORD-2594: Autodetect blueprints from EPC component services
CORD-2595: Generalize instance dependency checks

Change-Id: I115205f2b74cdf1d28be73455f3f0d983ea55695
diff --git a/xos/synchronizer/steps/sync_vspgwutenant.py b/xos/synchronizer/steps/sync_vspgwutenant.py
index df207af..f56988d 100644
--- a/xos/synchronizer/steps/sync_vspgwutenant.py
+++ b/xos/synchronizer/steps/sync_vspgwutenant.py
@@ -18,29 +18,65 @@
 from synchronizers.new_base.modelaccessor import *
 from synchronizers.new_base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
 
+from collections import defaultdict
+
 parentdir = os.path.join(os.path.dirname(__file__), "..")
 sys.path.insert(0, parentdir)
 
-
 class ServiceGraphException(Exception):
     pass
 
-
 class SyncVSPGWUTenant(SyncInstanceUsingAnsible):
     observes = VSPGWUTenant
     template_name = "vspgwutenant_playbook.yaml"
     service_key_name = "/opt/xos/configurations/mcord/mcord_private_key"
 
+    """ Convert ServiceInstance graph into an adjacency set"""
+    def adj_set_of_service_graph(self, o, visited = None, adj_set = None):
+        def key(o):
+            return o.leaf_model_name
+
+        if not adj_set:
+            adj_set = defaultdict(set)
+
+        if not o:
+            return adj_set
+        else:
+            if not visited:
+                visited = set()
+
+            ko = key(o)
+            visited.add(ko)
+
+            provider_links = ServiceInstanceLink.objects.filter(subscriber_service_instance_id = o.id)
+            for l in provider_links:
+                n = l.provider_service_instance
+                kn = key(n)
+                adj_set[ko].add(kn)
+                adj_set[kn].update([])
+                if kn not in visited:
+                    adj_set = self.adj_set_of_service_graph(n, visited, adj_set)
+
+            subscriber_links = ServiceInstanceLink.objects.filter(provider_service_instance_id = o.id)
+            for l in subscriber_links:
+                n = l.subscriber_service_instance
+                sn = key(n)
+                adj_set[sn].add(ko)
+                adj_set[ko].update([])
+                if sn not in visited:
+                    adj_set = self.adj_set_of_service_graph(n, visited, adj_set)
+
+            return adj_set
+
     def __init__(self, *args, **kwargs):
         super(SyncVSPGWUTenant, self).__init__(*args, **kwargs)
 
     def get_extra_attributes(self, o):
+        blueprint = self.get_blueprint_and_check_dependencies(o)
 
-        scenario = self.get_scenario(o)
-
-        if scenario == 'cord_4_1_scenario':
+        if blueprint == 'cord_4_1_blueprint':
             return self.get_values_for_CORD_4_1(o)
-        elif scenario == 'cord_5_0_scenario':
+        elif blueprint == 'cord_5_0_blueprint':
             return self.get_values_for_CORD_5_0(o)
         else:
             return self.get_extra_attributes_for_manual(o)
@@ -48,7 +84,7 @@
     # fields for manual case
     def get_extra_attributes_for_manual(self, o):
         fields = {}
-        fields['scenario'] = "manual"
+        fields['blueprint'] = "manual"
         fields['cord_version'] = "manual"
         # for interface.cfg file
         fields['zmq_sub_ip'] = "manual"
@@ -73,7 +109,7 @@
     def get_values_for_CORD_4_1(self, o):
         fields = {}
         fields['cord_version'] = "4.1"
-        fields['scenario'] = "cord_4_1_scenario"
+        fields['blueprint'] = "cord_4_1_blueprint"
         # for interface.cfg file
         fields['zmq_sub_ip'] = "127.0.0.1"
         fields['zmq_pub_ip'] = "127.0.0.1"
@@ -105,7 +141,7 @@
     def get_values_for_CORD_5_0(self, o):
         fields = {}
         fields['cord_version'] = "5.0"
-        fields['scenario'] = "cord_5_0_scenario"
+        fields['blueprint'] = "cord_5_0_blueprint"
 
         # for interface.cfg file
         fields['zmq_sub_ip'] = "127.0.0.1"
@@ -146,38 +182,52 @@
 
         return i.leaf_model.instance_id
 
-    # Which scenario does it use among Spirent or NG4T?
-    def get_scenario(self, o):
-        # try get vENB instance: one of both Spirent and NG4T
-        venb_flag = self.has_instance("VENBServiceInstance", o)
-        vmme_flag = self.has_instance("VMMETenant", o)
-        sdncontroller_flag = self.has_instance(
-            "SDNControllerServiceInstance", o)
-        vspgwc_flag = self.has_instance("VSPGWCTenant", o)
-        internetemulator_flag = self.has_instance(
-            "InternetEmulatorServiceInstance", o)
-        vhss_flag = self.has_instance("VHSSTenant", o)
-        hssdb_flag = self.has_instance("HSSDBServiceInstance", o)
+    def find_first_blueprint_subgraph(self, blueprints, adj_set):
+        found_blueprint = None
+        for blueprint in blueprints:
+            found = True
 
-        if (o.blueprint == "build") or (o.blueprint == "MCORD 4.1"):
-            if not venb_flag:
-                self.defer_sync(o, "Waiting for eNB image to become available")
-            if not vspgwc_flag:
-                self.defer_sync(o, "Waiting for SPGWC image to become available")
-            return 'cord_4_1_scenario'
+            for node in blueprint['graph']:
+                if node['name'] not in adj_set:
+                    found = False
+                    break
+                try:
+                    links = node['links']
+                except KeyError:
+                    links = []
 
-        if (o.blueprint == "mcord_5") or (o.blueprint == "MCORD 5"):
-            if not hssdb_flag:
-                self.defer_sync(o, "Waiting for HSS_DB image to become available")
-            if not vhss_flag:
-                self.defer_sync(o, "Waiting for vHSS image to become available")
-            if not vmme_flag:
-                self.defer_sync(o, "Waiting for vMME image to become available")
-            if not vspgwc_flag:
-                self.defer_sync(o, "Waiting for vSPGWC image to become available")
-            return 'cord_5_0_scenario'
+                for link in links:
+                    if link['name'] not in adj_set[node['name']]:
+                        found = False
+                        break
+                if not found:
+                    break
 
-        return 'manual'
+            if found:
+                found_blueprint = blueprint['name']
+                break
+
+        return found_blueprint
+
+    def check_instance_dependencies(self, blueprints, blueprint_name, o):
+        blueprint = next(
+                b for b in blueprints if b['name'] == blueprint_name)
+        node = next(n for n in blueprint['graph'] if n['name'] == o.leaf_model_name)
+        for link in node['links']:
+            flag = self.has_instance(link['name'], o)
+            if not flag:
+                self.defer_sync('%s does not have an instance. Deferring synchronization.'%link['name'])
+            
+    def get_blueprint_and_check_dependencies(self, o):
+        blueprints = Config().get('blueprints')
+
+        adj_set = self.adj_set_of_service_graph(o)
+        blueprint_name = self.find_first_blueprint_subgraph(blueprints, adj_set)
+        if blueprint_name:
+            self.check_instance_dependencies(blueprints, blueprint_name, o)
+
+        ret_blueprint = 'manual' if not blueprint_name else blueprint_name
+        return ret_blueprint
 
     def get_peer_serviceinstance_of_type(self, sitype, o):
 
@@ -277,3 +327,4 @@
     # To get each network id
     def get_network_id(self, network_name):
         return Network.objects.get(name=network_name).id
+
diff --git a/xos/synchronizer/vspgwu_config.yaml b/xos/synchronizer/vspgwu_config.yaml
index 5ada1cb..81035b5 100644
--- a/xos/synchronizer/vspgwu_config.yaml
+++ b/xos/synchronizer/vspgwu_config.yaml
@@ -21,3 +21,27 @@
 steps_dir: "/opt/xos/synchronizers/vspgwu/steps"
 sys_dir: "/opt/xos/synchronizers/vspgwu/sys"
 model_policies_dir: "/opt/xos/synchronizers/vspgwu/model_policies"
+blueprints:
+  - name: cord_5_0_blueprint
+    graph:
+      - name: VMMETenant
+        links:
+      - name: VSPGWCTenant
+        links:
+          - name: VMMETenant
+          - name: VSPGWUTenant
+      - name: VSPGWUTenant
+      - name: VHSSTenant
+        links:
+          - name: HSSDBServiceInstance
+      - name: HSSDBServiceInstance
+  - name: cord_4_1_blueprint
+    graph:
+      - name: VSPGWUTenant
+        links:
+          - name: VENBServiceInstance
+      - name: VENBServiceInstance
+      - name: VSPGWCTenant
+        links:
+          - name: VENBServiceInstance
+          - name: VSPGWUTenant
diff --git a/xos/vspgwu.xproto b/xos/vspgwu.xproto
index a914988..23827a9 100644
--- a/xos/vspgwu.xproto
+++ b/xos/vspgwu.xproto
@@ -21,6 +21,5 @@
     optional string enodeb_mac_addr = 3 [help_text = "external eNodeB MAC address (for 5.0)", default = "11:11:11:11:11:11", max_length = 32, null = True, db_index = False, blank = True];
     optional string appserver_ip_addr = 4 [help_text = "external app server IP address (for 5.0)", default = "127.0.0.1", max_length = 32, null = True, db_index = False, blank = True];
     optional string appserver_mac_addr = 5 [help_text = "external app server MAC address (for 5.0)", default = "11:11:11:11:11:11", max_length = 32, null = True, db_index = False, blank = True];
-    optional string blueprint = 6 [help_text = "name of blueprint", default = "manual", max_length = 32, null = True, db_index = False, blank = True];
 }