2 ###############################################################################
3 # Copyright (c) 2015 Ericsson AB and others.
4 # jonas.bjurel@ericsson.com
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9 ###############################################################################
11 ###############################################################################
13 # This script constructs the final deployment dea.yaml and dha.yaml files
14 # The dea.yaml get's constructed from (in reverse priority):
17 # 3) deployment-scenario dea-override-config section
19 # The dha.yaml get's constructed from (in reverse priority):
21 # 2) deployment-scenario dha-override-config section
22 ###############################################################################
34 from functools import reduce
35 from operator import or_
42 create_dir_if_not_exists,
49 def parse_arguments():
50 parser = ArgParser(prog='python %s' % __file__)
51 parser.add_argument('-dha', dest='dha_uri', action='store',
53 help='dha configuration file FQDN URI',
55 parser.add_argument('-deab', dest='dea_base_uri', action='store',
57 help='dea base configuration FQDN URI',
59 parser.add_argument('-deao', dest='dea_pod_override_uri',
62 help='dea POD override configuration FQDN URI',
64 parser.add_argument('-scenario-base-uri',
65 dest='scenario_base_uri',
68 help='Deployment scenario base directory URI',
70 parser.add_argument('-scenario', dest='scenario', action='store',
72 help=('Deployment scenario short-name (priority),'
73 'or base file name (in the absense of a'
74 'shortname defenition)'),
77 parser.add_argument('-plugins', dest='plugins_uri', action='store',
79 help='Plugin configurations directory URI',
81 parser.add_argument('-output', dest='output_path', action='store',
83 help='Local path for resulting output configuration files',
85 args = parser.parse_args()
87 kwargs = {'dha_uri': args.dha_uri,
88 'dea_base_uri': args.dea_base_uri,
89 'dea_pod_override_uri': args.dea_pod_override_uri,
90 'scenario_base_uri': args.scenario_base_uri,
91 'scenario': args.scenario,
92 'plugins_uri': args.plugins_uri,
93 'output_path': args.output_path}
100 print('%(red)s WARNING: %(msg)s %(NC)s' % {'red': red,
106 represent_dict_order = lambda self, data: self.represent_mapping('tag:yaml.org,2002:map', data.items())
107 yaml.add_representer(collections.OrderedDict, represent_dict_order)
111 response = urllib2.urlopen(uri)
112 data = response.read()
113 sha1 = hashlib.sha1()
115 return sha1.hexdigest()
118 def merge_fuel_plugin_version_list(list1, list2):
120 # When the plugin version in not there in list1 it will
123 plugin_version = e_l1.get('metadata', {}).get('plugin_version')
124 plugin_version_found = False
126 if plugin_version == e_l2.get('metadata', {}).get('plugin_version'):
127 final_list.append(dict(merge_dicts(e_l1, e_l2)))
128 plugin_version_found = True
129 if not plugin_version_found:
130 final_list.append(e_l1)
134 def merge_networks(list_1, list_2):
135 new_nets = {x.get('name'): x for x in list_2}
137 return [new_nets.get(net.get('name'), net) for net in list_1]
141 def merge_dicts(dict1, dict2):
142 for k in set(dict1).union(dict2):
143 if k in dict1 and k in dict2:
144 if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
145 yield (k, dict(merge_dicts(dict1[k], dict2[k])))
147 if isinstance(dict1[k], list) and isinstance(dict2[k], list):
150 merge_fuel_plugin_version_list(dict1[k], dict2[k]))
154 merge_networks(dict1[k], dict2[k]))
157 # If one of the values is not a dict nor a list,
158 # you can't continue merging it.
159 # Value from second dict overrides one in first if exists.
167 kwargs = parse_arguments()
169 # Generate final dea.yaml by merging following config files/fragments in revers priority order:
170 # "dea-base", "dea-pod-override", "deplyment-scenario/module-config-override"
171 # and "deployment-scenario/dea-override"
172 print('Generating final dea.yaml configuration....')
174 # Fetch dea-base, extract and purge meta-data
175 print('Parsing dea-base from: ' + kwargs["dea_base_uri"] + "....")
176 response = urllib2.urlopen(kwargs["dea_base_uri"])
177 dea_base_conf = yaml.load(response.read())
178 dea_base_title = dea_base_conf['dea-base-config-metadata']['title']
179 dea_base_version = dea_base_conf['dea-base-config-metadata']['version']
180 dea_base_creation = dea_base_conf['dea-base-config-metadata']['created']
181 dea_base_sha = sha_uri(kwargs["dea_base_uri"])
182 dea_base_comment = dea_base_conf['dea-base-config-metadata']['comment']
183 dea_base_conf.pop('dea-base-config-metadata')
184 final_dea_conf = dea_base_conf
185 dea_pod_override_nodes = None
187 # Fetch dea-pod-override, extract and purge meta-data, merge with previous dea data structure
188 print('Parsing the dea-pod-override from: ' + kwargs["dea_pod_override_uri"] + "....")
189 response = urllib2.urlopen(kwargs["dea_pod_override_uri"])
190 dea_pod_override_conf = yaml.load(response.read())
191 if dea_pod_override_conf:
192 dea_pod_title = dea_pod_override_conf['dea-pod-override-config-metadata']['title']
193 dea_pod_version = dea_pod_override_conf['dea-pod-override-config-metadata']['version']
194 dea_pod_creation = dea_pod_override_conf['dea-pod-override-config-metadata']['created']
195 dea_pod_sha = sha_uri(kwargs["dea_pod_override_uri"])
196 dea_pod_comment = dea_pod_override_conf['dea-pod-override-config-metadata']['comment']
197 print('Merging dea-base and dea-pod-override configuration ....')
198 dea_pod_override_conf.pop('dea-pod-override-config-metadata')
199 # Copy the list of original nodes, which holds info on their transformations
200 if 'nodes' in dea_pod_override_conf:
201 dea_pod_override_nodes = list(dea_pod_override_conf['nodes'])
202 if dea_pod_override_conf:
203 final_dea_conf = dict(merge_dicts(final_dea_conf, dea_pod_override_conf))
205 # Fetch deployment-scenario, extract and purge meta-data, merge deployment-scenario/
206 # dea-override-configith previous dea data structure
207 print('Parsing deployment-scenario from: ' + kwargs["scenario"] + "....")
209 response = urllib2.urlopen(kwargs["scenario_base_uri"] + "/scenario.yaml")
210 scenario_short_translation_conf = yaml.load(response.read())
211 if kwargs["scenario"] in scenario_short_translation_conf:
212 scenario_uri = (kwargs["scenario_base_uri"]
214 + scenario_short_translation_conf[kwargs["scenario"]]['configfile'])
216 scenario_uri = kwargs["scenario_base_uri"] + "/" + kwargs["scenario"]
217 response = urllib2.urlopen(scenario_uri)
218 deploy_scenario_conf = yaml.load(response.read())
220 if deploy_scenario_conf:
221 deploy_scenario_title = deploy_scenario_conf['deployment-scenario-metadata']['title']
222 deploy_scenario_version = deploy_scenario_conf['deployment-scenario-metadata']['version']
223 deploy_scenario_creation = deploy_scenario_conf['deployment-scenario-metadata']['created']
224 deploy_scenario_sha = sha_uri(scenario_uri)
225 deploy_scenario_comment = deploy_scenario_conf['deployment-scenario-metadata']['comment']
226 deploy_scenario_conf.pop('deployment-scenario-metadata')
228 print("Deployment scenario file not found or is empty")
229 print("Cannot continue, exiting ....")
232 dea_scenario_override_conf = deploy_scenario_conf["dea-override-config"]
233 if dea_scenario_override_conf:
234 print('Merging dea-base-, dea-pod-override- and deployment-scenario '
235 'configuration into final dea.yaml configuration....')
236 final_dea_conf = dict(merge_dicts(final_dea_conf, dea_scenario_override_conf))
238 # Fetch plugin-configuration configuration files, extract and purge meta-data,
239 # merge/append with previous dea data structure, override plugin-configuration with
240 # deploy-scenario/module-config-override
245 module_creations = []
248 if deploy_scenario_conf["stack-extensions"]:
249 for module in deploy_scenario_conf["stack-extensions"]:
250 print('Loading configuration for module: '
252 + ' and merging it to final dea.yaml configuration....')
253 response = urllib2.urlopen(kwargs["plugins_uri"]
255 + module["module-config-name"]
257 + module["module-config-version"]
259 module_conf = yaml.load(response.read())
260 modules.append(module["module"])
261 module_uris.append(kwargs["plugins_uri"]
263 + module["module-config-name"]
265 + module["module-config-version"]
267 module_titles.append(str(module_conf['plugin-config-metadata']['title']))
268 module_versions.append(str(module_conf['plugin-config-metadata']['version']))
269 module_creations.append(str(module_conf['plugin-config-metadata']['created']))
270 module_shas.append(sha_uri(kwargs["plugins_uri"]
272 + module["module-config-name"]
274 + module["module-config-version"]
276 module_comments.append(str(module_conf['plugin-config-metadata']['comment']))
277 module_conf.pop('plugin-config-metadata')
278 final_dea_conf['settings']['editable'].update(module_conf)
279 scenario_module_override_conf = module.get('module-config-override')
280 if scenario_module_override_conf:
281 dea_scenario_module_override_conf = {}
282 dea_scenario_module_override_conf['settings'] = {}
283 dea_scenario_module_override_conf['settings']['editable'] = {}
284 dea_scenario_module_override_conf['settings']['editable'][module["module"]] = scenario_module_override_conf
285 final_dea_conf = dict(merge_dicts(final_dea_conf, dea_scenario_module_override_conf))
287 def get_node_ifaces_and_trans(nodes, nid):
289 if node['id'] == nid:
290 if 'transformations' in node and 'interfaces' in node:
291 return (node['interfaces'], node['transformations'])
297 if dea_pod_override_nodes:
298 for node in final_dea_conf['nodes']:
299 data = get_node_ifaces_and_trans(dea_pod_override_nodes, node['id'])
301 print ("Honoring original interfaces and transformations for "
302 "node %d to %s, %s" % (node['id'], data[0], data[1]))
303 node['interfaces'] = data[0]
304 node['transformations'] = data[1]
306 # Dump final dea.yaml including configuration management meta-data to argument provided
308 if not os.path.exists(kwargs["output_path"]):
309 os.makedirs(kwargs["output_path"])
310 print('Dumping final dea.yaml to ' + kwargs["output_path"] + '/dea.yaml....')
311 with open(kwargs["output_path"] + '/dea.yaml', "w") as f:
312 f.write("\n".join([("title: DEA.yaml file automatically generated from the"
313 'configuration files stated in the "configuration-files"'
315 "version: " + str(calendar.timegm(time.gmtime())),
316 "created: " + str(time.strftime("%d/%m/%Y")) + " "
317 + str(time.strftime("%H:%M:%S")),
320 f.write("\n".join(["configuration-files:",
322 " uri: " + kwargs["dea_base_uri"],
323 " title: " + str(dea_base_title),
324 " version: " + str(dea_base_version),
325 " created: " + str(dea_base_creation),
326 " sha1: " + str(dea_base_sha),
327 " comment: " + str(dea_base_comment) + "\n"]))
329 f.write("\n".join([" pod-override:",
330 " uri: " + kwargs["dea_pod_override_uri"],
331 " title: " + str(dea_pod_title),
332 " version: " + str(dea_pod_version),
333 " created: " + str(dea_pod_creation),
334 " sha1: " + str(dea_pod_sha),
335 " comment: " + str(dea_pod_comment) + "\n"]))
337 f.write("\n".join([" deployment-scenario:",
338 " uri: " + str(scenario_uri),
339 " title: " + str(deploy_scenario_title),
340 " version: " + str(deploy_scenario_version),
341 " created: " + str(deploy_scenario_creation),
342 " sha1: " + str(deploy_scenario_sha),
343 " comment: " + str(deploy_scenario_comment) + "\n"]))
345 f.write(" plugin-modules:\n")
346 for k, _ in enumerate(modules):
347 f.write("\n".join([" - module: " + modules[k],
348 " uri: " + module_uris[k],
349 " title: " + module_titles[k],
350 " version: " + module_versions[k],
351 " created: " + module_creations[k],
352 " sha-1: " + module_shas[k],
353 " comment: " + module_comments[k] + "\n"]))
355 yaml.dump(final_dea_conf, f, default_flow_style=False)
357 # Load POD dha and override it with "deployment-scenario/dha-override-config" section
358 print('Generating final dha.yaml configuration....')
359 print('Parsing dha-pod yaml configuration....')
360 response = urllib2.urlopen(kwargs["dha_uri"])
361 dha_pod_conf = yaml.load(response.read())
362 dha_pod_title = dha_pod_conf['dha-pod-config-metadata']['title']
363 dha_pod_version = dha_pod_conf['dha-pod-config-metadata']['version']
364 dha_pod_creation = dha_pod_conf['dha-pod-config-metadata']['created']
365 dha_pod_sha = sha_uri(kwargs["dha_uri"])
366 dha_pod_comment = dha_pod_conf['dha-pod-config-metadata']['comment']
367 dha_pod_conf.pop('dha-pod-config-metadata')
368 final_dha_conf = dha_pod_conf
370 dha_scenario_override_conf = deploy_scenario_conf["dha-override-config"]
371 # Only virtual deploy scenarios can override dha.yaml since there
372 # is no way to programatically override a physical environment:
373 # wireing, IPMI set-up, etc.
374 # For Physical environments, dha.yaml overrides will be silently ignored
375 if dha_scenario_override_conf and (final_dha_conf['adapter'] == 'libvirt'
376 or final_dha_conf['adapter'] == 'esxi'
377 or final_dha_conf['adapter'] == 'vbox'):
378 print('Merging dha-pod and deployment-scenario override information to final dha.yaml configuration....')
379 final_dha_conf = dict(merge_dicts(final_dha_conf, dha_scenario_override_conf))
381 # Dump final dha.yaml to argument provided directory
382 print('Dumping final dha.yaml to ' + kwargs["output_path"] + '/dha.yaml....')
383 with open(kwargs["output_path"] + '/dha.yaml', "w") as f:
384 f.write("\n".join([("title: DHA.yaml file automatically generated from"
385 "the configuration files stated in the"
386 '"configuration-files" fragment below'),
387 "version: " + str(calendar.timegm(time.gmtime())),
388 "created: " + str(time.strftime("%d/%m/%Y")) + " "
389 + str(time.strftime("%H:%M:%S")),
392 f.write("configuration-files:\n")
394 f.write("\n".join([" dha-pod-configuration:",
395 " uri: " + kwargs["dha_uri"],
396 " title: " + str(dha_pod_title),
397 " version: " + str(dha_pod_version),
398 " created: " + str(dha_pod_creation),
399 " sha-1: " + str(dha_pod_sha),
400 " comment: " + str(dha_pod_comment) + "\n"]))
402 f.write("\n".join([" deployment-scenario:",
403 " uri: " + str(scenario_uri),
404 " title: " + str(deploy_scenario_title),
405 " version: " + str(deploy_scenario_version),
406 " created: " + str(deploy_scenario_creation),
407 " sha-1: " + str(deploy_scenario_sha),
408 " comment: " + str(deploy_scenario_comment) + "\n"]))
410 yaml.dump(final_dha_conf, f, default_flow_style=False)