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