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