5351c14619bc089161465990e81725d90a246bdf
[fuel.git] / deploy / deploy-config.py
1 #!/usr/bin/python
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 ###############################################################################
10
11 ###############################################################################
12 # Description
13 # This script constructs the final deployment dea.yaml and dha.yaml files
14 # The dea.yaml get's constructed from (in reverse priority):
15 # 1) dea-base
16 # 2) dea-pod-override
17 # 3) deployment-scenario dea-override-config section
18 #
19 # The dha.yaml get's constructed from (in reverse priority):
20 # 1) pod dha
21 # 2) deployment-scenario dha-override-config section
22 ###############################################################################
23
24 import os
25 import yaml
26 import sys
27 import urllib2
28 import calendar
29 import time
30 import collections
31 import hashlib
32
33 from functools import reduce
34 from operator import or_
35 from common import (
36     log,
37     exec_cmd,
38     err,
39     warn,
40     check_file_exists,
41     create_dir_if_not_exists,
42     delete,
43     check_if_root,
44     ArgParser,
45 )
46
47 def parse_arguments():
48     parser = ArgParser(prog='python %s' % __file__)
49     parser.add_argument('-dha', dest='dha_uri', action='store',
50                         default=False, help='dha configuration file FQDN URI', required=True)
51     parser.add_argument('-deab', dest='dea_base_uri', action='store',
52                         default=False, help='dea base configuration FQDN URI', required=True)
53     parser.add_argument('-deao', dest='dea_pod_override_uri', action='store',
54                         default=False, help='dea POD override configuration FQDN URI',
55                         required=True)
56     parser.add_argument('-scenario-base-uri', dest='scenario_base_uri', action='store',
57                         default=False, help='Deploymen scenario base directory URI',
58                         required=True)
59     parser.add_argument('-scenario', dest='scenario', action='store',
60                         default=False, help='Deploymen scenario short-name (priority), or base file name (in the absense of a shortname defenition)',
61                         required=True)
62
63     parser.add_argument('-plugins', dest='plugins_uri', action='store',
64                         default=False, help='Plugin configurations directory URI',
65                         required=True)
66     parser.add_argument('-output', dest='output_path', action='store',
67                         default=False,
68                         help='Local path for resulting output configuration files',
69                         required=True)
70     args = parser.parse_args()
71     log(args)
72     kwargs = {'dha_uri': args.dha_uri,
73               'dea_base_uri': args.dea_base_uri,
74               'dea_pod_override_uri': args.dea_pod_override_uri,
75               'scenario_base_uri': args.scenario_base_uri,
76               'scenario': args.scenario,
77               'plugins_uri': args.plugins_uri,
78               'output_path': args.output_path}
79     return kwargs
80
81 def setup_yaml():
82   represent_dict_order = lambda self, data:  self.represent_mapping('tag:yaml.org,2002:map', data.items())
83   yaml.add_representer(collections.OrderedDict, represent_dict_order)
84
85 def sha_uri(uri):
86     import hashlib
87     response = urllib2.urlopen(uri)
88     data = response.read()
89     sha1 = hashlib.sha1()
90     sha1.update(data)
91     return sha1.hexdigest()
92
93 def merge_fuel_plugin_version_list(list1, list2):
94     final_list = []
95     # When the plugin version in not there in list1 it will
96     # not be copied
97     for e_l1 in list1:
98         plugin_version = e_l1.get('metadata',
99                                   {'plugin_version', None}).get('plugin_version')
100         plugin_version_found = False
101         for e_l2 in list2:
102             if plugin_version == e_l2.get('metadata',
103                                           {'plugin_version',
104                                            None}).get('plugin_version'):
105                 final_list.append(dict(mergedicts(e_l1, e_l2)))
106                 plugin_version_found = True
107         if not plugin_version_found:
108             final_list.append(e_l1)
109     return final_list
110
111 def merge_lists(list1, list2):
112     if list1 and list2:
113         if isinstance(list1[0], dict):
114             if 'plugin_version' in list1[0].get('metadata', {}):
115                 return merge_fuel_plugin_version_list(list1, list2)
116             else:
117                 raise Exception("Lists with dics inside are not merge able! "
118                                 "You have to code the merge them. "
119                                 "List1: %s; List2: %s"
120                                 % (list1, list2))
121         else:
122             return list2
123     elif list1:
124         return list1
125     else:
126         return list2
127
128 def mergedicts(dict1, dict2):
129     for k in set(dict1.keys()).union(dict2.keys()):
130         if k in dict1 and k in dict2:
131             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
132                 yield (k, dict(mergedicts(dict1[k], dict2[k])))
133             elif isinstance(dict1[k], list) and isinstance(dict2[k], list):
134                 yield (k, merge_lists(dict1[k], dict2[k]))
135             else:
136                 # If one of the values is not a dict nor a list,
137                 # you can't continue merging it.
138                 # Value from second dict overrides one in first and we move on.
139                 yield (k, dict2[k])
140         elif k in dict1:
141             yield (k, dict1[k])
142         else:
143             yield (k, dict2[k])
144
145 setup_yaml()
146 kwargs = parse_arguments()
147
148 # Generate final dea.yaml by merging following config files/fragments in revers priority order:
149 # "dea-base", "dea-pod-override", "deplyment-scenario/module-config-override"
150 # and "deployment-scenario/dea-override"
151 print 'Generating final dea.yaml configuration....'
152
153 # Fetch dea-base, extract and purge meta-data
154 print 'Parsing dea-base from: ' + kwargs["dea_base_uri"] + "...."
155 response = urllib2.urlopen(kwargs["dea_base_uri"])
156 dea_base_conf = yaml.load(response.read())
157 dea_base_title = dea_base_conf['dea-base-config-metadata']['title']
158 dea_base_version = dea_base_conf['dea-base-config-metadata']['version']
159 dea_base_creation = dea_base_conf['dea-base-config-metadata']['created']
160 dea_base_sha = sha_uri(kwargs["dea_base_uri"])
161 dea_base_comment = dea_base_conf['dea-base-config-metadata']['comment']
162 dea_base_conf.pop('dea-base-config-metadata')
163 final_dea_conf = dea_base_conf
164
165 # Fetch dea-pod-override, extract and purge meta-data, merge with previous dea data structure
166 print 'Parsing the dea-pod-override from: ' + kwargs["dea_pod_override_uri"] + "...."
167 response = urllib2.urlopen(kwargs["dea_pod_override_uri"])
168 dea_pod_override_conf = yaml.load(response.read())
169 if dea_pod_override_conf:
170     dea_pod_title = dea_pod_override_conf['dea-pod-override-config-metadata']['title']
171     dea_pod_version = dea_pod_override_conf['dea-pod-override-config-metadata']['version']
172     dea_pod_creation = dea_pod_override_conf['dea-pod-override-config-metadata']['created']
173     dea_pod_sha = sha_uri(kwargs["dea_pod_override_uri"])
174     dea_pod_comment = dea_pod_override_conf['dea-pod-override-config-metadata']['comment']
175     print 'Merging dea-base and dea-pod-override configuration ....'
176     dea_pod_override_conf.pop('dea-pod-override-config-metadata')
177     if dea_pod_override_conf:
178         final_dea_conf = dict(mergedicts(final_dea_conf, dea_pod_override_conf))
179
180 # Fetch deployment-scenario, extract and purge meta-data, merge deployment-scenario/
181 # dea-override-configith previous dea data structure
182 print 'Parsing deployment-scenario from: ' + kwargs["scenario"] + "...."
183
184 response = urllib2.urlopen(kwargs["scenario_base_uri"] + "/scenario.yaml")
185 scenario_short_translation_conf = yaml.load(response.read())
186 if kwargs["scenario"] in scenario_short_translation_conf:
187     scenario_uri = kwargs["scenario_base_uri"] + "/" + scenario_short_translation_conf[kwargs["scenario"]]['configfile']
188 else:
189     scenario_uri = kwargs["scenario_base_uri"] + "/" + kwargs["scenario"]
190 response = urllib2.urlopen(scenario_uri)
191 deploy_scenario_conf = yaml.load(response.read())
192
193 if deploy_scenario_conf:
194     deploy_scenario_title = deploy_scenario_conf['deployment-scenario-metadata']['title']
195     deploy_scenario_version = deploy_scenario_conf['deployment-scenario-metadata']['version']
196     deploy_scenario_creation = deploy_scenario_conf['deployment-scenario-metadata']['created']
197     deploy_scenario_sha = sha_uri(scenario_uri)
198     deploy_scenario_comment = deploy_scenario_conf['deployment-scenario-metadata']['comment']
199     deploy_scenario_conf.pop('deployment-scenario-metadata')
200 else:
201     print "Deployment scenario file not found or is empty"
202     print "Cannot continue, exiting ...."
203     sys.exit(1)
204
205 dea_scenario_override_conf = deploy_scenario_conf["dea-override-config"]
206 if dea_scenario_override_conf:
207     print 'Merging dea-base-, dea-pod-override- and deployment-scenario configuration into final dea.yaml configuration....'
208     final_dea_conf = dict(mergedicts(final_dea_conf, dea_scenario_override_conf))
209
210 # Fetch plugin-configuration configuration files, extract and purge meta-data,
211 # merge/append with previous dea data structure, override plugin-configuration with
212 # deploy-scenario/module-config-override
213 modules = []
214 module_uris = []
215 module_titles = []
216 module_versions = []
217 module_creations = []
218 module_shas = []
219 module_comments = []
220 if deploy_scenario_conf["stack-extensions"]:
221     for module in deploy_scenario_conf["stack-extensions"]:
222         print 'Loading configuration for module: ' + module["module"] + ' and merging it to final dea.yaml configuration....'
223         response = urllib2.urlopen(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml')
224         module_conf = yaml.load(response.read())
225         modules.append(module["module"])
226         module_uris.append(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml')
227         module_titles.append(str(module_conf['plugin-config-metadata']['title']))
228         module_versions.append(str(module_conf['plugin-config-metadata']['version']))
229         module_creations.append(str(module_conf['plugin-config-metadata']['created']))
230         module_shas.append(sha_uri(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml'))
231         module_comments.append(str(module_conf['plugin-config-metadata']['comment']))
232         module_conf.pop('plugin-config-metadata')
233         final_dea_conf['settings']['editable'].update(module_conf)
234         scenario_module_override_conf = module.get('module-config-override')
235         if scenario_module_override_conf:
236             dea_scenario_module_override_conf = {}
237             dea_scenario_module_override_conf['settings'] = {}
238             dea_scenario_module_override_conf['settings']['editable'] = {}
239             dea_scenario_module_override_conf['settings']['editable'][module["module"]] = scenario_module_override_conf
240             final_dea_conf = dict(mergedicts(final_dea_conf, dea_scenario_module_override_conf))
241
242 # Dump final dea.yaml including configuration management meta-data to argument provided
243 # directory
244 if not os.path.exists(kwargs["output_path"]):
245     os.makedirs(kwargs["output_path"])
246 print 'Dumping final dea.yaml to ' + kwargs["output_path"] + '/dea.yaml....'
247 with open(kwargs["output_path"] + '/dea.yaml', "w") as f:
248     f.write("title: DEA.yaml file automatically generated from the configuration files stated in the \"configuration-files\" fragment below\n")
249     f.write("version: " + str(calendar.timegm(time.gmtime()))  + "\n")
250     f.write("created: " + str(time.strftime("%d/%m/%Y")) + " " + str(time.strftime("%H:%M:%S"))  + "\n")
251     f.write("comment: none\n")
252
253     f.write("configuration-files:\n")
254     f.write("  dea-base:\n")
255     f.write("    uri: " +  kwargs["dea_base_uri"] + "\n")
256     f.write("    title: " +  str(dea_base_title) + "\n")
257     f.write("    version: " +  str(dea_base_version) + "\n")
258     f.write("    created: " +  str(dea_base_creation) + "\n")
259     f.write("    sha1: " +  str(dea_base_sha) + "\n")
260     f.write("    comment: " +  str(dea_base_comment) + "\n")
261
262     f.write("  pod-override:\n")
263     f.write("    uri: " +  kwargs["dea_pod_override_uri"] + "\n")
264     f.write("    title: " +  str(dea_pod_title) + "\n")
265     f.write("    version: " +  str(dea_pod_version) + "\n")
266     f.write("    created: " +  str(dea_pod_creation) + "\n")
267     f.write("    sha1: " +  str(dea_pod_sha) + "\n")
268     f.write("    comment: " +  str(dea_pod_comment) + "\n")
269
270     f.write("  deployment-scenario:\n")
271     f.write("    uri: " +  str(scenario_uri) + "\n")
272     f.write("    title: " +  str(deploy_scenario_title) + "\n")
273     f.write("    version: " +  str(deploy_scenario_version) + "\n")
274     f.write("    created: " +  str(deploy_scenario_creation) + "\n")
275     f.write("    sha1: " +  str(deploy_scenario_sha) + "\n")
276     f.write("    comment: " +  str(deploy_scenario_comment) + "\n")
277
278     f.write("  plugin-modules:\n")
279     for k in range(0,len(modules)):
280         f.write("  - module: " + modules[k] + "\n")
281         f.write("    uri: " + module_uris[k] + "\n")
282         f.write("    title: " + module_titles[k] + "\n")
283         f.write("    version: " + module_versions[k] + "\n")
284         f.write("    created: " + module_creations[k] + "\n")
285         f.write("    sha-1: " + module_shas[k] + "\n")
286         f.write("    comment: " + module_comments[k] + "\n")
287
288     yaml.dump(final_dea_conf, f, default_flow_style=False)
289
290 # Load POD dha and override it with "deployment-scenario/dha-override-config" section
291 print 'Generating final dha.yaml configuration....'
292 print 'Parsing dha-pod yaml configuration....'
293 response = urllib2.urlopen(kwargs["dha_uri"])
294 dha_pod_conf = yaml.load(response.read())
295 dha_pod_title = dha_pod_conf['dha-pod-config-metadata']['title']
296 dha_pod_version = dha_pod_conf['dha-pod-config-metadata']['version']
297 dha_pod_creation = dha_pod_conf['dha-pod-config-metadata']['created']
298 dha_pod_sha = sha_uri(kwargs["dha_uri"])
299 dha_pod_comment = dha_pod_conf['dha-pod-config-metadata']['comment']
300 dha_pod_conf.pop('dha-pod-config-metadata')
301 final_dha_conf = dha_pod_conf
302
303 dha_scenario_override_conf = deploy_scenario_conf["dha-override-config"]
304 # Only virtual deploy scenarios can override dha.yaml since there
305 # is no way to programatically override a physical environment:
306 # wireing, IPMI set-up, etc.
307 # For Physical environments, dha.yaml overrides will be silently ignored
308 if dha_scenario_override_conf and (final_dha_conf['adapter'] == 'libvirt' or final_dha_conf['adapter'] == 'esxi' or final_dha_conf['adapter'] == 'vbox'):
309     print 'Merging dha-pod and deployment-scenario override information to final dha.yaml configuration....'
310     final_dha_conf = dict(mergedicts(final_dha_conf, dha_scenario_override_conf))
311
312 # Dump final dha.yaml to argument provided directory
313 print 'Dumping final dha.yaml to ' + kwargs["output_path"] + '/dha.yaml....'
314 with open(kwargs["output_path"] + '/dha.yaml', "w") as f:
315     f.write("title: DHA.yaml file automatically generated from the configuration files stated in the \"configuration-files\" fragment below\n")
316     f.write("version: " + str(calendar.timegm(time.gmtime()))  + "\n")
317     f.write("created: " + str(time.strftime("%d/%m/%Y")) + " " + str(time.strftime("%H:%M:%S"))  + "\n")
318     f.write("comment: none\n")
319
320     f.write("configuration-files:\n")
321
322     f.write("  dha-pod-configuration:\n")
323     f.write("    uri: " +  kwargs["dha_uri"] + "\n")
324     f.write("    title: " +  str(dha_pod_title) + "\n")
325     f.write("    version: " +  str(dha_pod_version) + "\n")
326     f.write("    created: " +  str(dha_pod_creation) + "\n")
327     f.write("    sha-1: " +  str(dha_pod_sha) + "\n")
328     f.write("    comment: " +  str(dha_pod_comment) + "\n")
329
330     f.write("  deployment-scenario:\n")
331     f.write("    uri: " +  str(scenario_uri) + "\n")
332     f.write("    title: " +  str(deploy_scenario_title) + "\n")
333     f.write("    version: " +  str(deploy_scenario_version) + "\n")
334     f.write("    created: " +  str(deploy_scenario_creation) + "\n")
335     f.write("    sha-1: " +  str(deploy_scenario_sha) + "\n")
336     f.write("    comment: " +  str(deploy_scenario_comment) + "\n")
337     yaml.dump(final_dha_conf, f, default_flow_style=False)