Merge "Use Fuel 8 CLI compatible commands" into stable/brahmaputra
[fuel.git] / deploy / deploy.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 os
13 import io
14 import re
15 import sys
16 import netaddr
17 import yaml
18
19 from dea import DeploymentEnvironmentAdapter
20 from dha import DeploymentHardwareAdapter
21 from install_fuel_master import InstallFuelMaster
22 from deploy_env import CloudDeploy
23 from execution_environment import ExecutionEnvironment
24
25 from common import (
26     log,
27     exec_cmd,
28     err,
29     warn,
30     check_file_exists,
31     create_dir_if_not_exists,
32     delete,
33     check_if_root,
34     ArgParser,
35 )
36
37 FUEL_VM = 'fuel'
38 PATCH_DIR = 'fuel_patch'
39 WORK_DIR = '~/deploy'
40 CWD = os.getcwd()
41
42
43 class cd:
44
45     def __init__(self, new_path):
46         self.new_path = os.path.expanduser(new_path)
47
48     def __enter__(self):
49         self.saved_path = CWD
50         os.chdir(self.new_path)
51
52     def __exit__(self, etype, value, traceback):
53         os.chdir(self.saved_path)
54
55
56 class AutoDeploy(object):
57
58     def __init__(self, no_fuel, fuel_only, no_health_check, cleanup_only,
59                  cleanup, storage_dir, pxe_bridge, iso_file, dea_file,
60                  dha_file, fuel_plugins_dir, fuel_plugins_conf_dir,
61                  no_plugins):
62         self.no_fuel = no_fuel
63         self.fuel_only = fuel_only
64         self.no_health_check = no_health_check
65         self.cleanup_only = cleanup_only
66         self.cleanup = cleanup
67         self.storage_dir = storage_dir
68         self.pxe_bridge = pxe_bridge
69         self.iso_file = iso_file
70         self.dea_file = dea_file
71         self.dha_file = dha_file
72         self.fuel_plugins_dir = fuel_plugins_dir
73         self.fuel_plugins_conf_dir = fuel_plugins_conf_dir
74         self.no_plugins = no_plugins
75         self.dea = (DeploymentEnvironmentAdapter(dea_file)
76                     if not cleanup_only else None)
77         self.dha = DeploymentHardwareAdapter(dha_file)
78         self.fuel_conf = {}
79         self.fuel_node_id = self.dha.get_fuel_node_id()
80         self.fuel_username, self.fuel_password = self.dha.get_fuel_access()
81         self.tmp_dir = None
82
83     def modify_ip(self, ip_addr, index, val):
84         ip_str = str(netaddr.IPAddress(ip_addr))
85         decimal_list = map(int, ip_str.split('.'))
86         decimal_list[index] = val
87         return '.'.join(map(str, decimal_list))
88
89     def collect_fuel_info(self):
90         self.fuel_conf['ip'] = self.dea.get_fuel_ip()
91         self.fuel_conf['gw'] = self.dea.get_fuel_gateway()
92         self.fuel_conf['dns1'] = self.dea.get_fuel_dns()
93         self.fuel_conf['netmask'] = self.dea.get_fuel_netmask()
94         self.fuel_conf['hostname'] = self.dea.get_fuel_hostname()
95         self.fuel_conf['showmenu'] = 'yes'
96
97     def install_fuel_master(self):
98         log('Install Fuel Master')
99         new_iso = ('%s/deploy-%s'
100                    % (self.tmp_dir, os.path.basename(self.iso_file)))
101         self.patch_iso(new_iso)
102         self.iso_file = new_iso
103         self.install_iso()
104
105     def install_iso(self):
106         fuel = InstallFuelMaster(self.dea_file, self.dha_file,
107                                  self.fuel_conf['ip'], self.fuel_username,
108                                  self.fuel_password, self.fuel_node_id,
109                                  self.iso_file, WORK_DIR,
110                                  self.fuel_plugins_dir, self.no_plugins)
111         fuel.install()
112
113     def patch_iso(self, new_iso):
114         tmp_orig_dir = '%s/origiso' % self.tmp_dir
115         tmp_new_dir = '%s/newiso' % self.tmp_dir
116         try:
117             self.copy(tmp_orig_dir, tmp_new_dir)
118             self.patch(tmp_new_dir, new_iso)
119         except Exception as e:
120             exec_cmd('fusermount -u %s' % tmp_orig_dir, False)
121             delete(self.tmp_dir)
122             err(e)
123
124     def copy(self, tmp_orig_dir, tmp_new_dir):
125         log('Copying...')
126         os.makedirs(tmp_orig_dir)
127         os.makedirs(tmp_new_dir)
128         exec_cmd('fuseiso %s %s' % (self.iso_file, tmp_orig_dir))
129         with cd(tmp_orig_dir):
130             exec_cmd('find . | cpio -pd %s' % tmp_new_dir)
131         exec_cmd('fusermount -u %s' % tmp_orig_dir)
132         delete(tmp_orig_dir)
133         exec_cmd('chmod -R 755 %s' % tmp_new_dir)
134
135     def patch(self, tmp_new_dir, new_iso):
136         log('Patching...')
137         patch_dir = '%s/%s' % (CWD, PATCH_DIR)
138         ks_path = '%s/ks.cfg.patch' % patch_dir
139
140         with cd(tmp_new_dir):
141             exec_cmd('cat %s | patch -p0' % ks_path)
142             delete('.rr_moved')
143             isolinux = 'isolinux/isolinux.cfg'
144             log('isolinux.cfg before: %s'
145                 % exec_cmd('grep netmask %s' % isolinux))
146             self.update_fuel_isolinux(isolinux)
147             log('isolinux.cfg after: %s'
148                 % exec_cmd('grep netmask %s' % isolinux))
149
150             iso_label = self.parse_iso_volume_label(self.iso_file)
151             log('Volume label: %s' % iso_label)
152
153             iso_linux_bin = 'isolinux/isolinux.bin'
154             exec_cmd('mkisofs -quiet -r -J -R -b %s '
155                      '-no-emul-boot -boot-load-size 4 '
156                      '-boot-info-table -hide-rr-moved '
157                      '-x "lost+found:" -V %s -o %s .'
158                      % (iso_linux_bin, iso_label, new_iso))
159
160     def update_fuel_isolinux(self, file):
161         with io.open(file) as f:
162             data = f.read()
163         for key, val in self.fuel_conf.iteritems():
164             pattern = r'%s=[^ ]\S+' % key
165             replace = '%s=%s' % (key, val)
166             data = re.sub(pattern, replace, data)
167
168         netmask = self.fuel_conf['netmask']
169         data = self.append_kernel_param(data, 'netmask=%s' % netmask)
170
171         with io.open(file, 'w') as f:
172             f.write(data)
173
174     def append_kernel_param(self, data, kernel_param):
175         """Append the specified kernel parameter to a list of kernel
176         parameters. Do it only if it isn't already there.
177         """
178         data_final = ''
179         key = re.match(r'(.+?=)', kernel_param).group()
180
181         for line in data.splitlines():
182             data_final += line
183             if (re.search(r'append ', line) and
184                 not re.search(key, line)):
185                 data_final += ' ' + kernel_param
186             data_final += '\n'
187
188         return data_final
189
190     def parse_iso_volume_label(self, iso_filename):
191         label_line = exec_cmd('isoinfo -d -i %s | grep -i "Volume id: "' % iso_filename)
192         # cut leading text: 'Volume id: '
193         return label_line[11:]
194
195     def deploy_env(self):
196         dep = CloudDeploy(self.dea, self.dha, self.fuel_conf['ip'],
197                           self.fuel_username, self.fuel_password,
198                           self.dea_file, self.fuel_plugins_conf_dir,
199                           WORK_DIR, self.no_health_check)
200         return dep.deploy()
201
202     def setup_execution_environment(self):
203         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
204                                         self.dha_file, self.dea)
205         exec_env.setup_environment()
206
207     def cleanup_execution_environment(self):
208         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
209                                         self.dha_file, self.dea)
210         exec_env.cleanup_environment()
211
212     def create_tmp_dir(self):
213         self.tmp_dir = '%s/fueltmp' % CWD
214         delete(self.tmp_dir)
215         create_dir_if_not_exists(self.tmp_dir)
216
217     def deploy(self):
218         self.collect_fuel_info()
219         if not self.no_fuel:
220             self.setup_execution_environment()
221             self.create_tmp_dir()
222             self.install_fuel_master()
223         if not self.fuel_only:
224             return self.deploy_env()
225         return True
226
227     def run(self):
228         check_if_root()
229         if self.cleanup_only:
230             self.cleanup_execution_environment()
231         else:
232             deploy_success = self.deploy()
233             if self.cleanup:
234                 self.cleanup_execution_environment()
235             return deploy_success
236         return True
237
238 def check_bridge(pxe_bridge, dha_path):
239     with io.open(dha_path) as yaml_file:
240         dha_struct = yaml.load(yaml_file)
241     if dha_struct['adapter'] != 'libvirt':
242         log('Using Linux Bridge %s for booting up the Fuel Master VM'
243             % pxe_bridge)
244         r = exec_cmd('ip link show %s' % pxe_bridge)
245         if pxe_bridge in r and 'state DOWN' in r:
246             err('Linux Bridge {0} is not Active, bring'
247                 ' it UP first: [ip link set dev {0} up]'.format(pxe_bridge))
248
249
250 def check_fuel_plugins_dir(dir):
251     msg = None
252     if not dir:
253         msg = 'Fuel Plugins Directory not specified!'
254     elif not os.path.isdir(dir):
255         msg = 'Fuel Plugins Directory does not exist!'
256     elif not os.listdir(dir):
257         msg = 'Fuel Plugins Directory is empty!'
258     if msg:
259         warn('%s No external plugins will be installed!' % msg)
260
261
262 def parse_arguments():
263     parser = ArgParser(prog='python %s' % __file__)
264     parser.add_argument('-nf', dest='no_fuel', action='store_true',
265                         default=False,
266                         help='Do not install Fuel Master (and Node VMs when '
267                              'using libvirt)')
268     parser.add_argument('-nh', dest='no_health_check', action='store_true',
269                         default=False,
270                         help='Don\'t run health check after deployment')
271     parser.add_argument('-fo', dest='fuel_only', action='store_true',
272                         default=False,
273                         help='Install Fuel Master only (and Node VMs when '
274                              'using libvirt)')
275     parser.add_argument('-co', dest='cleanup_only', action='store_true',
276                         default=False,
277                         help='Cleanup VMs and Virtual Networks according to '
278                              'what is defined in DHA')
279     parser.add_argument('-c', dest='cleanup', action='store_true',
280                         default=False,
281                         help='Cleanup after deploy')
282     if {'-iso', '-dea', '-dha', '-h'}.intersection(sys.argv):
283         parser.add_argument('-iso', dest='iso_file', action='store', nargs='?',
284                             default='%s/OPNFV.iso' % CWD,
285                             help='ISO File [default: OPNFV.iso]')
286         parser.add_argument('-dea', dest='dea_file', action='store', nargs='?',
287                             default='%s/dea.yaml' % CWD,
288                             help='Deployment Environment Adapter: dea.yaml')
289         parser.add_argument('-dha', dest='dha_file', action='store', nargs='?',
290                             default='%s/dha.yaml' % CWD,
291                             help='Deployment Hardware Adapter: dha.yaml')
292     else:
293         parser.add_argument('iso_file', action='store', nargs='?',
294                             default='%s/OPNFV.iso' % CWD,
295                             help='ISO File [default: OPNFV.iso]')
296         parser.add_argument('dea_file', action='store', nargs='?',
297                             default='%s/dea.yaml' % CWD,
298                             help='Deployment Environment Adapter: dea.yaml')
299         parser.add_argument('dha_file', action='store', nargs='?',
300                             default='%s/dha.yaml' % CWD,
301                             help='Deployment Hardware Adapter: dha.yaml')
302     parser.add_argument('-s', dest='storage_dir', action='store',
303                         default='%s/images' % CWD,
304                         help='Storage Directory [default: images]')
305     parser.add_argument('-b', dest='pxe_bridge', action='store',
306                         default='pxebr',
307                         help='Linux Bridge for booting up the Fuel Master VM '
308                              '[default: pxebr]')
309     parser.add_argument('-p', dest='fuel_plugins_dir', action='store',
310                         help='Fuel Plugins directory')
311     parser.add_argument('-pc', dest='fuel_plugins_conf_dir', action='store',
312                         help='Fuel Plugins Configuration directory')
313     parser.add_argument('-np', dest='no_plugins', action='store_true',
314                         default=False, help='Do not install Fuel Plugins')
315
316     args = parser.parse_args()
317     log(args)
318
319     check_file_exists(args.dha_file)
320
321     if not args.cleanup_only:
322         check_file_exists(args.dea_file)
323         check_fuel_plugins_dir(args.fuel_plugins_dir)
324
325     if not args.no_fuel and not args.cleanup_only:
326         log('Using OPNFV ISO file: %s' % args.iso_file)
327         check_file_exists(args.iso_file)
328         log('Using image directory: %s' % args.storage_dir)
329         create_dir_if_not_exists(args.storage_dir)
330         check_bridge(args.pxe_bridge, args.dha_file)
331
332     kwargs = {'no_fuel': args.no_fuel, 'fuel_only': args.fuel_only,
333               'no_health_check': args.no_health_check,
334               'cleanup_only': args.cleanup_only, 'cleanup': args.cleanup,
335               'storage_dir': args.storage_dir, 'pxe_bridge': args.pxe_bridge,
336               'iso_file': args.iso_file, 'dea_file': args.dea_file,
337               'dha_file': args.dha_file,
338               'fuel_plugins_dir': args.fuel_plugins_dir,
339               'fuel_plugins_conf_dir': args.fuel_plugins_conf_dir,
340               'no_plugins': args.no_plugins}
341     return kwargs
342
343
344 def main():
345     kwargs = parse_arguments()
346     d = AutoDeploy(**kwargs)
347     sys.exit(d.run())
348
349 if __name__ == '__main__':
350     main()