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