Merge "Reaping improvements for Fuel 7"
[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_linux_bin = 'isolinux/isolinux.bin'
151             exec_cmd('mkisofs -quiet -r -J -R -b %s '
152                      '-no-emul-boot -boot-load-size 4 '
153                      '-boot-info-table -hide-rr-moved '
154                      '-x "lost+found:" -o %s .'
155                      % (iso_linux_bin, new_iso))
156
157     def update_fuel_isolinux(self, file):
158         with io.open(file) as f:
159             data = f.read()
160         for key, val in self.fuel_conf.iteritems():
161             pattern = r'%s=[^ ]\S+' % key
162             replace = '%s=%s' % (key, val)
163             data = re.sub(pattern, replace, data)
164         with io.open(file, 'w') as f:
165             f.write(data)
166
167     def deploy_env(self):
168         dep = CloudDeploy(self.dea, self.dha, self.fuel_conf['ip'],
169                           self.fuel_username, self.fuel_password,
170                           self.dea_file, self.fuel_plugins_conf_dir,
171                           WORK_DIR, self.no_health_check)
172         return dep.deploy()
173
174     def setup_execution_environment(self):
175         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
176                                         self.dha_file, self.dea)
177         exec_env.setup_environment()
178
179     def cleanup_execution_environment(self):
180         exec_env = ExecutionEnvironment(self.storage_dir, self.pxe_bridge,
181                                         self.dha_file, self.dea)
182         exec_env.cleanup_environment()
183
184     def create_tmp_dir(self):
185         self.tmp_dir = '%s/fueltmp' % CWD
186         delete(self.tmp_dir)
187         create_dir_if_not_exists(self.tmp_dir)
188
189     def deploy(self):
190         self.collect_fuel_info()
191         if not self.no_fuel:
192             self.setup_execution_environment()
193             self.create_tmp_dir()
194             self.install_fuel_master()
195         if not self.fuel_only:
196             return self.deploy_env()
197         return True
198
199     def run(self):
200         check_if_root()
201         if self.cleanup_only:
202             self.cleanup_execution_environment()
203         else:
204             deploy_success = self.deploy()
205             if self.cleanup:
206                 self.cleanup_execution_environment()
207             return deploy_success
208         return True
209
210 def check_bridge(pxe_bridge, dha_path):
211     with io.open(dha_path) as yaml_file:
212         dha_struct = yaml.load(yaml_file)
213     if dha_struct['adapter'] != 'libvirt':
214         log('Using Linux Bridge %s for booting up the Fuel Master VM'
215             % pxe_bridge)
216         r = exec_cmd('ip link show %s' % pxe_bridge)
217         if pxe_bridge in r and 'state DOWN' in r:
218             err('Linux Bridge {0} is not Active, bring'
219                 ' it UP first: [ip link set dev {0} up]'.format(pxe_bridge))
220
221
222 def check_fuel_plugins_dir(dir):
223     msg = None
224     if not dir:
225         msg = 'Fuel Plugins Directory not specified!'
226     elif not os.path.isdir(dir):
227         msg = 'Fuel Plugins Directory does not exist!'
228     elif not os.listdir(dir):
229         msg = 'Fuel Plugins Directory is empty!'
230     if msg:
231         warn('%s No external plugins will be installed!' % msg)
232
233
234 def parse_arguments():
235     parser = ArgParser(prog='python %s' % __file__)
236     parser.add_argument('-nf', dest='no_fuel', action='store_true',
237                         default=False,
238                         help='Do not install Fuel Master (and Node VMs when '
239                              'using libvirt)')
240     parser.add_argument('-nh', dest='no_health_check', action='store_true',
241                         default=False,
242                         help='Don\'t run health check after deployment')
243     parser.add_argument('-fo', dest='fuel_only', action='store_true',
244                         default=False,
245                         help='Install Fuel Master only (and Node VMs when '
246                              'using libvirt)')
247     parser.add_argument('-co', dest='cleanup_only', action='store_true',
248                         default=False,
249                         help='Cleanup VMs and Virtual Networks according to '
250                              'what is defined in DHA')
251     parser.add_argument('-c', dest='cleanup', action='store_true',
252                         default=False,
253                         help='Cleanup after deploy')
254     if {'-iso', '-dea', '-dha', '-h'}.intersection(sys.argv):
255         parser.add_argument('-iso', dest='iso_file', action='store', nargs='?',
256                             default='%s/OPNFV.iso' % CWD,
257                             help='ISO File [default: OPNFV.iso]')
258         parser.add_argument('-dea', dest='dea_file', action='store', nargs='?',
259                             default='%s/dea.yaml' % CWD,
260                             help='Deployment Environment Adapter: dea.yaml')
261         parser.add_argument('-dha', dest='dha_file', action='store', nargs='?',
262                             default='%s/dha.yaml' % CWD,
263                             help='Deployment Hardware Adapter: dha.yaml')
264     else:
265         parser.add_argument('iso_file', action='store', nargs='?',
266                             default='%s/OPNFV.iso' % CWD,
267                             help='ISO File [default: OPNFV.iso]')
268         parser.add_argument('dea_file', action='store', nargs='?',
269                             default='%s/dea.yaml' % CWD,
270                             help='Deployment Environment Adapter: dea.yaml')
271         parser.add_argument('dha_file', action='store', nargs='?',
272                             default='%s/dha.yaml' % CWD,
273                             help='Deployment Hardware Adapter: dha.yaml')
274     parser.add_argument('-s', dest='storage_dir', action='store',
275                         default='%s/images' % CWD,
276                         help='Storage Directory [default: images]')
277     parser.add_argument('-b', dest='pxe_bridge', action='store',
278                         default='pxebr',
279                         help='Linux Bridge for booting up the Fuel Master VM '
280                              '[default: pxebr]')
281     parser.add_argument('-p', dest='fuel_plugins_dir', action='store',
282                         help='Fuel Plugins directory')
283     parser.add_argument('-pc', dest='fuel_plugins_conf_dir', action='store',
284                         help='Fuel Plugins Configuration directory')
285     parser.add_argument('-np', dest='no_plugins', action='store_true',
286                         default=False, help='Do not install Fuel Plugins')
287
288     args = parser.parse_args()
289     log(args)
290
291     check_file_exists(args.dha_file)
292
293     if not args.cleanup_only:
294         check_file_exists(args.dea_file)
295         check_fuel_plugins_dir(args.fuel_plugins_dir)
296
297     if not args.no_fuel and not args.cleanup_only:
298         log('Using OPNFV ISO file: %s' % args.iso_file)
299         check_file_exists(args.iso_file)
300         log('Using image directory: %s' % args.storage_dir)
301         create_dir_if_not_exists(args.storage_dir)
302         check_bridge(args.pxe_bridge, args.dha_file)
303
304     kwargs = {'no_fuel': args.no_fuel, 'fuel_only': args.fuel_only,
305               'no_health_check': args.no_health_check,
306               'cleanup_only': args.cleanup_only, 'cleanup': args.cleanup,
307               'storage_dir': args.storage_dir, 'pxe_bridge': args.pxe_bridge,
308               'iso_file': args.iso_file, 'dea_file': args.dea_file,
309               'dha_file': args.dha_file,
310               'fuel_plugins_dir': args.fuel_plugins_dir,
311               'fuel_plugins_conf_dir': args.fuel_plugins_conf_dir,
312               'no_plugins': args.no_plugins}
313     return kwargs
314
315
316 def main():
317     kwargs = parse_arguments()
318     d = AutoDeploy(**kwargs)
319     sys.exit(d.run())
320
321 if __name__ == '__main__':
322     main()