Merge "New verified version of OpenDaylight" 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             # skip replacing these keys, as the format is custom
165             if key in ['ip', 'gw', 'netmask', 'hostname']:
166                 continue
167
168             pattern = r'%s=[^ ]\S+' % key
169             replace = '%s=%s' % (key, val)
170             data = re.sub(pattern, replace, data)
171
172         # process networking parameters
173         ip = ':'.join([self.fuel_conf['ip'],
174                       '',
175                       self.fuel_conf['gw'],
176                       self.fuel_conf['netmask'],
177                       self.fuel_conf['hostname'],
178                       'eth0:off:::'])
179
180         data = re.sub(r'ip=[^ ]\S+', 'ip=%s' % ip, data)
181
182         netmask = self.fuel_conf['netmask']
183         data = self.append_kernel_param(data, 'netmask=%s' % netmask)
184
185         with io.open(file, 'w') as f:
186             f.write(data)
187
188     def append_kernel_param(self, data, kernel_param):
189         """Append the specified kernel parameter to a list of kernel
190         parameters. Do it only if it isn't already there.
191         """
192         data_final = ''
193         key = re.match(r'(.+?=)', kernel_param).group()
194
195         for line in data.splitlines():
196             data_final += line
197             if (re.search(r'append ', line) and
198                 not re.search(key, line)):
199                 data_final += ' ' + kernel_param
200             data_final += '\n'
201
202         return data_final
203
204     def parse_iso_volume_label(self, iso_filename):
205         label_line = exec_cmd('isoinfo -d -i %s | grep -i "Volume id: "' % iso_filename)
206         # cut leading text: 'Volume id: '
207         return label_line[11:]
208
209     def deploy_env(self):
210         dep = CloudDeploy(self.dea, self.dha, self.fuel_conf['ip'],
211                           self.fuel_username, self.fuel_password,
212                           self.dea_file, self.fuel_plugins_conf_dir,
213                           WORK_DIR, self.no_health_check)
214         return dep.deploy()
215
216     def setup_execution_environment(self):
217         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
218                                         self.dha_file, self.dea)
219         exec_env.setup_environment()
220
221     def cleanup_execution_environment(self):
222         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
223                                         self.dha_file, self.dea)
224         exec_env.cleanup_environment()
225
226     def create_tmp_dir(self):
227         self.tmp_dir = '%s/fueltmp' % CWD
228         delete(self.tmp_dir)
229         create_dir_if_not_exists(self.tmp_dir)
230
231     def deploy(self):
232         self.collect_fuel_info()
233         if not self.no_fuel:
234             self.setup_execution_environment()
235             self.create_tmp_dir()
236             self.install_fuel_master()
237         if not self.fuel_only:
238             return self.deploy_env()
239         return True
240
241     def run(self):
242         check_if_root()
243         if self.cleanup_only:
244             self.cleanup_execution_environment()
245         else:
246             deploy_success = self.deploy()
247             if self.cleanup:
248                 self.cleanup_execution_environment()
249             return deploy_success
250         return True
251
252 def check_bridge(pxe_bridge, dha_path):
253     with io.open(dha_path) as yaml_file:
254         dha_struct = yaml.load(yaml_file)
255     if dha_struct['adapter'] != 'libvirt':
256         log('Using Linux Bridge %s for booting up the Fuel Master VM'
257             % pxe_bridge)
258         r = exec_cmd('ip link show %s' % pxe_bridge)
259         if pxe_bridge in r and 'state DOWN' in r:
260             err('Linux Bridge {0} is not Active, bring'
261                 ' it UP first: [ip link set dev {0} up]'.format(pxe_bridge))
262
263
264 def check_fuel_plugins_dir(dir):
265     msg = None
266     if not dir:
267         msg = 'Fuel Plugins Directory not specified!'
268     elif not os.path.isdir(dir):
269         msg = 'Fuel Plugins Directory does not exist!'
270     elif not os.listdir(dir):
271         msg = 'Fuel Plugins Directory is empty!'
272     if msg:
273         warn('%s No external plugins will be installed!' % msg)
274
275
276 def parse_arguments():
277     parser = ArgParser(prog='python %s' % __file__)
278     parser.add_argument('-nf', dest='no_fuel', action='store_true',
279                         default=False,
280                         help='Do not install Fuel Master (and Node VMs when '
281                              'using libvirt)')
282     parser.add_argument('-nh', dest='no_health_check', action='store_true',
283                         default=False,
284                         help='Don\'t run health check after deployment')
285     parser.add_argument('-fo', dest='fuel_only', action='store_true',
286                         default=False,
287                         help='Install Fuel Master only (and Node VMs when '
288                              'using libvirt)')
289     parser.add_argument('-co', dest='cleanup_only', action='store_true',
290                         default=False,
291                         help='Cleanup VMs and Virtual Networks according to '
292                              'what is defined in DHA')
293     parser.add_argument('-c', dest='cleanup', action='store_true',
294                         default=False,
295                         help='Cleanup after deploy')
296     if {'-iso', '-dea', '-dha', '-h'}.intersection(sys.argv):
297         parser.add_argument('-iso', dest='iso_file', action='store', nargs='?',
298                             default='%s/OPNFV.iso' % CWD,
299                             help='ISO File [default: OPNFV.iso]')
300         parser.add_argument('-dea', dest='dea_file', action='store', nargs='?',
301                             default='%s/dea.yaml' % CWD,
302                             help='Deployment Environment Adapter: dea.yaml')
303         parser.add_argument('-dha', dest='dha_file', action='store', nargs='?',
304                             default='%s/dha.yaml' % CWD,
305                             help='Deployment Hardware Adapter: dha.yaml')
306     else:
307         parser.add_argument('iso_file', action='store', nargs='?',
308                             default='%s/OPNFV.iso' % CWD,
309                             help='ISO File [default: OPNFV.iso]')
310         parser.add_argument('dea_file', action='store', nargs='?',
311                             default='%s/dea.yaml' % CWD,
312                             help='Deployment Environment Adapter: dea.yaml')
313         parser.add_argument('dha_file', action='store', nargs='?',
314                             default='%s/dha.yaml' % CWD,
315                             help='Deployment Hardware Adapter: dha.yaml')
316     parser.add_argument('-s', dest='storage_dir', action='store',
317                         default='%s/images' % CWD,
318                         help='Storage Directory [default: images]')
319     parser.add_argument('-b', dest='pxe_bridge', action='store',
320                         default='pxebr',
321                         help='Linux Bridge for booting up the Fuel Master VM '
322                              '[default: pxebr]')
323     parser.add_argument('-p', dest='fuel_plugins_dir', action='store',
324                         help='Fuel Plugins directory')
325     parser.add_argument('-pc', dest='fuel_plugins_conf_dir', action='store',
326                         help='Fuel Plugins Configuration directory')
327     parser.add_argument('-np', dest='no_plugins', action='store_true',
328                         default=False, help='Do not install Fuel Plugins')
329
330     args = parser.parse_args()
331     log(args)
332
333     check_file_exists(args.dha_file)
334
335     if not args.cleanup_only:
336         check_file_exists(args.dea_file)
337         check_fuel_plugins_dir(args.fuel_plugins_dir)
338
339     if not args.no_fuel and not args.cleanup_only:
340         log('Using OPNFV ISO file: %s' % args.iso_file)
341         check_file_exists(args.iso_file)
342         log('Using image directory: %s' % args.storage_dir)
343         create_dir_if_not_exists(args.storage_dir)
344         check_bridge(args.pxe_bridge, args.dha_file)
345
346     kwargs = {'no_fuel': args.no_fuel, 'fuel_only': args.fuel_only,
347               'no_health_check': args.no_health_check,
348               'cleanup_only': args.cleanup_only, 'cleanup': args.cleanup,
349               'storage_dir': args.storage_dir, 'pxe_bridge': args.pxe_bridge,
350               'iso_file': args.iso_file, 'dea_file': args.dea_file,
351               'dha_file': args.dha_file,
352               'fuel_plugins_dir': args.fuel_plugins_dir,
353               'fuel_plugins_conf_dir': args.fuel_plugins_conf_dir,
354               'no_plugins': args.no_plugins}
355     return kwargs
356
357
358 def main():
359     kwargs = parse_arguments()
360     d = AutoDeploy(**kwargs)
361     sys.exit(d.run())
362
363 if __name__ == '__main__':
364     main()