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

Change-Id: I5656782bfeeccca5a9855ffc1a5bd8de646fa9e3
diff --git a/xos/synchronizer/steps/sync_vspgwctenant.py b/xos/synchronizer/steps/sync_vspgwctenant.py
index 0318ad6..aa1e8b2 100644
--- a/xos/synchronizer/steps/sync_vspgwctenant.py
+++ b/xos/synchronizer/steps/sync_vspgwctenant.py
@@ -19,9 +19,13 @@
 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)
 
+blueprints = Config().get('blueprints')
+
 class ServiceGraphException(Exception):
     pass
 
@@ -30,16 +34,53 @@
     template_name = "vspgwctenant_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(SyncVSPGWCTenant, self).__init__(*args, **kwargs)
 
     def get_extra_attributes(self, o):
 
-        scenario = self.get_scenario(o)
+        blueprint = self.get_blueprint_and_check_dependencies(o)
 
-        if scenario == 'cord_4_1_scenario':
+        if blueprint == 'cord_4_1_scenario':
             return self.get_values_for_CORD_4_1(o)
-        elif scenario == 'cord_5_0_scenario':
+        elif blueprint == 'cord_5_0_scenario':
             return self.get_values_for_CORD_5_0(o)
         else:
             return self.get_extra_attributes_for_manual(o)
@@ -132,38 +173,51 @@
             'sgi_network', "VSPGWUTenant", o, 'sgi_spgwu_ip')
 
         return fields
+    
+    def find_first_blueprint_subgraph(self, blueprints, adj_set):
+        found_blueprint = None
+        for blueprint in blueprints:
+            found = True
 
+            for node in blueprint['graph']:
+                if node['name'] not in adj_set:
+                    found = False
+                    break
+                try:
+                    links = node['links']
+                except KeyError:
+                    links = []
 
-    def get_scenario(self, o):
-        venb_flag = self.has_instance("VENBServiceInstance", o)
-        vmme_flag = self.has_instance("VMMETenant", o)
-        sdncontroller_flag = self.has_instance(
-            "SDNControllerServiceInstance", o)
-        vspgwu_flag = self.has_instance("VSPGWUTenant", o)
-        internetemulator_flag = self.has_instance(
-            "InternetEmulatorServiceInstance", o)
-        vhss_flag = self.has_instance("VHSSTenant", o)
-        hssdb_flag = self.has_instance("HSSDBServiceInstance", o)
+                for link in links:
+                    if link['name'] not in adj_set[node['name']]:
+                        found = False
+                        break
+                if not found:
+                    break
 
-        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 vspgwu_flag:
-                self.defer_sync(o, "Waiting for SPGWU image to become available")
-            return 'cord_4_1_scenario'
+            if found:
+                found_blueprint = blueprint['name']
+                break
 
-        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 vspgwu_flag:
-                self.defer_sync(o, "Waiting for vSPGWU image to become available")
-            return 'cord_5_0_scenario'
+        return found_blueprint
 
-        return 'manual'
+    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):
+        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_ip_address_from_peer_service_instance(self, network_name, sitype, o, parameter=None):
         peer_si = self.get_peer_serviceinstance_of_type(sitype, o)
diff --git a/xos/synchronizer/vspgwc_config.yaml b/xos/synchronizer/vspgwc_config.yaml
index 54c487e..a824b80 100644
--- a/xos/synchronizer/vspgwc_config.yaml
+++ b/xos/synchronizer/vspgwc_config.yaml
@@ -20,3 +20,28 @@
 steps_dir: "/opt/xos/synchronizers/vspgwc/steps"
 sys_dir: "/opt/xos/synchronizers/vspgwc/sys"
 model_policies_dir: "/opt/xos/synchronizers/vspgwc/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/vspgwc.xproto b/xos/vspgwc.xproto
index 9038da1..22a24af 100644
--- a/xos/vspgwc.xproto
+++ b/xos/vspgwc.xproto
@@ -21,5 +21,4 @@
     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];
 }