Merge "Properly clean up after SIGTERM (kill) and Ctrl-c" into stable/brahmaputra
[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 mergedicts(dict1, dict2):
94     for k in set(dict1.keys()).union(dict2.keys()):
95         if k in dict1 and k in dict2:
96             if isinstance(dict1[k], dict) and isinstance(dict2[k], dict):
97                 yield (k, dict(mergedicts(dict1[k], dict2[k])))
98             else:
99                 # If one of the values is not a dict, you can't continue
100                 # merging it.
101                 # Value from second dict overrides one in first and we move on.
102                 yield (k, dict2[k])
103         elif k in dict1:
104             yield (k, dict1[k])
105         else:
106             yield (k, dict2[k])
107
108 setup_yaml()
109 kwargs = parse_arguments()
110
111 # Generate final dea.yaml by merging following config files/fragments in revers priority order:
112 # "dea-base", "dea-pod-override", "deplyment-scenario/module-config-override"
113 # and "deployment-scenario/dea-override"
114 print 'Generating final dea.yaml configuration....'
115
116 # Fetch dea-base, extract and purge meta-data
117 print 'Parsing dea-base from: ' + kwargs["dea_base_uri"] + "...."
118 response = urllib2.urlopen(kwargs["dea_base_uri"])
119 dea_base_conf = yaml.load(response.read())
120 dea_base_title = dea_base_conf['dea-base-config-metadata']['title']
121 dea_base_version = dea_base_conf['dea-base-config-metadata']['version']
122 dea_base_creation = dea_base_conf['dea-base-config-metadata']['created']
123 dea_base_sha = sha_uri(kwargs["dea_base_uri"])
124 dea_base_comment = dea_base_conf['dea-base-config-metadata']['comment']
125 dea_base_conf.pop('dea-base-config-metadata')
126 final_dea_conf = dea_base_conf
127
128 # Fetch dea-pod-override, extract and purge meta-data, merge with previous dea data structure
129 print 'Parsing the dea-pod-override from: ' + kwargs["dea_pod_override_uri"] + "...."
130 response = urllib2.urlopen(kwargs["dea_pod_override_uri"])
131 dea_pod_override_conf = yaml.load(response.read())
132 if dea_pod_override_conf:
133     dea_pod_title = dea_pod_override_conf['dea-pod-override-config-metadata']['title']
134     dea_pod_version = dea_pod_override_conf['dea-pod-override-config-metadata']['version']
135     dea_pod_creation = dea_pod_override_conf['dea-pod-override-config-metadata']['created']
136     dea_pod_sha = sha_uri(kwargs["dea_pod_override_uri"])
137     dea_pod_comment = dea_pod_override_conf['dea-pod-override-config-metadata']['comment']
138     print 'Merging dea-base and dea-pod-override configuration ....'
139     dea_pod_override_conf.pop('dea-pod-override-config-metadata')
140     if dea_pod_override_conf:
141         final_dea_conf = dict(mergedicts(final_dea_conf, dea_pod_override_conf))
142
143 # Fetch deployment-scenario, extract and purge meta-data, merge deployment-scenario/
144 # dea-override-configith previous dea data structure
145 print 'Parsing deployment-scenario from: ' + kwargs["scenario"] + "...."
146
147 response = urllib2.urlopen(kwargs["scenario_base_uri"] + "/scenario.yaml")
148 scenario_short_translation_conf = yaml.load(response.read())
149 if kwargs["scenario"] in scenario_short_translation_conf:
150     scenario_uri = kwargs["scenario_base_uri"] + "/" + scenario_short_translation_conf[kwargs["scenario"]]['configfile']
151 else:
152     scenario_uri = kwargs["scenario_base_uri"] + "/" + kwargs["scenario"]
153 response = urllib2.urlopen(scenario_uri)
154 deploy_scenario_conf = yaml.load(response.read())
155
156 if deploy_scenario_conf:
157     deploy_scenario_title = deploy_scenario_conf['deployment-scenario-metadata']['title']
158     deploy_scenario_version = deploy_scenario_conf['deployment-scenario-metadata']['version']
159     deploy_scenario_creation = deploy_scenario_conf['deployment-scenario-metadata']['created']
160     deploy_scenario_sha = sha_uri(scenario_uri)
161     deploy_scenario_comment = deploy_scenario_conf['deployment-scenario-metadata']['comment']
162     deploy_scenario_conf.pop('deployment-scenario-metadata')
163 else:
164     print "Deployment scenario file not found or is empty"
165     print "Cannot continue, exiting ...."
166     sys.exit(1)
167
168 dea_scenario_override_conf = deploy_scenario_conf["dea-override-config"]
169 if dea_scenario_override_conf:
170     print 'Merging dea-base-, dea-pod-override- and deployment-scenario configuration into final dea.yaml configuration....'
171     final_dea_conf = dict(mergedicts(final_dea_conf, dea_scenario_override_conf))
172
173 # Fetch plugin-configuration configuration files, extract and purge meta-data,
174 # merge/append with previous dea data structure, override plugin-configuration with
175 # deploy-scenario/module-config-override
176 modules = []
177 module_uris = []
178 module_titles = []
179 module_versions = []
180 module_creations = []
181 module_shas = []
182 module_comments = []
183 if deploy_scenario_conf["stack-extensions"]:
184     for module in deploy_scenario_conf["stack-extensions"]:
185         print 'Loading configuration for module: ' + module["module"] + ' and merging it to final dea.yaml configuration....'
186         response = urllib2.urlopen(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml')
187         module_conf = yaml.load(response.read())
188         modules.append(module["module"])
189         module_uris.append(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml')
190         module_titles.append(str(module_conf['plugin-config-metadata']['title']))
191         module_versions.append(str(module_conf['plugin-config-metadata']['version']))
192         module_creations.append(str(module_conf['plugin-config-metadata']['created']))
193         module_shas.append(sha_uri(kwargs["plugins_uri"] + '/' + module["module-config-name"] + '_' + module["module-config-version"] + '.yaml'))
194         module_comments.append(str(module_conf['plugin-config-metadata']['comment']))
195         module_conf.pop('plugin-config-metadata')
196         final_dea_conf['settings']['editable'].update(module_conf)
197         scenario_module_override_conf = module['module-config-override']
198         if scenario_module_override_conf:
199             dea_scenario_module_override_conf = {}
200             dea_scenario_module_override_conf['settings'] = {}
201             dea_scenario_module_override_conf['settings']['editable'] = {}
202             dea_scenario_module_override_conf['settings']['editable'][module["module"]] = scenario_module_override_conf
203             final_dea_conf = dict(mergedicts(final_dea_conf, dea_scenario_module_override_conf))
204
205 # Dump final dea.yaml including configuration management meta-data to argument provided
206 # directory
207 if not os.path.exists(kwargs["output_path"]):
208     os.makedirs(kwargs["output_path"])
209 print 'Dumping final dea.yaml to ' + kwargs["output_path"] + '/dea.yaml....'
210 with open(kwargs["output_path"] + '/dea.yaml', "w") as f:
211     f.write("title: DEA.yaml file automatically generated from the configuration files stated in the \"configuration-files\" fragment below\n")
212     f.write("version: " + str(calendar.timegm(time.gmtime()))  + "\n")
213     f.write("created: " + str(time.strftime("%d/%m/%Y")) + " " + str(time.strftime("%H:%M:%S"))  + "\n")
214     f.write("comment: none\n")
215
216     f.write("configuration-files:\n")
217     f.write("  dea-base:\n")
218     f.write("    uri: " +  kwargs["dea_base_uri"] + "\n")
219     f.write("    title: " +  str(dea_base_title) + "\n")
220     f.write("    version: " +  str(dea_base_version) + "\n")
221     f.write("    created: " +  str(dea_base_creation) + "\n")
222     f.write("    sha1: " +  str(dea_base_sha) + "\n")
223     f.write("    comment: " +  str(dea_base_comment) + "\n")
224
225     f.write("  pod-override:\n")
226     f.write("    uri: " +  kwargs["dea_pod_override_uri"] + "\n")
227     f.write("    title: " +  str(dea_pod_title) + "\n")
228     f.write("    version: " +  str(dea_pod_version) + "\n")
229     f.write("    created: " +  str(dea_pod_creation) + "\n")
230     f.write("    sha1: " +  str(dea_pod_sha) + "\n")
231     f.write("    comment: " +  str(dea_pod_comment) + "\n")
232
233     f.write("  deployment-scenario:\n")
234     f.write("    uri: " +  str(scenario_uri) + "\n")
235     f.write("    title: " +  str(deploy_scenario_title) + "\n")
236     f.write("    version: " +  str(deploy_scenario_version) + "\n")
237     f.write("    created: " +  str(deploy_scenario_creation) + "\n")
238     f.write("    sha1: " +  str(deploy_scenario_sha) + "\n")
239     f.write("    comment: " +  str(deploy_scenario_comment) + "\n")
240
241     f.write("  plugin-modules:\n")
242     for k in range(0,len(modules)):
243         f.write("  - module: " + modules[k] + "\n")
244         f.write("    uri: " + module_uris[k] + "\n")
245         f.write("    title: " + module_titles[k] + "\n")
246         f.write("    version: " + module_versions[k] + "\n")
247         f.write("    created: " + module_creations[k] + "\n")
248         f.write("    sha-1: " + module_shas[k] + "\n")
249         f.write("    comment: " + module_comments[k] + "\n")
250
251     yaml.dump(final_dea_conf, f, default_flow_style=False)
252
253 # Load POD dha and override it with "deployment-scenario/dha-override-config" section
254 print 'Generating final dha.yaml configuration....'
255 print 'Parsing dha-pod yaml configuration....'
256 response = urllib2.urlopen(kwargs["dha_uri"])
257 dha_pod_conf = yaml.load(response.read())
258 dha_pod_title = dha_pod_conf['dha-pod-config-metadata']['title']
259 dha_pod_version = dha_pod_conf['dha-pod-config-metadata']['version']
260 dha_pod_creation = dha_pod_conf['dha-pod-config-metadata']['created']
261 dha_pod_sha = sha_uri(kwargs["dha_uri"])
262 dha_pod_comment = dha_pod_conf['dha-pod-config-metadata']['comment']
263 dha_pod_conf.pop('dha-pod-config-metadata')
264 final_dha_conf = dha_pod_conf
265
266 dha_scenario_override_conf = deploy_scenario_conf["dha-override-config"]
267 # Only virtual deploy scenarios can override dha.yaml since there
268 # is no way to programatically override a physical environment:
269 # wireing, IPMI set-up, etc.
270 # For Physical environments, dha.yaml overrides will be silently ignored
271 if dha_scenario_override_conf and (final_dha_conf['adapter'] == 'libvirt' or final_dha_conf['adapter'] == 'esxi' or final_dha_conf['adapter'] == 'vbox'):
272     print 'Merging dha-pod and deployment-scenario override information to final dha.yaml configuration....'
273     final_dha_conf = dict(mergedicts(final_dha_conf, dha_scenario_override_conf))
274
275 # Dump final dha.yaml to argument provided directory
276 print 'Dumping final dha.yaml to ' + kwargs["output_path"] + '/dha.yaml....'
277 with open(kwargs["output_path"] + '/dha.yaml', "w") as f:
278     f.write("title: DHA.yaml file automatically generated from the configuration files stated in the \"configuration-files\" fragment below\n")
279     f.write("version: " + str(calendar.timegm(time.gmtime()))  + "\n")
280     f.write("created: " + str(time.strftime("%d/%m/%Y")) + " " + str(time.strftime("%H:%M:%S"))  + "\n")
281     f.write("comment: none\n")
282
283     f.write("configuration-files:\n")
284
285     f.write("  dha-pod-configuration:\n")
286     f.write("    uri: " +  kwargs["dha_uri"] + "\n")
287     f.write("    title: " +  str(dha_pod_title) + "\n")
288     f.write("    version: " +  str(dha_pod_version) + "\n")
289     f.write("    created: " +  str(dha_pod_creation) + "\n")
290     f.write("    sha-1: " +  str(dha_pod_sha) + "\n")
291     f.write("    comment: " +  str(dha_pod_comment) + "\n")
292
293     f.write("  deployment-scenario:\n")
294     f.write("    uri: " +  str(scenario_uri) + "\n")
295     f.write("    title: " +  str(deploy_scenario_title) + "\n")
296     f.write("    version: " +  str(deploy_scenario_version) + "\n")
297     f.write("    created: " +  str(deploy_scenario_creation) + "\n")
298     f.write("    sha-1: " +  str(deploy_scenario_sha) + "\n")
299     f.write("    comment: " +  str(deploy_scenario_comment) + "\n")
300     yaml.dump(final_dha_conf, f, default_flow_style=False)