Enforce black code format via pre-commit hook
[vvp/validation-scripts.git] / ice_validator / tests / structures.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 """structures
40 """
41 import collections
42 import inspect
43 import os
44 import re
45 import sys
46
47 from tests import cached_yaml as yaml
48 from tests.helpers import load_yaml, get_param
49 from .utils import nested_dict
50
51 VERSION = "4.2.0"
52
53 # key = pattern, value = regex compiled from pattern
54 _REGEX_CACHE = {}
55
56
57 def _get_regex(pattern):
58     """Return a compiled version of pattern.
59     Keep result in _REGEX_CACHE to avoid re-compiling.
60     """
61     regex = _REGEX_CACHE.get(pattern, None)
62     if regex is None:
63         regex = re.compile(pattern)
64         _REGEX_CACHE[pattern] = regex
65     return regex
66
67
68 class Hashabledict(dict):
69     """A hashable dict.
70     dicts with the same keys and whose keys have the same values
71     are assigned the same hash.
72     """
73
74     def __hash__(self):
75         return hash((frozenset(self), frozenset(self.values())))
76
77
78 class HeatProcessor(object):
79     """base class for xxxx::xxxx::xxxx processors
80     """
81
82     resource_type = None  # string 'xxxx::xxxx::xxxx'
83     re_rids = collections.OrderedDict()  # OrderedDict of name: regex
84     # name is a string to name the regex.
85     # regex parses the proper resource id format.
86
87     @staticmethod
88     def get_param_value(value, withIndex=False):
89         """Return get_param value of `value`
90         """
91         if isinstance(value, dict) and len(value) == 1:
92             v = value.get("get_param")
93             if isinstance(v, list) and v:
94                 if withIndex and len(v) > 1:
95                     idx = v[1]
96                     if isinstance(idx, dict):
97                         idx = idx.get("get_param", idx)
98                     v = "{}{}".format(v[0], idx)
99                 else:
100                     v = v[0]
101         else:
102             v = None
103         return v
104
105     @classmethod
106     def get_resource_or_param_value(cls, value):
107         """Return the get_resource or get_param value of `value`
108         """
109         if isinstance(value, dict) and len(value) == 1:
110             v = value.get("get_resource") or cls.get_param_value(value)
111         else:
112             v = None
113         return v
114
115     @classmethod
116     def get_rid_match_tuple(cls, rid):
117         """find the first regex matching `rid` and return the tuple
118         (name, match object) or ('', None) if no match.
119         """
120         rid = "" if rid is None else rid
121         for name, regex in cls.re_rids.items():
122             match = regex.match(rid)
123             if match:
124                 return name, match
125         return "", None
126
127     @classmethod
128     def get_rid_patterns(cls):
129         """Return OrderedDict of name: friendly regex.pattern
130         "friendly" means the group notation is replaced with
131         braces, and the trailing "$" is removed.
132
133         NOTE
134         nested parentheses in any rid_pattern will break this parser.
135         The final character is ASSUMED to be a dollar sign.
136         """
137         friendly_pattern = _get_regex(r"\(\?P<(.*?)>.*?\)")
138         rid_patterns = collections.OrderedDict()
139         for name, regex in cls.re_rids.items():
140             rid_patterns[name] = friendly_pattern.sub(
141                 r"{\1}", regex.pattern  # replace groups with braces
142             )[
143                 :-1
144             ]  # remove trailing $
145         return rid_patterns
146
147     @classmethod
148     def get_str_replace_name(cls, resource_dict):
149         """Return the name modified by str_replace of `resource_dict`,
150         a resource (i.e. a value in some template's resources).
151         Return None, if there is no name, str_replace, its template,
152         or any missing parameters.
153         """
154         str_replace = Heat.nested_get(
155             resource_dict, "properties", "name", "str_replace"
156         )
157         if not str_replace:
158             return None
159         template = Heat.nested_get(str_replace, "template")
160         if not isinstance(template, str):
161             return None
162         params = Heat.nested_get(str_replace, "params", default={})
163         if not isinstance(params, dict):
164             return None
165         # WARNING
166         # The user must choose non-overlapping keys for params since they
167         # are replaced in the template in arbitrary order.
168         name = template
169         for key, value in params.items():
170             param = cls.get_param_value(value, withIndex=True)
171             if param is None:
172                 return None
173             name = name.replace(key, str(param))
174         return name
175
176
177 class CinderVolumeAttachmentProcessor(HeatProcessor):
178     """ Cinder VolumeAttachment
179     """
180
181     resource_type = "OS::Cinder::VolumeAttachment"
182
183     @classmethod
184     def get_config(cls, resources):
185         """Return a tuple (va_config, va_count)
186         va_config - Hashabledict of Cinder Volume Attachment config
187                     indexed by rid.
188         va_count - dict of attachment counts indexed by rid.
189         """
190         va_count = collections.defaultdict(int)
191         va_config = Hashabledict()
192         for resource in resources.values():
193             resource_type = nested_dict.get(resource, "type")
194             if resource_type == cls.resource_type:
195                 config, rids = cls.get_volume_attachment_config(resource)
196                 for rid in rids:
197                     va_config[rid] = config
198                     va_count[rid] += 1
199         return va_config, va_count
200
201     @classmethod
202     def get_volume_attachment_config(cls, resource):
203         """Returns the cinder volume attachment configuration
204         of `resource` as a tuple (config, rids)
205         where:
206         - config is a Hashabledict whose keys are the keys of the
207             properties of resource, and whose values are the
208             corresponding property values (nova server resource ids)
209             replaced with the vm-type they reference.
210         - rids is the set of nova server resource ids referenced by
211             the property values.
212         """
213         config = Hashabledict()
214         rids = set()
215         for key, value in (resource.get("properties") or {}).items():
216             rid = cls.get_resource_or_param_value(value)
217             if rid:
218                 name, match = NovaServerProcessor.get_rid_match_tuple(rid)
219                 if name == "server":
220                     vm_type = match.groupdict()["vm_type"]
221                     config[key] = vm_type
222                     rids.add(rid)
223         return config, rids
224
225
226 class ContrailV2NetworkFlavorBaseProcessor(HeatProcessor):
227     """ContrailV2 objects which have network_flavor
228     """
229
230     network_flavor_external = "external"
231     network_flavor_internal = "internal"
232     network_flavor_subint = "subinterface"
233
234     @classmethod
235     def get_network_flavor(cls, resource):
236         """Return the network flavor of resource, one of
237         "internal" - get_resource, or get_param contains _int_
238         "subint" - get_param contains _subint_
239         "external" - otherwise
240         None - no parameters found to decide the flavor.
241
242         resource.properties.virtual_network_refs should be a list.
243         All the parameters in the list should have the same "flavor"
244         so the flavor is determined from the first item.
245         """
246         network_flavor = None
247         network_refs = nested_dict.get(resource, "properties", "virtual_network_refs")
248         if network_refs and isinstance(network_refs, list):
249             param = network_refs[0]
250             if isinstance(param, dict):
251                 if "get_resource" in param:
252                     network_flavor = cls.network_flavor_internal
253                 else:
254                     p = param.get("get_param")
255                     if isinstance(p, str):
256                         if "_int_" in p or p.startswith("int_"):
257                             network_flavor = cls.network_flavor_internal
258                         elif "_subint_" in p:
259                             network_flavor = cls.network_flavor_subint
260                         else:
261                             network_flavor = cls.network_flavor_external
262         return network_flavor
263
264
265 class ContrailV2InstanceIpProcessor(ContrailV2NetworkFlavorBaseProcessor):
266     """ ContrailV2 InstanceIp
267     """
268
269     resource_type = "OS::ContrailV2::InstanceIp"
270     re_rids = collections.OrderedDict(
271         [
272             (
273                 "internal",
274                 _get_regex(
275                     r"(?P<vm_type>.+)"
276                     r"_(?P<vm_type_index>\d+)"
277                     r"_int"
278                     r"_(?P<network_role>.+)"
279                     r"_vmi"
280                     r"_(?P<vmi_index>\d+)"
281                     r"(_v6)?"
282                     r"_IP"
283                     r"_(?P<index>\d+)"
284                     r"$"
285                 ),
286             ),
287             (
288                 "subinterface",
289                 _get_regex(
290                     r"(?P<vm_type>.+)"
291                     r"_(?P<vm_type_index>\d+)"
292                     r"_subint"
293                     r"_(?P<network_role>.+)"
294                     r"_vmi"
295                     r"_(?P<vmi_index>\d+)"
296                     r"(_v6)?"
297                     r"_IP"
298                     r"_(?P<index>\d+)"
299                     r"$"
300                 ),
301             ),
302             (
303                 "external",
304                 _get_regex(
305                     r"(?P<vm_type>.+)"
306                     r"_(?P<vm_type_index>\d+)"
307                     r"_(?P<network_role>.+)"
308                     r"_vmi"
309                     r"_(?P<vmi_index>\d+)"
310                     r"(_v6)?"
311                     r"_IP"
312                     r"_(?P<index>\d+)"
313                     r"$"
314                 ),
315             ),
316         ]
317     )
318
319
320 class ContrailV2InterfaceRouteTableProcessor(HeatProcessor):
321     """ ContrailV2 InterfaceRouteTable
322     """
323
324     resource_type = "OS::ContrailV2::InterfaceRouteTable"
325
326
327 class ContrailV2NetworkIpamProcessor(HeatProcessor):
328     """ ContrailV2 NetworkIpam
329     """
330
331     resource_type = "OS::ContrailV2::NetworkIpam"
332
333
334 class ContrailV2PortTupleProcessor(HeatProcessor):
335     """ ContrailV2 PortTuple
336     """
337
338     resource_type = "OS::ContrailV2::PortTuple"
339
340
341 class ContrailV2ServiceHealthCheckProcessor(HeatProcessor):
342     """ ContrailV2 ServiceHealthCheck
343     """
344
345     resource_type = "OS::ContrailV2::ServiceHealthCheck"
346
347
348 class ContrailV2ServiceInstanceProcessor(HeatProcessor):
349     """ ContrailV2 ServiceInstance
350     """
351
352     resource_type = "OS::ContrailV2::ServiceInstance"
353
354
355 class ContrailV2ServiceInstanceIpProcessor(HeatProcessor):
356     """ ContrailV2 ServiceInstanceIp
357     """
358
359     resource_type = "OS::ContrailV2::ServiceInstanceIp"
360
361
362 class ContrailV2ServiceTemplateProcessor(HeatProcessor):
363     """ ContrailV2 ServiceTemplate
364     """
365
366     resource_type = "OS::ContrailV2::ServiceTemplate"
367
368
369 class ContrailV2VirtualMachineInterfaceProcessor(ContrailV2NetworkFlavorBaseProcessor):
370     """ ContrailV2 Virtual Machine Interface resource
371     """
372
373     resource_type = "OS::ContrailV2::VirtualMachineInterface"
374     re_rids = collections.OrderedDict(
375         [
376             (
377                 "internal",
378                 _get_regex(
379                     r"(?P<vm_type>.+)"
380                     r"_(?P<vm_type_index>\d+)"
381                     r"_int"
382                     r"_(?P<network_role>.+)"
383                     r"_vmi"
384                     r"_(?P<vmi_index>\d+)"
385                     r"$"
386                 ),
387             ),
388             (
389                 "subinterface",
390                 _get_regex(
391                     r"(?P<vm_type>.+)"
392                     r"_(?P<vm_type_index>\d+)"
393                     r"_subint"
394                     r"_(?P<network_role>.+)"
395                     r"_vmi"
396                     r"_(?P<vmi_index>\d+)"
397                     r"$"
398                 ),
399             ),
400             (
401                 "external",
402                 _get_regex(
403                     r"(?P<vm_type>.+)"
404                     r"_(?P<vm_type_index>\d+)"
405                     r"_(?P<network_role>.+)"
406                     r"_vmi"
407                     r"_(?P<vmi_index>\d+)"
408                     r"$"
409                 ),
410             ),
411         ]
412     )
413
414
415 class ContrailV2VirtualNetworkProcessor(HeatProcessor):
416     """ ContrailV2 VirtualNetwork
417     """
418
419     resource_type = "OS::ContrailV2::VirtualNetwork"
420     re_rids = collections.OrderedDict(
421         [
422             ("network", _get_regex(r"int" r"_(?P<network_role>.+)" r"_network" r"$")),
423             # ("rvn", _get_regex(r"int" r"_(?P<network_role>.+)" r"_RVN" r"$")),
424         ]
425     )
426
427
428 class HeatResourceGroupProcessor(HeatProcessor):
429     """ Heat ResourceGroup
430     """
431
432     resource_type = "OS::Heat::ResourceGroup"
433     re_rids = collections.OrderedDict(
434         [
435             (
436                 "subint",
437                 _get_regex(
438                     r"(?P<vm_type>.+)"
439                     r"_(?P<vm_type_index>\d+)"
440                     r"_subint"
441                     r"_(?P<network_role>.+)"
442                     r"_port_(?P<port_index>\d+)"
443                     r"_subinterfaces"
444                     r"$"
445                 ),
446             )
447         ]
448     )
449
450
451 class NeutronNetProcessor(HeatProcessor):
452     """ Neutron Net resource
453     """
454
455     resource_type = "OS::Neutron::Net"
456     re_rids = collections.OrderedDict(
457         [("network", _get_regex(r"int" r"_(?P<network_role>.+)" r"_network" r"$"))]
458     )
459
460
461 class NeutronPortProcessor(HeatProcessor):
462     """ Neutron Port resource
463     """
464
465     resource_type = "OS::Neutron::Port"
466     re_rids = collections.OrderedDict(
467         [
468             (
469                 "internal",
470                 _get_regex(
471                     r"(?P<vm_type>.+)"
472                     r"_(?P<vm_type_index>\d+)"
473                     r"_int"
474                     r"_(?P<network_role>.+)"
475                     r"_port_(?P<port_index>\d+)"
476                     r"$"
477                 ),
478             ),
479             (
480                 "external",
481                 _get_regex(
482                     r"(?P<vm_type>.+)"
483                     r"_(?P<vm_type_index>\d+)"
484                     r"_(?P<network_role>.+)"
485                     r"_port_(?P<port_index>\d+)"
486                     r"$"
487                 ),
488             ),
489         ]
490     )
491
492     @classmethod
493     def uses_sr_iov(cls, resource):
494         """Returns True/False as `resource` is/not
495         An OS::Nova:Port with the property binding:vnic_type
496         """
497         resource_properties = nested_dict.get(resource, "properties", default={})
498         if (
499             nested_dict.get(resource, "type") == cls.resource_type
500             and resource_properties.get("binding:vnic_type", "") == "direct"
501         ):
502             return True
503
504         return False
505
506
507 class NovaServerProcessor(HeatProcessor):
508     """ Nova Server resource
509     """
510
511     resource_type = "OS::Nova::Server"
512     re_rids = collections.OrderedDict(
513         [
514             (
515                 "server",
516                 _get_regex(r"(?P<vm_type>.+)" r"_server_(?P<vm_type_index>\d+)" r"$"),
517             )
518         ]
519     )
520
521     @classmethod
522     def get_flavor(cls, resource):
523         """Return the flavor property of `resource`
524         """
525         return cls.get_param_value(nested_dict.get(resource, "properties", "flavor"))
526
527     @classmethod
528     def get_image(cls, resource):
529         """Return the image property of `resource`
530         """
531         return cls.get_param_value(nested_dict.get(resource, "properties", "image"))
532
533     @classmethod
534     def get_network(cls, resource):
535         """Return the network configuration of `resource` as a
536         frozenset of network-roles.
537         """
538         network = set()
539         networks = nested_dict.get(resource, "properties", "networks")
540         if isinstance(networks, list):
541             for port in networks:
542                 value = cls.get_resource_or_param_value(nested_dict.get(port, "port"))
543                 name, match = NeutronPortProcessor.get_rid_match_tuple(value)
544                 if name:
545                     network_role = match.groupdict().get("network_role")
546                     if network_role:
547                         network.add(network_role)
548         return frozenset(network)
549
550     @classmethod
551     def get_vm_class(cls, resource):
552         """Return the vm_class of `resource`, a Hashabledict (of
553         hashable values) whose keys are only the required keys.
554         Return empty Hashabledict if `resource` is not a NovaServer.
555         """
556         vm_class = Hashabledict()
557         resource_type = nested_dict.get(resource, "type")
558         if resource_type == cls.resource_type:
559             d = dict(
560                 flavor=cls.get_flavor(resource),
561                 image=cls.get_image(resource),
562                 networks=cls.get_network(resource),
563             )
564             if all(d.values()):
565                 vm_class.update(d)
566         return vm_class
567
568
569 class Heat(object):
570     """A Heat template.
571     filepath - absolute path to template file.
572     envpath - absolute path to environmnt file.
573     """
574
575     type_bool = "boolean"
576     type_boolean = "boolean"
577     type_cdl = "comma_delimited_list"
578     type_comma_delimited_list = "comma_delimited_list"
579     type_json = "json"
580     type_num = "number"
581     type_number = "number"
582     type_str = "string"
583     type_string = "string"
584
585     def __init__(self, filepath=None, envpath=None):
586         self.filepath = None
587         self.basename = None
588         self.dirname = None
589         self.yml = None
590         self.heat_template_version = None
591         self.description = None
592         self.parameter_groups = None
593         self.parameters = None
594         self.resources = None
595         self.outputs = None
596         self.conditions = None
597         if filepath:
598             self.load(filepath)
599         self.env = None
600         if envpath:
601             self.load_env(envpath)
602         self.heat_processors = self.get_heat_processors()
603
604     @property
605     def contrail_resources(self):
606         """This attribute is a dict of Contrail resources.
607         """
608         return self.get_resource_by_type(
609             resource_type=ContrailV2VirtualMachineInterfaceProcessor.resource_type
610         )
611
612     def get_all_resources(self, base_dir=None, count=1):
613         """
614         Like ``resources``, but this returns all the resources definitions
615         defined in the template, resource groups, and nested YAML files.
616
617         A special variable will be added to all resource properties (__count__).
618         This will normally be 1, but if the resource is generated by a
619         ResourceGroup **and** an env file is present, then the count will be
620         the value from the env file (assuming this follows standard VNF Heat
621         Guidelines)
622         """
623         base_dir = base_dir or self.dirname
624         resources = {}
625         for r_id, r_data in self.resources.items():
626             r_data["__count__"] = count
627             resources[r_id] = r_data
628             resource = Resource(r_id, r_data)
629             if resource.is_nested():
630                 nested_count = resource.get_count(self.env)
631                 nested = Heat(os.path.join(base_dir, resource.get_nested_filename()))
632                 nested_resources = nested.get_all_resources(count=nested_count)
633                 resources.update(nested_resources)
634         return resources
635
636     @staticmethod
637     def get_heat_processors():
638         """Return a dict, key is resource_type, value is the
639         HeatProcessor subclass whose resource_type is the key.
640         """
641         return _HEAT_PROCESSORS
642
643     def get_resource_by_type(self, resource_type, all_resources=False):
644         """Return dict of resources whose type is `resource_type`.
645         key is resource_id, value is resource.
646         """
647         resources = self.get_all_resources() if all_resources else self.resources
648         return {
649             rid: resource
650             for rid, resource in resources.items()
651             if self.nested_get(resource, "type") == resource_type
652         }
653
654     def get_rid_match_tuple(self, rid, resource_type):
655         """return get_rid_match_tuple(rid) called on the class
656         corresponding to the given resource_type.
657         """
658         processor = self.heat_processors.get(resource_type, HeatProcessor)
659         return processor.get_rid_match_tuple(rid)
660
661     def get_vm_type(self, rid, resource=None):
662         """return the vm_type
663         """
664         if resource is None:
665             resource = self
666         resource_type = self.nested_get(resource, "type")
667         match = self.get_rid_match_tuple(rid, resource_type)[1]
668         vm_type = match.groupdict().get("vm_type") if match else None
669         return vm_type
670
671     def load(self, filepath):
672         """Load the Heat template given a filepath.
673         """
674         self.filepath = filepath
675         self.basename = os.path.basename(self.filepath)
676         self.dirname = os.path.dirname(self.filepath)
677         with open(self.filepath) as fi:
678             self.yml = yaml.load(fi)
679         self.heat_template_version = self.yml.get("heat_template_version", None)
680         self.description = self.yml.get("description", "")
681         self.parameter_groups = self.yml.get("parameter_groups") or {}
682         self.parameters = self.yml.get("parameters") or {}
683         self.resources = self.yml.get("resources") or {}
684         self.outputs = self.yml.get("outputs") or {}
685         self.conditions = self.yml.get("conditions") or {}
686
687     def load_env(self, envpath):
688         """Load the Environment template given a envpath.
689         """
690         self.env = Env(filepath=envpath)
691
692     @staticmethod
693     def nested_get(dic, *keys, **kwargs):
694         """make utils.nested_dict.get available as a class method.
695         """
696         return nested_dict.get(dic, *keys, **kwargs)
697
698     @property
699     def neutron_port_resources(self):
700         """This attribute is a dict of Neutron Ports
701         """
702         return self.get_resource_by_type(
703             resource_type=NeutronPortProcessor.resource_type
704         )
705
706     @property
707     def nova_server_resources(self):
708         """This attribute is a dict of Nova Servers
709         """
710         return self.get_resource_by_type(
711             resource_type=NovaServerProcessor.resource_type
712         )
713
714     @staticmethod
715     def part_is_in_name(part, name):
716         """
717         Return True if any of
718         - name starts with part + '_'
719         - name contains '_' + part + '_'
720         - name ends with '_' + part
721         False otherwise
722         """
723         return bool(
724             re.search("(^(%(x)s)_)|(_(%(x)s)_)|(_(%(x)s)$)" % dict(x=part), name)
725         )
726
727
728 class Env(Heat):
729     """An Environment file
730     """
731
732     pass
733
734
735 class Resource(object):
736     """A Resource
737     """
738
739     def __init__(self, resource_id=None, resource=None):
740         self.resource_id = resource_id or ""
741         self.resource = resource or {}
742         self.properties = self.resource.get("properties", {})
743         self.resource_type = self.resource.get("type", "")
744
745     @staticmethod
746     def get_index_var(resource):
747         """Return the index_var for this resource.
748         """
749         index_var = nested_dict.get(resource, "properties", "index_var") or "index"
750         return index_var
751
752     def get_nested_filename(self):
753         """Returns the filename of the nested YAML file if the
754         resource is a nested YAML or ResourceDef, returns '' otherwise."""
755         typ = self.resource.get("type", "")
756         if typ == "OS::Heat::ResourceGroup":
757             rd = nested_dict.get(self.resource, "properties", "resource_def")
758             typ = rd.get("type", "") if rd else ""
759         ext = os.path.splitext(typ)[1]
760         ext = ext.lower()
761         if ext == ".yml" or ext == ".yaml":
762             return typ
763         else:
764             return ""
765
766     def get_nested_properties(self):
767         """
768         Returns {} if not nested
769         Returns resource: properties if nested
770         Returns resource: properties: resource_def: properties if RG
771         """
772         if not bool(self.get_nested_filename()):
773             return {}
774         elif self.resource_type == "OS::Heat::ResourceGroup":
775             return nested_dict.get(
776                 self.properties, "resource_def", "properties", default={}
777             )
778         else:
779             return self.properties
780
781     def get_count(self, env):
782         if self.resource_type == "OS::Heat::ResourceGroup":
783             if not env:
784                 return 1
785             env_params = env.parameters
786             count_param = get_param(self.properties["count"])
787             count_value = env_params.get(count_param) if count_param else 1
788             try:
789                 return int(count_value)
790             except (ValueError, TypeError):
791                 print(
792                     (
793                         "WARNING: Invalid value for count parameter {}. Expected "
794                         "an integer, but got {}. Defaulting to 1"
795                     ).format(count_param, count_value)
796                 )
797         return 1
798
799     @property
800     def depends_on(self):
801         """
802         Returns the list of resources this resource depends on.  Always
803         returns a list.
804
805         :return: list of all resource IDs this resource depends on.  If none,
806                  then returns an empty list
807         """
808         parents = self.resource.get("depends_on", [])
809         return parents if isinstance(parents, list) else [parents]
810
811     def is_nested(self):
812         """Returns True if the resource represents a Nested YAML resource
813         using either type: {filename} or ResourceGroup -> resource_def"""
814         return bool(self.get_nested_filename())
815
816     def get_nested_yaml(self, base_dir):
817         """If the resource represents a Nested YAML resource, then it
818         returns the loaded YAML.  If the resource is not nested or the
819         file cannot be found, then an empty dict is returned"""
820         filename = self.get_nested_filename()
821         if filename:
822             file_path = os.path.join(base_dir, filename)
823             return load_yaml(file_path) if os.path.exists(file_path) else {}
824         else:
825             return {}
826
827
828 def get_all_resources(yaml_files):
829     """Return a dict, resource id: resource
830     of the union of resources across all files.
831     """
832     resources = {}
833     for heat_template in yaml_files:
834         heat = Heat(filepath=heat_template)
835         dirname = os.path.dirname(heat_template)
836         resources.update(heat.get_all_resources(dirname))
837     return resources
838
839
840 def _get_heat_processors():
841     """Introspect this module and return a
842     dict of all HeatProcessor sub-classes with a (True) resource_type.
843     Key is the resource_type, value is the corrresponding class.
844     """
845     mod_classes = inspect.getmembers(sys.modules[__name__], inspect.isclass)
846     heat_processors = {
847         c.resource_type: c
848         for _, c in mod_classes
849         if issubclass(c, HeatProcessor) and c.resource_type
850     }
851     return heat_processors
852
853
854 _HEAT_PROCESSORS = _get_heat_processors()