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