Merge "Reaping improvements for Fuel 7"
[fuel.git] / deploy / reap.py
1 #!/usr/bin/python
2 ###############################################################################
3 # Copyright (c) 2015 Ericsson AB and others.
4 # szilard.cserey@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 import time
13 import os
14 import yaml
15 import glob
16 import shutil
17
18 from common import (
19     N,
20     E,
21     R,
22     ArgParser,
23     exec_cmd,
24     parse,
25     err,
26     log,
27     delete,
28     commafy,
29 )
30
31 DEA_1 = '''
32 title: Deployment Environment Adapter (DEA)
33 # DEA API version supported
34 version: 1.1
35 created: {date}
36 comment: {comment}
37 '''
38
39 DHA_1 = '''
40 title: Deployment Hardware Adapter (DHA)
41 # DHA API version supported
42 version: 1.1
43 created: {date}
44 comment: {comment}
45
46 # Adapter to use for this definition
47 # adapter: [ipmi|libvirt]
48 adapter:
49
50 # Node list.
51 # Mandatory properties are id and role.
52 # All other properties are adapter specific.
53 # For Non-Fuel nodes controlled by:
54 #   - ipmi adapter you need to provide:
55 #       pxeMac
56 #       ipmiIp
57 #       ipmiUser
58 #       ipmiPass
59 #   - libvirt adapter you need to provide:
60 #       libvirtName: <whatever>
61 #       libvirtTemplate: [libvirt/vms/controller.xml | libvirt/vms/compute.xml]
62 #
63 # For the Fuel Node you need to provide:
64 #       libvirtName: <whatever>
65 #       libvirtTemplate: libvirt/vms/fuel.xml
66 #       isFuel: yes
67 #       username: root
68 #       password: r00tme
69 '''
70
71 DHA_2 = '''
72 # Adding the Fuel node as node id {node_id}
73 # which may not be correct - please adjust as needed.
74 '''
75
76 DISKS = {'fuel': '50G',
77          'controller': '60G',
78          'compute': '50G'}
79
80
81 class Reap(object):
82
83     def __init__(self, dea_file, dha_file, comment):
84         self.dea_file = dea_file
85         self.dha_file = dha_file
86         self.comment = comment
87         self.temp_dir = None
88         self.env = None
89         self.env_id = None
90         self.last_node = None
91
92     def get_env(self):
93         env_list = parse(exec_cmd('fuel env'))
94         if len(env_list) > 1:
95             err('Not exactly one environment')
96         self.env = env_list[0]
97         self.env_id = self.env[E['id']]
98
99     def download_config(self, config_type):
100         log('Download %s config for environment %s'
101             % (config_type, self.env_id))
102         exec_cmd('fuel %s --env %s --download --dir %s'
103                  % (config_type, self.env_id, self.temp_dir))
104
105     def download_node_config(self, nodeid):
106         log('Download node %s config for environment %s to %s'
107             % (nodeid, self.env_id,self.temp_dir))
108         exec_cmd('fuel deployment --node-id %s --env %s  --default --dir %s'
109                  % (nodeid, self.env_id, self.temp_dir))
110
111     def write(self, file, text, newline=True):
112         mode = 'a' if os.path.isfile(file) else 'w'
113         with open(file, mode) as f:
114             f.write('%s%s' % (text, ('\n' if newline else '')))
115
116     def write_yaml(self, file, data, newline=True):
117         self.write(file, yaml.dump(data, default_flow_style=False).strip(),
118                    newline)
119
120     def get_node_by_id(self, node_list, node_id):
121         for node in node_list:
122             if node[N['id']] == node_id:
123                 return node
124
125     def reap_interface(self, node_id, interfaces):
126         interface, mac = self.get_interface(node_id)
127         if_name = None
128         if interfaces:
129             if_name = self.check_dict_exists(interfaces, interface)
130         if not if_name:
131             if_name = 'interfaces_%s' % str(len(interfaces) + 1)
132             interfaces[if_name] = interface
133         return if_name, mac
134
135     def reap_transformation(self, node_id, roles, transformations):
136         main_role = 'controller' if 'controller' in roles else 'compute'
137         node_file = glob.glob('%s/deployment_%s/*%s_%s.yaml'
138                               % (self.temp_dir, self.env_id,
139                                  main_role, node_id))
140         tr_name = None
141         with open(node_file[0]) as f:
142             node_config = yaml.load(f)
143         transformation = {'transformations':
144                               node_config['network_scheme']['transformations']}
145         if transformations:
146             tr_name = self.check_dict_exists(transformations, transformation)
147         if not tr_name:
148             tr_name = 'transformations_%s' % str(len(transformations) + 1)
149             transformations[tr_name] = transformation
150         return tr_name
151
152     def check_dict_exists(self, main_dict, dict):
153         for key, val in main_dict.iteritems():
154             if cmp(dict, val) == 0:
155                 return key
156
157     def reap_nodes_interfaces_transformations(self):
158         node_list = parse(exec_cmd('fuel node'))
159         real_node_ids = [node[N['id']] for node in node_list]
160         real_node_ids.sort()
161         min_node = real_node_ids[0]
162         interfaces = {}
163         transformations = {}
164         dea_nodes = []
165         dha_nodes = []
166
167         for real_node_id in real_node_ids:
168             node_id = int(real_node_id) - int(min_node) + 1
169             self.last_node = node_id
170             node = self.get_node_by_id(node_list, real_node_id)
171             roles = commafy(node[N['roles']])
172             if not roles:
173                 err('Fuel Node %s has no role' % real_node_id)
174             dea_node = {'id': node_id,
175                         'role': roles}
176             dha_node = {'id': node_id}
177             if_name, mac = self.reap_interface(real_node_id, interfaces)
178             log('reap transformation for node %s' % real_node_id)
179             tr_name = self.reap_transformation(real_node_id, roles,
180                                                transformations)
181             dea_node.update(
182                 {'interfaces': if_name,
183                  'transformations': tr_name})
184
185             dha_node.update(
186                 {'pxeMac': mac if mac else None,
187                  'ipmiIp': None,
188                  'ipmiUser': None,
189                  'ipmiPass': None,
190                  'libvirtName': None,
191                  'libvirtTemplate': None})
192
193             dea_nodes.append(dea_node)
194             dha_nodes.append(dha_node)
195
196         self.write_yaml(self.dha_file, {'nodes': dha_nodes}, False)
197         self.write_yaml(self.dea_file, {'nodes': dea_nodes})
198         self.write_yaml(self.dea_file, interfaces)
199         self.write_yaml(self.dea_file, transformations)
200         self.reap_fuel_node_info()
201         self.write_yaml(self.dha_file, {'disks': DISKS})
202
203     def reap_fuel_node_info(self):
204         dha_nodes = []
205         dha_node = {
206             'id': self.last_node + 1,
207             'libvirtName': None,
208             'libvirtTemplate': None,
209             'isFuel': True,
210             'username': 'root',
211             'password': 'r00tme'}
212
213         dha_nodes.append(dha_node)
214
215         self.write(self.dha_file, DHA_2.format(node_id=dha_node['id']), False)
216         self.write_yaml(self.dha_file, dha_nodes)
217
218     def reap_environment_info(self):
219         network_file = ('%s/network_%s.yaml'
220                         % (self.temp_dir, self.env_id))
221         network = self.read_yaml(network_file)
222
223         # ha_compact not understood by Fuel when deploying...OD
224         if self.env[E['mode']] == 'ha_compact':
225             self.env[E['mode']] = 'ha'
226
227         env = {'environment':
228                    {'name': self.env[E['name']],
229                     'mode': self.env[E['mode']],
230                     'net_segment_type':
231                         network['networking_parameters']['segmentation_type']}}
232         self.write_yaml(self.dea_file, env)
233         wanted_release = None
234         rel_list = parse(exec_cmd('fuel release'))
235         for rel in rel_list:
236             if rel[R['id']] == self.env[E['release_id']]:
237                 wanted_release = rel[R['name']]
238         self.write_yaml(self.dea_file, {'wanted_release': wanted_release})
239
240     def reap_fuel_settings(self):
241         data = self.read_yaml('/etc/fuel/astute.yaml')
242         fuel = {}
243         del data['ADMIN_NETWORK']['mac']
244         del data['ADMIN_NETWORK']['interface']
245         for key in ['ADMIN_NETWORK', 'HOSTNAME', 'DNS_DOMAIN', 'DNS_SEARCH',
246                     'DNS_UPSTREAM', 'NTP1', 'NTP2', 'NTP3', 'FUEL_ACCESS']:
247             fuel[key] = data[key]
248         for key in fuel['ADMIN_NETWORK'].keys():
249             if key not in ['ipaddress', 'netmask',
250                            'dhcp_pool_start', 'dhcp_pool_end']:
251                 del fuel['ADMIN_NETWORK'][key]
252         self.write_yaml(self.dea_file, {'fuel': fuel})
253
254     def reap_network_settings(self):
255         network_file = ('%s/network_%s.yaml'
256                         % (self.temp_dir, self.env_id))
257         data = self.read_yaml(network_file)
258         network = {}
259         network['networking_parameters'] = data['networking_parameters']
260         network['networks'] = data['networks']
261         for net in network['networks']:
262             del net['id']
263             del net['group_id']
264         self.write_yaml(self.dea_file, {'network': network})
265
266     def reap_settings(self):
267         settings_file = '%s/settings_%s.yaml' % (self.temp_dir, self.env_id)
268         settings = self.read_yaml(settings_file)
269         self.write_yaml(self.dea_file, {'settings': settings})
270
271     def get_interface(self, real_node_id):
272         exec_cmd('fuel node --node-id %s --network --download --dir %s'
273                  % (real_node_id, self.temp_dir))
274         interface_file = ('%s/node_%s/interfaces.yaml'
275                           % (self.temp_dir, real_node_id))
276         interfaces = self.read_yaml(interface_file)
277         interface_config = {}
278         pxe_mac = None
279         for interface in interfaces:
280             networks = []
281             for network in interface['assigned_networks']:
282                 networks.append(network['name'])
283                 if network['name'] == 'fuelweb_admin':
284                     pxe_mac = interface['mac']
285             if networks:
286                 interface_config[interface['name']] = networks
287         return interface_config, pxe_mac
288
289     def read_yaml(self, yaml_file):
290         with open(yaml_file) as f:
291             data = yaml.load(f)
292             return data
293
294     def intro(self):
295         delete(self.dea_file)
296         delete(self.dha_file)
297         self.temp_dir = exec_cmd('mktemp -d')
298         date = time.strftime('%c')
299         self.write(self.dea_file,
300                    DEA_1.format(date=date, comment=self.comment), False)
301         self.write(self.dha_file,
302                    DHA_1.format(date=date, comment=self.comment))
303         self.get_env()
304
305         # Need to download deployment with explicit node ids
306         node_list = parse(exec_cmd('fuel node'))
307         real_node_ids = [node[N['id']] for node in node_list]
308         real_node_ids.sort()
309         self.download_node_config(','.join(real_node_ids))
310
311         self.download_config('settings')
312         self.download_config('network')
313
314     def finale(self):
315         log('DEA file is available at %s' % self.dea_file)
316         log('DHA file is available at %s (this is just a template)'
317             % self.dha_file)
318         shutil.rmtree(self.temp_dir)
319
320     def reap(self):
321         self.intro()
322         self.reap_environment_info()
323         self.reap_nodes_interfaces_transformations()
324         self.reap_fuel_settings()
325         self.reap_network_settings()
326         self.reap_settings()
327         self.finale()
328
329
330 def usage():
331     print '''
332     Usage:
333     python reap.py <dea_file> <dha_file> <comment>
334     '''
335
336
337 def parse_arguments():
338     parser = ArgParser(prog='python %s' % __file__)
339     parser.add_argument('dea_file', nargs='?', action='store',
340                         default='dea.yaml',
341                         help='Deployment Environment Adapter: dea.yaml')
342     parser.add_argument('dha_file', nargs='?', action='store',
343                         default='dha.yaml',
344                         help='Deployment Hardware Adapter: dha.yaml')
345     parser.add_argument('comment', nargs='?', action='store', help='Comment')
346     args = parser.parse_args()
347     return (args.dea_file, args.dha_file, args.comment)
348
349
350 def main():
351     dea_file, dha_file, comment = parse_arguments()
352
353     r = Reap(dea_file, dha_file, comment)
354     r.reap()
355
356
357 if __name__ == '__main__':
358     main()