[VVP] Generated completed preload from env files
[vvp/validation-scripts.git] / ice_validator / preload / generator.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 import json
39 import os
40 from abc import ABC, abstractmethod
41
42 import yaml
43
44
45 def get_json_template(template_dir, template_name):
46     template_name = template_name + ".json"
47     with open(os.path.join(template_dir, template_name)) as f:
48         return json.loads(f.read())
49
50
51 def get_or_create_template(template_dir, key, value, sequence, template_name):
52     """
53     Search a sequence of dicts where a given key matches value.  If
54     found, then it returns that item.  If not, then it loads the
55     template identified by template_name, adds it ot the sequence, and
56     returns the template
57     """
58     for item in sequence:
59         if item[key] == value:
60             return item
61     new_template = get_json_template(template_dir, template_name)
62     sequence.append(new_template)
63     return new_template
64
65
66 def yield_by_count(sequence):
67     """
68     Iterates through sequence and yields each item according to its __count__
69     attribute.  If an item has a __count__ of it will be returned 3 times
70     before advancing to the next item in the sequence.
71
72     :param sequence: sequence of dicts (must contain __count__)
73     :returns:        generator of tuple key, value pairs
74     """
75     for key, value in sequence.items():
76         for i in range(value["__count__"]):
77             yield (key, value)
78
79
80 def replace(param):
81     """
82     Optionally used by the preload generator to wrap items in the preload
83     that need to be replaced by end users
84     :param param: p
85     """
86     return "VALUE FOR: {}".format(param) if param else ""
87
88
89 class AbstractPreloadGenerator(ABC):
90     """
91     All preload generators must inherit from this class and implement the
92     abstract methods.
93
94     Preload generators are automatically discovered at runtime via a plugin
95     architecture.  The system path is scanned looking for modules with the name
96     preload_*, then all non-abstract classes that inherit from AbstractPreloadGenerator
97     are registered as preload plugins
98
99     Attributes:
100         :param vnf:             Instance of Vnf that contains the preload data
101         :param base_output_dir: Base directory to house the preloads.  All preloads
102                                 must be written to a subdirectory under this directory
103     """
104
105     def __init__(self, vnf, base_output_dir, preload_env):
106         self.preload_env = preload_env
107         self.vnf = vnf
108         self.current_module = None
109         self.current_module_env = {}
110         self.base_output_dir = base_output_dir
111         self.env_cache = {}
112
113     @classmethod
114     @abstractmethod
115     def format_name(cls):
116         """
117         String name to identify the format (ex: VN-API, GR-API)
118         """
119         raise NotImplementedError()
120
121     @classmethod
122     @abstractmethod
123     def output_sub_dir(cls):
124         """
125         String sub-directory name that will appear under ``base_output_dir``
126         """
127         raise NotImplementedError()
128
129     @classmethod
130     @abstractmethod
131     def supports_output_passing(cls):
132         """
133         Some preload methods allow automatically mapping output parameters in the
134         base module to the input parameter of other modules.  This means these
135         that the incremental modules do not need these base module outputs in their
136         preloads.
137
138         At this time, VNF-API does not support output parameter passing, but
139         GR-API does.
140
141         If this is true, then the generator will call Vnf#filter_output_params
142         after the preload module for the base module has been created
143         """
144         raise NotImplementedError()
145
146     @abstractmethod
147     def generate_module(self, module, output_dir):
148         """
149         Create the preloads and write them to ``output_dir``.  This
150         method is responsible for generating the content of the preload and
151         writing the file to disk.
152         """
153         raise NotImplementedError()
154
155     def generate(self):
156         # handle the base module first
157         print("\nGenerating {} preloads".format(self.format_name()))
158         self.generate_environments(self.vnf.base_module)
159         if self.supports_output_passing():
160             self.vnf.filter_base_outputs()
161         for mod in self.vnf.incremental_modules:
162             self.generate_environments(mod)
163
164     def replace(self, param_name, alt_message=None, single=False):
165         value = self.get_param(param_name, single)
166         if value:
167             return value
168         return alt_message or replace(param_name)
169
170     def generate_environments(self, module):
171         """
172         Generate a preload for the given module in all available environments
173         in the ``self.preload_env``.  This will invoke the abstract
174         generate_module once for each available environment **and** an
175         empty environment to create a blank template.
176
177         :param module:  module to generate for
178         """
179         print("\nGenerating Preloads for {}".format(module))
180         print("-" * 50)
181         print("... generating blank template")
182         self.current_module = module
183         self.current_module_env = {}
184         self.env_cache = {}
185         blank_preload_dir = self.make_preload_dir(self.base_output_dir)
186         self.generate_module(module, blank_preload_dir)
187         self.generate_preload_env(module, blank_preload_dir)
188         if self.preload_env:
189             for env in self.preload_env.environments:
190                 output_dir = self.make_preload_dir(env.base_dir / "preloads")
191                 print(
192                     "... generating preload for env ({}) to {}".format(
193                         env.name, output_dir
194                     )
195                 )
196                 self.env_cache = {}
197                 self.current_module = module
198                 self.current_module_env = env.get_module(module.label)
199                 self.generate_module(module, output_dir)
200         self.current_module = None
201         self.current_module_env = None
202
203     def make_preload_dir(self, base_dir):
204         path = os.path.join(base_dir, self.output_sub_dir())
205         if not os.path.exists(path):
206             os.makedirs(path, exist_ok=True)
207         return path
208
209     def generate_preload_env(self, module, blank_preload_dir):
210         """
211         Create a .env template suitable for completing and using for
212         preload generation from env files.
213         """
214         output_dir = os.path.join(blank_preload_dir, "preload_env")
215         output_file = os.path.join(output_dir, "{}.env".format(module.vnf_name))
216         if not os.path.exists(output_dir):
217             os.makedirs(output_dir, exist_ok=True)
218         with open(output_file, "w") as f:
219             yaml.dump(module.env_template, f)
220
221     def get_param(self, param_name, single):
222         """
223         Retrieves the value for the given param if it exists. If requesting a
224         single item, and the parameter is tied to a list then only one item from
225         the list will be returned.  For each subsequent call with the same parameter
226         it will iterate/rotate through the values in that list.  If single is False
227         then the full list will be returned.
228
229         :param param_name:  name of the parameter
230         :param single:      If True returns single value from lists otherwises the full
231                             list.  This has no effect on non-list values
232         """
233         value = self.env_cache.get(param_name)
234         if not value:
235             value = self.current_module_env.get(param_name)
236             if isinstance(value, list):
237                 value.reverse()
238             self.env_cache[param_name] = value
239         if value and single and isinstance(value, list):
240             return value.pop()
241         else:
242             return value