[VVP] Add test for R-100260 and fix mapping
[vvp/validation-scripts.git] / ice_validator / tests / utils / nested_files.py
1 # -*- coding: utf8 -*-
2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
7 #
8 # Unless otherwise specified, all software contained herein is licensed
9 # under the Apache License, Version 2.0 (the "License");
10 # you may not use this software except in compliance with the License.
11 # You may obtain a copy of the License at
12 #
13 #             http://www.apache.org/licenses/LICENSE-2.0
14 #
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS,
17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 # See the License for the specific language governing permissions and
19 # limitations under the License.
20 #
21 #
22 #
23 # Unless otherwise specified, all documentation contained herein is licensed
24 # under the Creative Commons License, Attribution 4.0 Intl. (the "License");
25 # you may not use this documentation except in compliance with the License.
26 # You may obtain a copy of the License at
27 #
28 #             https://creativecommons.org/licenses/by/4.0/
29 #
30 # Unless required by applicable law or agreed to in writing, documentation
31 # distributed under the License is distributed on an "AS IS" BASIS,
32 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 # See the License for the specific language governing permissions and
34 # limitations under the License.
35 #
36 # ============LICENSE_END============================================
37 #
38 #
39
40 """nested files
41 """
42
43 from os import path, listdir
44 import re
45 from tests import cached_yaml as yaml
46 from tests.structures import Heat
47
48 VERSION = "1.4.0"
49
50 """
51 test nesting depth
52 0 -> 1 -> 2 -> too deep.
53 """
54 MAX_DEPTH = 3
55
56
57 def check_for_invalid_nesting(  # pylint: disable=too-many-branches
58     yml, yaml_file, dirpath
59 ):
60     """
61     return a list of all nested files
62     """
63     if not hasattr(yml, "items"):
64         return []
65     invalid_nesting = []
66     p = re.compile("^[A-z]*::[A-z]*::[A-z]*$")
67
68     for v in yml.values():
69         if isinstance(v, dict) and "type" in v:
70             t = v["type"]
71             if t.endswith(".yml") or t.endswith(".yaml"):
72                 filepath = path.join(dirpath, t)
73             elif t == "OS::Heat::ResourceGroup":
74                 rd = v["properties"]["resource_def"]
75                 if not isinstance(rd, dict) or "type" not in rd:
76                     invalid_nesting.append(yaml_file)
77                     continue
78                 elif not p.match(rd["type"]):
79                     filepath = path.join(dirpath, rd["type"])
80                 else:
81                     continue
82             else:
83                 continue
84             try:
85                 with open(filepath) as fh:
86                     yml = yaml.load(fh)
87             except yaml.YAMLError as e:
88                 invalid_nesting.append(filepath)
89                 print(e)  # pylint: disable=superfluous-parens
90             invalid_nesting.extend(check_for_invalid_nesting(yml, filepath, dirpath))
91         if isinstance(v, dict):
92             invalid_nesting.extend(check_for_invalid_nesting(v, yaml_file, dirpath))
93         elif isinstance(v, list):
94             for d in v:
95                 invalid_nesting.extend(check_for_invalid_nesting(d, yaml_file, dirpath))
96     return invalid_nesting
97
98
99 def get_dict_of_nested_files(yml, dirpath):
100     """Return dict.
101     key: resource id in yml which references a nested file.
102     value: the nested file name.
103     Nested files are either referenced through "type", or
104     for OS::Heat::ResourceGroup, through "resource_def type".
105     """
106     nested_files = get_type_nested_files(yml, dirpath)
107     nested_files.update(get_resourcegroup_nested_files(yml, dirpath))
108     return nested_files
109
110
111 def get_list_of_nested_files(yml, dirpath):
112     """
113     return a list of all nested files
114     """
115
116     if not hasattr(yml, "items"):
117         return []
118
119     nested_files = []
120
121     for v in yml.values():
122         if isinstance(v, dict) and "type" in v:
123             t = v["type"]
124             if t.endswith(".yml") or t.endswith(".yaml"):
125                 filepath = path.join(dirpath, t)
126                 if path.exists(filepath):
127                     with open(filepath) as fh:
128                         t_yml = yaml.load(fh)
129                     nested_files.append(filepath)
130                     nested_files.extend(get_list_of_nested_files(t_yml, dirpath))
131             elif t == "OS::Heat::ResourceGroup":
132                 rdt = v.get("properties", {}).get("resource_def", {}).get("type", None)
133                 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
134                     filepath = path.join(dirpath, rdt)
135                     if path.exists(filepath):
136                         with open(filepath) as fh:
137                             rdt_yml = yaml.load(fh)
138                         nested_files.append(filepath)
139                         nested_files.extend(get_list_of_nested_files(rdt_yml, dirpath))
140         if isinstance(v, dict):
141             nested_files.extend(get_list_of_nested_files(v, dirpath))
142         elif isinstance(v, list):
143             for d in v:
144                 nested_files.extend(get_list_of_nested_files(d, dirpath))
145     return nested_files
146
147
148 def get_nesting(yaml_files):
149     """return bad, files, heat, depths
150     bad - list of error messages.
151     files - dict: key is filename, value is dict of nested files.
152             This is the tree.
153     heat - dict,: key is filename, value is Heat instance.
154     depths - dict: key is filename, value is a depth tuple
155
156     level: 0           1         2         3
157     file:  template -> nested -> nested -> nested
158     depth: 3           2         1         0
159     """
160     bad = []
161     files = {}
162     heat = {}
163     depths = {}
164     for yaml_file in yaml_files:
165         dirname, basename = path.split(yaml_file)
166         h = Heat(filepath=yaml_file)
167         heat[basename] = h
168         files[basename] = get_dict_of_nested_files(h.yml, dirname)
169     for filename in files:
170         depths[filename] = _get_nesting_depth_start(0, filename, files, [])
171         for depth in depths[filename]:
172             if depth[0] > MAX_DEPTH:
173                 bad.append("{} {}".format(filename, str(depth[1])))
174     return bad, files, heat, depths
175
176
177 def _get_nesting_depth_start(depth, filename, files, context):
178     depths = []
179     for rid, nf in files[filename].items():
180         depths.append(_get_nesting_depth(1, nf, files, context))
181     return depths
182
183
184 def _get_nesting_depth(depth, filename, files, context):
185     """Return a depth tuple (max_depth, current_context).
186     `context` is the list of filenames.
187     `depth` is the length of `context`.
188     Finds the max_depth of all the resources of `filename`.
189     current_context is the updated list of filenames
190     and max_depth is its length.
191     """
192     max_depth = depth + 1
193     current_context = context + [filename]
194     if depth <= MAX_DEPTH:
195         nested_filenames = files.get(filename, {})
196         if nested_filenames:
197             max_depth, current_context = max(
198                 _get_nesting_depth(depth + 1, nested_filename, files, current_context)
199                 for nested_filename in nested_filenames.values()
200             )
201     return max_depth, current_context
202
203
204 def get_resourcegroup_nested_files(yml, dirpath):
205     """
206     return a dict.
207     key: key in yml which references a nested ResourceGroup file.
208         (resource->type is ResourceGroup
209             and resource->properties->resource_def->type is a yaml file)
210     value: the nested file name.
211
212     The keys are assumed to be unique across files.
213     A separate test checks for that.
214     """
215
216     if not hasattr(yml, "get"):
217         return {}
218
219     nested_files = {}
220     for rid, r in yml.get("resources", {}).items():
221         if isinstance(r, dict) and "type" in r:
222             t = r["type"]
223             nested_file = None
224             if t == "OS::Heat::ResourceGroup":
225                 rdt = r.get("properties", {}).get("resource_def", {}).get("type", None)
226                 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
227                     nested_file = rdt
228             if nested_file:
229                 filepath = path.join(dirpath, nested_file)
230                 if path.exists(filepath):
231                     nested_files[rid] = nested_file
232     return nested_files
233
234
235 def get_type_nested_files(yml, dirpath):
236     """
237     return a dict.
238     key: key in yml which references a nested type file.
239         (the resource "type" is a yaml file.)
240     value: the nested file name.
241
242     The keys are assumed to be unique across files.
243     A separate test checks for that.
244     """
245
246     if not hasattr(yml, "get"):
247         return {}
248
249     nested_files = {}
250     for rid, r in yml.get("resources", {}).items():
251         if isinstance(r, dict) and "type" in r:
252             t = r["type"]
253             nested_file = None
254             if t.endswith(".yml") or t.endswith(".yaml"):
255                 nested_file = t
256             if nested_file:
257                 filepath = path.join(dirpath, nested_file)
258                 if path.exists(filepath):
259                     nested_files[rid] = nested_file
260     return nested_files
261
262
263 def get_nested_files(filenames):
264     """
265     returns all the nested files for a set of filenames
266     """
267     nested_files = []
268     for filename in filenames:
269         if file_is_a_nested_template(filename):
270             nested_files.append(filename)
271     return nested_files
272
273
274 def file_is_a_nested_template(file):
275     directory = path.dirname(file)
276     nested_files = []
277     for filename in listdir(directory):
278         if filename.endswith(".yaml") or filename.endswith(".yml"):
279             filename = "{}/{}".format(directory, filename)
280             try:
281                 with open(filename) as fh:
282                     yml = yaml.load(fh)
283                 if "resources" not in yml:
284                     continue
285                 nested_files.extend(
286                     get_list_of_nested_files(yml["resources"], path.dirname(filename))
287                 )
288             except yaml.YAMLError as e:
289                 print(e)  # pylint: disable=superfluous-parens
290                 continue
291     return file in nested_files