010edabc1559de6397f64470cebcba3924ce2197
[vvp/validation-scripts.git] / ice_validator / tests / test_environment_file_parameters.py
1 # -*- coding: utf8 -*-
2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2019 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 """ environment file structure
40 """
41 import os
42 from collections import Iterable
43
44 from tests.structures import Heat
45 from tests.utils import nested_dict
46 from .helpers import (
47     validates,
48     categories,
49     get_environment_pair,
50     find_environment_file,
51     get_param,
52 )
53 import re
54 import pytest
55 from tests import cached_yaml as yaml
56
57 VERSION = "1.0.0"
58
59 # pylint: disable=invalid-name
60
61
62 def check_parameter_exists(pattern, parameters):
63     if not parameters:
64         return False
65
66     for param in parameters:
67         if pattern.search(param):
68             return True
69
70     return False
71
72
73 def check_param_in_env_file(environment_pair, param, DESIRED, exclude_parameter=None):
74
75     # workaround for internal/external parameters
76     if exclude_parameter and re.match(exclude_parameter, param):
77         return False
78
79     if not environment_pair:
80         pytest.skip("No heat/env pair could be identified")
81
82     env_file = environment_pair.get("eyml")
83
84     pattern = re.compile(r"^{}$".format(param))
85
86     if "parameters" not in env_file:
87         pytest.skip("No parameters specified in the environment file")
88
89     return (
90         check_parameter_exists(pattern, env_file.get("parameters", {})) is not DESIRED
91     )
92
93
94 """
95 This function supports this structure, deviations
96 may or may not work without enhancement
97
98 resource_id:
99     type: <resource_type>
100     properties:
101         prop0: { get_param: parameter_0 }
102         prop1:  # this is a list of dicts
103             - nested_prop_0: { get_param: parameter_1 }
104             - nested_prop_1: { get_param: [parameter_2, {index}] }
105         prop2:  # this is a dict of dicts
106             nested_prop_0: { get_param: parameter_1 }
107         prop3: { get_param: [parameter_3, 0]}
108 """
109
110
111 def check_resource_parameter(
112     environment_pair,
113     prop,
114     DESIRED,
115     resource_type,
116     resource_type_inverse=False,
117     nested_prop="",
118     exclude_resource="",
119     exclude_parameter="",
120 ):
121     if not environment_pair:
122         pytest.skip("No heat/env pair could be identified")
123
124     env_file = environment_pair.get("eyml")
125     template_file = environment_pair.get("yyml")
126
127     if "parameters" not in env_file:
128         pytest.skip("No parameters specified in the environment file")
129
130     invalid_parameters = []
131     if template_file:
132         for resource, resource_prop in template_file.get("resources", {}).items():
133
134             # workaround for subinterface resource groups
135             if exclude_resource and re.match(exclude_resource, resource):
136                 continue
137
138             if (
139                 resource_prop.get("type") == resource_type and not resource_type_inverse
140             ) or (resource_prop.get("type") != resource_type and resource_type_inverse):
141
142                 pattern = False
143
144                 if not resource_prop.get("properties"):
145                     continue
146
147                 resource_parameter = resource_prop.get("properties").get(prop)
148
149                 if not resource_parameter:
150                     continue
151                 if isinstance(resource_parameter, list) and nested_prop:
152                     for param in resource_parameter:
153                         nested_param = param.get(nested_prop)
154                         if not nested_param:
155                             continue
156
157                         if isinstance(nested_param, dict):
158                             pattern = nested_param.get("get_param")
159                         else:
160                             pattern = ""
161
162                         if not pattern:
163                             continue
164
165                         if isinstance(pattern, list):
166                             pattern = pattern[0]
167
168                         if check_param_in_env_file(
169                             environment_pair,
170                             pattern,
171                             DESIRED,
172                             exclude_parameter=exclude_parameter,
173                         ):
174                             invalid_parameters.append(pattern)
175
176                 elif isinstance(resource_parameter, dict):
177                     if nested_prop and nested_prop in resource_parameter:
178                         resource_parameter = resource_parameter.get(nested_prop)
179
180                     pattern = resource_parameter.get("get_param")
181                     if not pattern:
182                         continue
183
184                     if isinstance(pattern, list):
185                         pattern = pattern[0]
186
187                     if check_param_in_env_file(
188                         environment_pair,
189                         pattern,
190                         DESIRED,
191                         exclude_parameter=exclude_parameter,
192                     ):
193                         invalid_parameters.append(pattern)
194                 else:
195                     continue
196
197     return set(invalid_parameters)
198
199
200 def run_check_resource_parameter(
201     yaml_file, prop, DESIRED, resource_type, check_resource=True, **kwargs
202 ):
203     filepath, filename = os.path.split(yaml_file)
204     environment_pair = get_environment_pair(yaml_file)
205
206     if not environment_pair:
207         # this is a nested file
208
209         if not check_resource:
210             # dont check env for nested files
211             # This will be tested separately for parent template
212             pytest.skip("This test doesn't apply to nested files")
213
214         environment_pair = find_environment_file(yaml_file)
215         if environment_pair:
216             with open(yaml_file, "r") as f:
217                 yml = yaml.load(f)
218             environment_pair["yyml"] = yml
219         else:
220             pytest.skip("unable to determine environment file for nested yaml file")
221
222     if check_resource:
223         invalid_parameters = check_resource_parameter(
224             environment_pair, prop, DESIRED, resource_type, **kwargs
225         )
226     else:
227         invalid_parameters = check_param_in_env_file(environment_pair, prop, DESIRED)
228
229     if kwargs.get("resource_type_inverse"):
230         resource_type = "non-{}".format(resource_type)
231
232     params = (
233         ": {}".format(", ".join(invalid_parameters))
234         if isinstance(invalid_parameters, Iterable)
235         else ""
236     )
237
238     assert not invalid_parameters, (
239         "{} {} parameters in template {}{}"
240         " found in {} environment file{}".format(
241             resource_type,
242             prop,
243             filename,
244             " not" if DESIRED else "",
245             environment_pair.get("name"),
246             params,
247         )
248     )
249
250
251 @validates("R-91125")
252 def test_nova_server_image_parameter_exists_in_environment_file(yaml_file):
253     run_check_resource_parameter(yaml_file, "image", True, "OS::Nova::Server")
254
255
256 @validates("R-69431")
257 def test_nova_server_flavor_parameter_exists_in_environment_file(yaml_file):
258     run_check_resource_parameter(yaml_file, "flavor", True, "OS::Nova::Server")
259
260
261 @categories("environment_file")
262 @validates("R-22838")
263 def test_nova_server_name_parameter_doesnt_exist_in_environment_file(yaml_file):
264     run_check_resource_parameter(yaml_file, "name", False, "OS::Nova::Server")
265
266
267 @categories("environment_file")
268 @validates("R-59568")
269 def test_nova_server_az_parameter_doesnt_exist_in_environment_file(yaml_file):
270     run_check_resource_parameter(
271         yaml_file, "availability_zone", False, "OS::Nova::Server"
272     )
273
274
275 @categories("environment_file")
276 @validates("R-20856")
277 def test_nova_server_vnf_id_parameter_doesnt_exist_in_environment_file(yaml_file):
278     run_check_resource_parameter(yaml_file, "vnf_id", False, "", check_resource=False)
279
280
281 @categories("environment_file")
282 @validates("R-72871")
283 def test_nova_server_vf_module_id_parameter_doesnt_exist_in_environment_file(yaml_file):
284     run_check_resource_parameter(
285         yaml_file, "vf_module_id", False, "", check_resource=False
286     )
287
288
289 @categories("environment_file")
290 @validates("R-37039")
291 def test_nova_server_vf_module_index_parameter_doesnt_exist_in_environment_file(
292     yaml_file
293 ):
294     run_check_resource_parameter(
295         yaml_file, "vf_module_index", False, "", check_resource=False
296     )
297
298
299 @categories("environment_file")
300 @validates("R-36542")
301 def test_nova_server_vnf_name_parameter_doesnt_exist_in_environment_file(yaml_file):
302     run_check_resource_parameter(yaml_file, "vnf_name", False, "", check_resource=False)
303
304
305 @categories("environment_file")
306 @validates("R-80374")
307 def test_nova_server_vf_module_name_parameter_doesnt_exist_in_environment_file(
308     yaml_file
309 ):
310     run_check_resource_parameter(
311         yaml_file, "vf_module_name", False, "", check_resource=False
312     )
313
314
315 @categories("environment_file")
316 @validates("R-02691")
317 def test_nova_server_workload_context_parameter_doesnt_exist_in_environment_file(
318     yaml_file
319 ):
320     run_check_resource_parameter(
321         yaml_file, "workload_context", False, "", check_resource=False
322     )
323
324
325 @categories("environment_file")
326 @validates("R-13194")
327 def test_nova_server_environment_context_parameter_doesnt_exist_in_environment_file(
328     yaml_file
329 ):
330     run_check_resource_parameter(
331         yaml_file, "environment_context", False, "", check_resource=False
332     )
333
334
335 @categories("environment_file")
336 @validates("R-29872")
337 def test_neutron_port_network_parameter_doesnt_exist_in_environment_file(yaml_file):
338     run_check_resource_parameter(yaml_file, "network", False, "OS::Neutron::Port")
339
340
341 @categories("environment_file")
342 @validates("R-39841", "R-87123", "R-62590", "R-98905", "R-93030", "R-62590")
343 def test_neutron_port_external_fixedips_ipaddress_parameter_doesnt_exist_in_environment_file(
344     yaml_file
345 ):
346     run_check_resource_parameter(
347         yaml_file,
348         "fixed_ips",
349         False,
350         "OS::Neutron::Port",
351         nested_prop="ip_address",
352         exclude_parameter=re.compile(r"^(.+?)_int_(.+?)$"),
353     )
354
355
356 @validates("R-28795", "R-97201", "R-93496", "R-90206", "R-98569", "R-93496")
357 def test_neutron_port_internal_fixedips_ipaddress_parameter_exists_in_environment_file(
358     yaml_file
359 ):
360     run_check_resource_parameter(
361         yaml_file,
362         "fixed_ips",
363         True,
364         "OS::Neutron::Port",
365         nested_prop="ip_address",
366         exclude_parameter=re.compile(r"^((?!_int_).)*$"),
367     )
368
369
370 @categories("environment_file")
371 @validates("R-83677", "R-80829", "R-69634", "R-22288")
372 def test_neutron_port_fixedips_subnet_parameter_doesnt_exist_in_environment_file(
373     yaml_file
374 ):
375     run_check_resource_parameter(
376         yaml_file, "fixed_ips", False, "OS::Neutron::Port", nested_prop="subnet"
377     )
378
379
380 @categories("environment_file")
381 @validates("R-83412", "R-83418")
382 def test_neutron_port_external_aap_ip_parameter_doesnt_exist_in_environment_file(
383     yaml_file
384 ):
385     run_check_resource_parameter(
386         yaml_file,
387         "allowed_address_pairs",
388         False,
389         "OS::Neutron::Port",
390         nested_prop="ip_address",
391         exclude_parameter=re.compile(r"^(.+?)_int_(.+?)$"),
392     )
393
394
395 @categories("environment_file")
396 @validates("R-99812")
397 def test_non_nova_server_name_parameter_doesnt_exist_in_environment_file(yaml_file):
398     run_check_resource_parameter(
399         yaml_file, "name", False, "OS::Nova::Server", resource_type_inverse=True
400     )
401
402
403 @categories("environment_file")
404 @validates("R-92193")
405 def test_network_fqdn_parameter_doesnt_exist_in_environment_file(yaml_file):
406     run_check_resource_parameter(
407         yaml_file, r"^(.+?)_net_fqdn$", False, "", check_resource=False
408     )
409
410
411 @categories("environment_file")
412 @validates("R-76682")
413 def test_contrail_route_prefixes_parameter_doesnt_exist_in_environment_file(yaml_file):
414     run_check_resource_parameter(
415         yaml_file,
416         "interface_route_table_routes",
417         False,
418         "OS::ContrailV2::InterfaceRouteTable",
419         nested_prop="interface_route_table_routes_route",
420     )
421
422
423 @validates("R-50011")
424 def test_heat_rg_count_parameter_exists_in_environment_file(yaml_file):
425     run_check_resource_parameter(
426         yaml_file,
427         "count",
428         True,
429         "OS::Heat::ResourceGroup",
430         exclude_resource=re.compile(r"^(.+?)_subint_(.+?)_port_(.+?)_subinterfaces$"),
431     )
432
433
434 @categories("environment_file")
435 @validates("R-100020", "R-100040", "R-100060", "R-100080", "R-100170")
436 def test_contrail_external_instance_ip_does_not_exist_in_environment_file(yaml_file):
437     run_check_resource_parameter(
438         yaml_file,
439         "instance_ip_address",
440         False,
441         "OS::ContrailV2::InstanceIp",
442         exclude_resource=re.compile(r"^.*_int_.*$"),  # exclude internal IPs
443     )
444
445
446 @validates("R-100100", "R-100120", "R-100140", "R-100160", "R-100180")
447 def test_contrail_internal_instance_ip_does_exist_in_environment_file(yaml_file):
448     run_check_resource_parameter(
449         yaml_file,
450         "instance_ip_address",
451         True,
452         "OS::ContrailV2::InstanceIp",
453         exclude_resource=re.compile(r"(?!.*_int_.*)"),  # exclude external IPs
454     )
455
456
457 @categories("environment_file")
458 @validates("R-100210", "R-100230", "R-100250", "R-100270")
459 def test_contrail_subnet_uuid_does_not_exist_in_environment_file(yaml_file):
460     run_check_resource_parameter(
461         yaml_file, "subnet_uuid", False, "OS::ContrailV2::InstanceIp"
462     )
463
464
465 @categories("environment_file")
466 @validates("R-100320", "R-100340")
467 def test_contrail_vmi_aap_does_not_exist_in_environment_file(yaml_file):
468     # This test needs to check a more complex structure.  Rather than try to force
469     # that into the existing run_check_resource_parameter logic we'll just check it
470     # directly
471     pairs = get_environment_pair(yaml_file)
472     if not pairs:
473         pytest.skip("No matching env file found")
474     heat = Heat(filepath=yaml_file)
475     env_parameters = pairs["eyml"].get("parameters") or {}
476     vmis = heat.get_resource_by_type("OS::ContrailV2::VirtualMachineInterface")
477     external_vmis = {rid: data for rid, data in vmis.items() if "_int_" not in rid}
478     invalid_params = []
479     for r_id, vmi in external_vmis.items():
480         aap_value = nested_dict.get(
481             vmi,
482             "properties",
483             "virtual_machine_interface_allowed_address_pairs",
484             "virtual_machine_interface_allowed_address_pairs_allowed_address_pair",
485         )
486         if not aap_value or not isinstance(aap_value, list):
487             # Skip if aap not used or is not a list.
488             continue
489         for pair_ip in aap_value:
490             if not isinstance(pair_ip, dict):
491                 continue  # Invalid Heat will be detected by another test
492             settings = (
493                 pair_ip.get(
494                     "virtual_machine_interface_allowed_address"
495                     "_pairs_allowed_address_pair_ip"
496                 )
497                 or {}
498             )
499             if isinstance(settings, dict):
500                 ip_prefix = (
501                     settings.get(
502                         "virtual_machine_interface_allowed_address"
503                         "_pairs_allowed_address_pair_ip_ip_prefix"
504                     )
505                     or {}
506                 )
507                 ip_prefix_param = get_param(ip_prefix)
508                 if ip_prefix_param and ip_prefix_param in env_parameters:
509                     invalid_params.append(ip_prefix_param)
510
511     msg = (
512         "OS::ContrailV2::VirtualMachineInterface "
513         "virtual_machine_interface_allowed_address_pairs"
514         "_allowed_address_pair_ip_ip_prefix "
515         "parameters found in environment file {}: {}"
516     ).format(pairs.get("name"), ", ".join(invalid_params))
517     assert not invalid_params, msg