Merge "Compile DPDK to use basic cpu features" into stable/colorado
[fuel.git] / deploy / reap.py
1 #!/usr/bin/python
2 ###############################################################################
3 # Copyright (c) 2015, 2016 Ericsson AB and others.
4 # szilard.cserey@ericsson.com
5 # peter.barabas@ericsson.com
6 # All rights reserved. This program and the accompanying materials
7 # are made available under the terms of the Apache License, Version 2.0
8 # which accompanies this distribution, and is available at
9 # http://www.apache.org/licenses/LICENSE-2.0
10 ###############################################################################
11
12
13 import time
14 import os
15 import yaml
16 import glob
17 import shutil
18 import tempfile
19 import re
20 import netaddr
21 import templater
22
23 from common import (
24     N,
25     E,
26     R,
27     ArgParser,
28     exec_cmd,
29     parse,
30     err,
31     log,
32     delete,
33     commafy,
34 )
35
36 DEA_1 = '''
37 title: Deployment Environment Adapter (DEA)
38 # DEA API version supported
39 version: 1.1
40 created: {date}
41 comment: {comment}
42 '''
43
44 DHA_1 = '''
45 title: Deployment Hardware Adapter (DHA)
46 # DHA API version supported
47 version: 1.1
48 created: {date}
49 comment: {comment}
50
51 # Adapter to use for this definition
52 # adapter: [ipmi|libvirt]
53 adapter:
54
55 # Node list.
56 # Mandatory properties are id and role.
57 # All other properties are adapter specific.
58 # For Non-Fuel nodes controlled by:
59 #   - ipmi adapter you need to provide:
60 #       pxeMac
61 #       ipmiIp
62 #       ipmiUser
63 #       ipmiPass
64 #     and you *MAY* provide (optional, not added by reap.py):
65 #       ipmiPort
66 #   - libvirt adapter you need to provide:
67 #       libvirtName: <whatever>
68 #       libvirtTemplate: [libvirt/vms/controller.xml | libvirt/vms/compute.xml]
69 #
70 # For the Fuel Node you need to provide:
71 #       libvirtName: <whatever>
72 #       libvirtTemplate: libvirt/vms/fuel.xml
73 #       isFuel: yes
74 #       username: root
75 #       password: r00tme
76 '''
77
78 DHA_2 = '''
79 # Adding the Fuel node as node id {node_id}
80 # which may not be correct - please adjust as needed.
81 '''
82
83 DISKS = {'fuel': '100G',
84          'controller': '100G',
85          'compute': '100G'}
86
87
88 class Reap(object):
89
90     def __init__(self, dea_file, dha_file, comment, base_dea, template):
91         self.dea_file = dea_file
92         self.dha_file = dha_file
93         self.comment = comment
94         self.base_dea = base_dea
95         self.template = template
96         self.temp_dir = None
97         self.env = None
98         self.env_id = None
99         self.last_node = None
100
101     def get_env(self):
102         env_list = parse(exec_cmd('fuel env'))
103         if len(env_list) == 0:
104             err('No environment deployed')
105         elif len(env_list) > 1:
106             err('More than 1 environment deployed')
107         self.env = env_list[0]
108         self.env_id = self.env[E['id']]
109
110     def download_config(self, config_type):
111         log('Download %s config for environment %s'
112             % (config_type, self.env_id))
113         exec_cmd('fuel %s --env %s --download --dir %s'
114                  % (config_type, self.env_id, self.temp_dir))
115
116     def download_node_config(self, nodeid):
117         log('Download node %s config for environment %s to %s'
118             % (nodeid, self.env_id,self.temp_dir))
119         exec_cmd('fuel deployment --node-id %s --env %s  --default --dir %s'
120                  % (nodeid, self.env_id, self.temp_dir))
121
122     def write(self, file, text, newline=True):
123         mode = 'a' if os.path.isfile(file) else 'w'
124         with open(file, mode) as f:
125             f.write('%s%s' % (text, ('\n' if newline else '')))
126
127     def write_yaml(self, file, data, newline=True):
128         self.write(file, yaml.dump(data, default_flow_style=False).strip(),
129                    newline)
130
131     def get_node_by_id(self, node_list, node_id):
132         for node in node_list:
133             if node[N['id']] == node_id:
134                 return node
135
136     def reap_interface(self, node_id, interfaces):
137         interface, mac = self.get_interface(node_id)
138         if_name = None
139         if interfaces:
140             if_name = self.check_dict_exists(interfaces, interface)
141         if not if_name:
142             if_name = 'interfaces_%s' % str(len(interfaces) + 1)
143             interfaces[if_name] = interface
144         return if_name, mac
145
146     def reap_transformation(self, node_id, roles, transformations):
147         main_role = 'controller' if 'controller' in roles else 'compute'
148         node_file = glob.glob('%s/deployment_%s/%s.yaml'
149                               % (self.temp_dir, self.env_id, node_id))
150         tr_name = None
151         with open(node_file[0]) as f:
152             node_config = yaml.load(f)
153         transformation = {'transformations':
154                               node_config['network_scheme']['transformations']}
155         if transformations:
156             tr_name = self.check_dict_exists(transformations, transformation)
157         if not tr_name:
158             tr_name = 'transformations_%s' % str(len(transformations) + 1)
159             transformations[tr_name] = transformation
160         return tr_name
161
162     def check_dict_exists(self, main_dict, dict):
163         for key, val in main_dict.iteritems():
164             if cmp(dict, val) == 0:
165                 return key
166
167     def reap_nodes_interfaces_transformations(self):
168         node_list = parse(exec_cmd('fuel node'))
169         real_node_ids = [node[N['id']] for node in node_list]
170         real_node_ids.sort()
171         min_node = real_node_ids[0]
172         interfaces = {}
173         transformations = {}
174         dea_nodes = []
175         dha_nodes = []
176
177         for real_node_id in real_node_ids:
178             node_id = int(real_node_id) - int(min_node) + 1
179             self.last_node = node_id
180             node = self.get_node_by_id(node_list, real_node_id)
181             roles = commafy(node[N['roles']])
182             if not roles:
183                 err('Fuel Node %s has no role' % real_node_id)
184             dea_node = {'id': node_id,
185                         'role': roles}
186             dha_node = {'id': node_id}
187             if_name, mac = self.reap_interface(real_node_id, interfaces)
188             log('reap transformation for node %s' % real_node_id)
189             tr_name = self.reap_transformation(real_node_id, roles,
190                                                transformations)
191             dea_node.update(
192                 {'interfaces': if_name,
193                  'transformations': tr_name})
194
195             dha_node.update(
196                 {'pxeMac': mac if mac else None,
197                  'ipmiIp': None,
198                  'ipmiUser': None,
199                  'ipmiPass': None,
200                  'libvirtName': None,
201                  'libvirtTemplate': None})
202
203             dea_nodes.append(dea_node)
204             dha_nodes.append(dha_node)
205
206         self.write_yaml(self.dha_file, {'nodes': dha_nodes}, False)
207         self.write_yaml(self.dea_file, {'nodes': dea_nodes})
208         self.write_yaml(self.dea_file, interfaces)
209         self.write_yaml(self.dea_file, transformations)
210         self.reap_fuel_node_info()
211         self.write_yaml(self.dha_file, {'disks': DISKS})
212
213     def reap_fuel_node_info(self):
214         dha_nodes = []
215         dha_node = {
216             'id': self.last_node + 1,
217             'libvirtName': None,
218             'libvirtTemplate': None,
219             'isFuel': True,
220             'username': 'root',
221             'password': 'r00tme'}
222
223         dha_nodes.append(dha_node)
224
225         self.write(self.dha_file, DHA_2.format(node_id=dha_node['id']), False)
226         self.write_yaml(self.dha_file, dha_nodes)
227
228     def reap_environment_info(self):
229         network_file = ('%s/network_%s.yaml'
230                         % (self.temp_dir, self.env_id))
231         network = self.read_yaml(network_file)
232
233         env = {'environment':
234                    {'name': self.env[E['name']],
235                     'net_segment_type':
236                         network['networking_parameters']['segmentation_type']}}
237         self.write_yaml(self.dea_file, env)
238         wanted_release = None
239         rel_list = parse(exec_cmd('fuel release'))
240         for rel in rel_list:
241             if rel[R['id']] == self.env[E['release_id']]:
242                 wanted_release = rel[R['name']]
243         self.write_yaml(self.dea_file, {'wanted_release': wanted_release})
244
245     def reap_fuel_settings(self):
246         data = self.read_yaml('/etc/fuel/astute.yaml')
247         fuel = {}
248         del data['ADMIN_NETWORK']['mac']
249         del data['ADMIN_NETWORK']['interface']
250         for key in ['ADMIN_NETWORK', 'HOSTNAME', 'DNS_DOMAIN', 'DNS_SEARCH',
251                     'DNS_UPSTREAM', 'NTP1', 'NTP2', 'NTP3', 'FUEL_ACCESS']:
252             fuel[key] = data[key]
253         for key in fuel['ADMIN_NETWORK'].keys():
254             if key not in ['ipaddress', 'netmask',
255                            'dhcp_pool_start', 'dhcp_pool_end', 'ssh_network']:
256                 del fuel['ADMIN_NETWORK'][key]
257
258         ## FIXME(armband): Factor in support for adding public/other interfaces.
259         ## TODO: Following block expects interface name(s) to be lowercase only
260         interfaces_list = exec_cmd('ip -o -4 a | grep -e "e[nt][hopsx].*"')
261         for interface in re.split('\n', interfaces_list):
262             # Sample output line from above cmd:
263             # 3: eth1 inet 10.0.2.10/24 scope global eth1 valid_lft forever ...
264             ifcfg = re.split(r'\s+', interface)
265             ifcfg_name = ifcfg[1]
266             ifcfg_ipaddr = ifcfg[3]
267
268             # Filter out admin interface (device name is not known, match IP)
269             current_network = netaddr.IPNetwork(ifcfg_ipaddr)
270             if str(current_network.ip) == fuel['ADMIN_NETWORK']['ipaddress']:
271                 continue
272
273             # Read ifcfg-* network interface config file, write IFCFG_<IFNAME>
274             ifcfg_sec = 'IFCFG_%s' % ifcfg_name.upper()
275             fuel[ifcfg_sec] = {}
276             ifcfg_data = {}
277             ifcfg_f = ('/etc/sysconfig/network-scripts/ifcfg-%s' % ifcfg_name)
278             with open(ifcfg_f) as f:
279                 for line in f:
280                     if line.startswith('#'):
281                         continue
282                     (key, val) = line.split('=')
283                     ifcfg_data[key.lower()] = val.rstrip()
284
285             # Keep only needed info (e.g. filter-out type=Ethernet).
286             fuel[ifcfg_sec]['ipaddress'] = ifcfg_data['ipaddr']
287             fuel[ifcfg_sec]['device'] = ifcfg_data['device']
288             fuel[ifcfg_sec]['netmask'] = str(current_network.netmask)
289             fuel[ifcfg_sec]['gateway'] = ifcfg_data['gateway']
290
291         self.write_yaml(self.dea_file, {'fuel': fuel})
292
293     def reap_network_settings(self):
294         network_file = ('%s/network_%s.yaml'
295                         % (self.temp_dir, self.env_id))
296         data = self.read_yaml(network_file)
297         network = {}
298         network['networking_parameters'] = data['networking_parameters']
299         network['networks'] = data['networks']
300         for net in network['networks']:
301             del net['id']
302             del net['group_id']
303         self.write_yaml(self.dea_file, {'network': network})
304
305     def reap_settings(self):
306         settings_file = '%s/settings_%s.yaml' % (self.temp_dir, self.env_id)
307         settings = self.read_yaml(settings_file)
308         self.write_yaml(self.dea_file, {'settings': settings})
309
310     def get_interface(self, real_node_id):
311         exec_cmd('fuel node --node-id %s --network --download --dir %s'
312                  % (real_node_id, self.temp_dir))
313         interface_file = ('%s/node_%s/interfaces.yaml'
314                           % (self.temp_dir, real_node_id))
315         interfaces = self.read_yaml(interface_file)
316         interface_config = {}
317         pxe_mac = None
318         for interface in interfaces:
319             networks = []
320             for network in interface['assigned_networks']:
321                 networks.append(network['name'])
322                 if network['name'] == 'fuelweb_admin':
323                     pxe_mac = interface['mac']
324             if networks:
325                 interface_config[interface['name']] = networks
326         return interface_config, pxe_mac
327
328     def read_yaml(self, yaml_file):
329         with open(yaml_file) as f:
330             data = yaml.load(f)
331             return data
332
333     def intro(self):
334         delete(self.dea_file)
335         delete(self.dha_file)
336
337         self.temp_dir = tempfile.mkdtemp()
338         date = time.strftime('%c')
339         self.write(self.dea_file,
340                    DEA_1.format(date=date, comment=self.comment), False)
341         self.write(self.dha_file,
342                    DHA_1.format(date=date, comment=self.comment))
343         self.get_env()
344
345         # Need to download deployment with explicit node ids
346         node_list = parse(exec_cmd('fuel node'))
347         real_node_ids = [node[N['id']] for node in node_list]
348         real_node_ids.sort()
349         self.download_node_config(','.join(real_node_ids))
350
351         self.download_config('settings')
352         self.download_config('network')
353
354     def create_base_dea(self):
355         templater = templater.Templater(self.dea_file,
356                                         self.template,
357                                         self.base_dea)
358         templater.run()
359
360     def finale(self):
361         log('DEA file is available at %s' % self.dea_file)
362         log('DHA file is available at %s (this is just a template)'
363             % self.dha_file)
364         if self.base_dea:
365             log('DEA base file is available at %s' % self.base_dea)
366         shutil.rmtree(self.temp_dir)
367
368     def reap(self):
369         self.intro()
370         self.reap_environment_info()
371         self.reap_nodes_interfaces_transformations()
372         self.reap_fuel_settings()
373         self.reap_network_settings()
374         self.reap_settings()
375         if self.base_dea:
376             self.create_base_dea()
377         self.finale()
378
379
380 def parse_arguments():
381     parser = ArgParser(prog='python %s' % __file__)
382     parser.add_argument('dea_file', nargs='?', action='store',
383                         default='dea.yaml',
384                         help='Deployment Environment Adapter: dea.yaml')
385     parser.add_argument('dha_file', nargs='?', action='store',
386                         default='dha.yaml',
387                         help='Deployment Hardware Adapter: dha.yaml')
388     parser.add_argument('comment', nargs='?', action='store', help='Comment')
389     parser.add_argument('-base_dea',
390                         dest='base_dea',
391                         help='Create specified base DEA file from "dea_file"')
392     parser.add_argument('-template',
393                         dest='template',
394                         nargs='?',
395                         default='base_dea_template.yaml',
396                         help='Base DEA is generated from this template')
397     args = parser.parse_args()
398     return (args.dea_file,
399             args.dha_file,
400             args.comment,
401             args.base_dea,
402             args.template)
403
404
405 def main():
406     dea_file, dha_file, comment, base_dea, template = parse_arguments()
407
408     r = Reap(dea_file, dha_file, comment, base_dea, template)
409     r.reap()
410
411
412 if __name__ == '__main__':
413     main()