Fix for KDT node disappearance

One node went for a walk and disappeared. This change
brings it back.

PROD-26064

Change-Id: I67845af1f23f566acf86f9db0a18b2f971d2fc73
diff --git a/reclass/nodegenerator.py b/reclass/nodegenerator.py
index bb21c48..43c451d 100644
--- a/reclass/nodegenerator.py
+++ b/reclass/nodegenerator.py
@@ -31,6 +31,10 @@
 _param_reference = re.compile('\$\{_param\:.*?\}')
 _refernce_name = re.compile('\$\{_param\:(.*?)\}')
 _iprange = re.compile('<<(.*?)>>')
+NODEF = 'NOTADEFINITION'
+CTRAIT = ['storage.system', 'cluster.']
+STRAIT = 'system/reclass/storage'
+ITRAIT = 'infra/init.yml'
 
 
 def get_references(string):
@@ -43,6 +47,27 @@
     return re.findall(_refernce_name, string)
 
 
+def contains(x, *sub):
+    return any(x.find(s) != -1 for s in sub)
+
+
+def get_params(data):
+    return data.get('parameters', {}).get('_param', {})
+
+
+def get_nodes(data):
+    return data.get('parameters', {}).get('reclass', {}).get(
+                    'storage', {}).get('node', {})
+
+
+def get_cls(data):
+    return data.get('classes', [])
+
+
+def get_system(data):
+    return data.get("parameters", {}).get("linux", {}).get("system", {})
+
+
 def has_subst(value):
     """Checks if an element has slot for substitution."""
     # NOTE: the code does not address possible nested references so far since
@@ -105,9 +130,7 @@
             # will be used till more statistics is collected and a generic
             # solution is created.
                 if key == 'classes':
-                    for el in value:
-                        if 'gateway' in el:
-                            x[key].extend(value)
+                    [x[key].extend(value) for el in value if 'gateway' in el]
                 continue
             if isinstance(x[key], dict) and isinstance(value, dict):
                 update_dict(x[key], value)
@@ -116,10 +139,8 @@
 
 
 def get_substitution_parameters(source):
-    result = {}
-    exps = [x.get('parameters', {}).get('_param', {}) for x in source]
-    for expansion in exps:
-        update_dict(result, expansion)
+    exps, result = [get_params(x) for x in source], {}
+    [update_dict(result, e) for e in exps]
     return result
 
 external = {}
@@ -130,12 +151,9 @@
         for fname in [x for x in files if x.endswith('yml')]:
             config = os.path.join(rootdir, fname)
             if fname == 'init.yml':
-                if config.find("infra/init.yml") != -1:
+                if contains(config, ITRAIT):
                     with open(config, 'r') as f:
-                        ddd = yaml.load(f)
-                        external.update(ddd.get("parameters", {}).
-                                            get("linux", {}).
-                                            get("system", {}))
+                        external.update(get_system(yaml.load(f)))
                 configs_to_process.append(config)
             # NOTE: this is a special case left here for the time being.
             elif fname == 'nodes.yml':  # TODO: refactor it.
@@ -145,7 +163,7 @@
                     data = yaml.load(f)
                     if data is None:
                         continue
-                    if data.get('parameters', {}).get('_param') is not None:
+                    if get_params(data):
                         configs_to_process.append(config)
     for config in configs_to_process:
         with open(config, 'r') as f:
@@ -160,22 +178,27 @@
         fn = os.path.join(basename, x.replace('.', '/') + '.yml')
         return fn if os.path.isfile(fn) else os.path.join(fn[:-4], 'init.yml')
 
-    out, storage_classnames, cluster_cn = collections.defaultdict(dict), [], []
-    for cfg in cfgs:
-        storage_classnames.extend(x for x in cfg.get('classes', [])
-                                  if ((x.find('storage.system') != -1)
-                                  or x.find('cluster.') != -1))
-    for x in map(fixname, storage_classnames):
+    def innerread(x, params, content):
         with open(x, 'r') as f:
             data = yaml.load(f)
-        if params is None:
-            params = {}
-        if data  is None:
-            data = {}
-        update_dict(params, data.get('parameters', {}).get('_param', {}), True)
-        node_content = (data.get('parameters', {}).get('reclass', {})
-                            .get('storage', {}).get('node', {}))
+        params = {} if params is None else params
+        data = {} if data is None else data
+        update_dict(params, get_params(data), True)
+        update_dict(content, get_nodes(data),  True)
+        depends_on = get_cls(data)
+        # TODO: this works around single (so far) issue with structured
+        # reference in system/reclass/storage.
+        if depends_on and contains(x, STRAIT):
+            for klass in depends_on:
+                innerread(fixname(klass), params, content)
+        return data
 
+    out, storage_cnames, cluster_cn = collections.defaultdict(dict), [], []
+    for cfg in cfgs:
+        storage_cnames.extend(x for x in get_cls(cfg) if contains(x, *CTRAIT))
+    for x in map(fixname, storage_cnames):
+        node_content = {}
+        data = innerread(x, params, node_content)
         for nodename, nodecontent in node_content.iteritems():
             if out[nodename].get('src') is not None:
                 out[nodename]['src'].append(x)
@@ -192,13 +215,12 @@
 
 def dump_to_files(content, dest):
     for res in content:
-        tt = ".".join([res.get('name', 'NOTADEFINITION'),
-                       res.get('domain','NOTADEFINITION'), 'yml'])
-        if tt.find('NOTADEFINITION') !=-1:
+        tt = ".".join([res.get('name', NODEF), res.get('domain',NODEF), 'yml'])
+        if contains(tt, NODEF):
             continue
         systemdesc = UnsortableDict()
-        systemdesc['name'] = res.get('name', 'FOO')
-        systemdesc['domain'] = res.get('domain', 'BAR')
+        systemdesc['name'] = res.get('name')
+        systemdesc['domain'] = res.get('domain')
         # NOTE: this should stay here until cfg01 definition stabilizes.
         if systemdesc['name'] != 'cfg01':
             systemdesc['cluster'] = res.get('cluster', 'default')