2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2019 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
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
13 # http://www.apache.org/licenses/LICENSE-2.0
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.
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
28 # https://creativecommons.org/licenses/by/4.0/
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.
36 # ============LICENSE_END============================================
40 from abc import ABC, abstractmethod
41 from collections import OrderedDict
46 def represent_ordered_dict(dumper, data):
49 for item_key, item_value in data.items():
50 node_key = dumper.represent_data(item_key)
51 node_value = dumper.represent_data(item_value)
53 value.append((node_key, node_value))
55 return yaml.nodes.MappingNode(u"tag:yaml.org,2002:map", value)
58 def get_json_template(template_dir, template_name):
59 template_name = template_name + ".json"
60 with open(os.path.join(template_dir, template_name)) as f:
61 return json.loads(f.read())
64 def get_or_create_template(template_dir, key, value, sequence, template_name):
66 Search a sequence of dicts where a given key matches value. If
67 found, then it returns that item. If not, then it loads the
68 template identified by template_name, adds it ot the sequence, and
72 if item[key] == value:
74 new_template = get_json_template(template_dir, template_name)
75 sequence.append(new_template)
79 def yield_by_count(sequence):
81 Iterates through sequence and yields each item according to its __count__
82 attribute. If an item has a __count__ of it will be returned 3 times
83 before advancing to the next item in the sequence.
85 :param sequence: sequence of dicts (must contain __count__)
86 :returns: generator of tuple key, value pairs
88 for key, value in sequence.items():
89 for i in range(value["__count__"]):
95 Optionally used by the preload generator to wrap items in the preload
96 that need to be replaced by end users
99 return "VALUE FOR: {}".format(param) if param else ""
102 class AbstractPreloadGenerator(ABC):
104 All preload generators must inherit from this class and implement the
107 Preload generators are automatically discovered at runtime via a plugin
108 architecture. The system path is scanned looking for modules with the name
109 preload_*, then all non-abstract classes that inherit from AbstractPreloadGenerator
110 are registered as preload plugins
113 :param vnf: Instance of Vnf that contains the preload data
114 :param base_output_dir: Base directory to house the preloads. All preloads
115 must be written to a subdirectory under this directory
118 def __init__(self, vnf, base_output_dir, preload_env):
119 self.preload_env = preload_env
121 self.current_module = None
122 self.current_module_env = {}
123 self.base_output_dir = base_output_dir
125 self.module_incomplete = False
129 def format_name(cls):
131 String name to identify the format (ex: VN-API, GR-API)
133 raise NotImplementedError()
137 def output_sub_dir(cls):
139 String sub-directory name that will appear under ``base_output_dir``
141 raise NotImplementedError()
145 def supports_output_passing(cls):
147 Some preload methods allow automatically mapping output parameters in the
148 base module to the input parameter of other modules. This means these
149 that the incremental modules do not need these base module outputs in their
152 At this time, VNF-API does not support output parameter passing, but
155 If this is true, then the generator will call Vnf#filter_output_params
156 after the preload module for the base module has been created
158 raise NotImplementedError()
161 def generate_module(self, module, output_dir):
163 Create the preloads and write them to ``output_dir``. This
164 method is responsible for generating the content of the preload and
165 writing the file to disk.
167 raise NotImplementedError()
170 # handle the base module first
171 print("\nGenerating {} preloads".format(self.format_name()))
172 if self.vnf.base_module:
173 self.generate_environments(self.vnf.base_module)
174 if self.supports_output_passing():
175 self.vnf.filter_base_outputs()
176 for mod in self.vnf.incremental_modules:
177 self.generate_environments(mod)
179 def replace(self, param_name, alt_message=None, single=False):
180 value = self.get_param(param_name, single)
181 value = None if value == "CHANGEME" else value
185 self.module_incomplete = True
186 return alt_message or replace(param_name)
188 def start_module(self, module, env):
189 """Initialize/reset the environment for the module"""
190 self.current_module = module
191 self.current_module_env = env
192 self.module_incomplete = False
195 def generate_environments(self, module):
197 Generate a preload for the given module in all available environments
198 in the ``self.preload_env``. This will invoke the abstract
199 generate_module once for each available environment **and** an
200 empty environment to create a blank template.
202 :param module: module to generate for
204 print("\nGenerating Preloads for {}".format(module))
206 print("... generating blank template")
207 self.start_module(module, {})
208 blank_preload_dir = self.make_preload_dir(self.base_output_dir)
209 self.generate_module(module, blank_preload_dir)
210 self.generate_preload_env(module, blank_preload_dir)
212 for env in self.preload_env.environments:
213 output_dir = self.make_preload_dir(env.base_dir / "preloads")
215 "... generating preload for env ({}) to {}".format(
219 self.start_module(module, env.get_module(module.label))
220 self.generate_module(module, output_dir)
222 def make_preload_dir(self, base_dir):
223 path = os.path.join(base_dir, self.output_sub_dir())
224 if not os.path.exists(path):
225 os.makedirs(path, exist_ok=True)
229 def generate_preload_env(module, blank_preload_dir):
231 Create a .env template suitable for completing and using for
232 preload generation from env files.
234 yaml.add_representer(OrderedDict, represent_ordered_dict)
235 output_dir = os.path.join(blank_preload_dir, "preload_env")
236 env_file = os.path.join(output_dir, "{}.env".format(module.vnf_name))
237 defaults_file = os.path.join(output_dir, "defaults.yaml")
238 if not os.path.exists(output_dir):
239 os.makedirs(output_dir, exist_ok=True)
240 with open(env_file, "w") as f:
241 yaml.dump(module.env_template, f)
242 if not os.path.exists(defaults_file):
243 with open(defaults_file, "w") as f:
244 yaml.dump({"vnf_name": "CHANGEME"}, f)
246 def get_param(self, param_name, single):
248 Retrieves the value for the given param if it exists. If requesting a
249 single item, and the parameter is tied to a list then only one item from
250 the list will be returned. For each subsequent call with the same parameter
251 it will iterate/rotate through the values in that list. If single is False
252 then the full list will be returned.
254 :param param_name: name of the parameter
255 :param single: If True returns single value from lists otherwises the full
256 list. This has no effect on non-list values
258 value = self.env_cache.get(param_name)
260 value = self.current_module_env.get(param_name)
261 if isinstance(value, list):
264 self.env_cache[param_name] = value
265 if value and single and isinstance(value, list):