upgrade mode was not working; CheckForNewDisks was erroneously taking the disk as...
[bootmanager.git] / source / steps / InstallBootstrapFS.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2003 Intel Corporation
4 # All rights reserved.
5 #
6 # Copyright (c) 2004-2007 The Trustees of Princeton University
7 # All rights reserved.
8 # expected /proc/partitions format
9
10 import os, string
11 import popen2
12 import shutil
13 import traceback 
14 import time
15
16 from Exceptions import *
17 import utils
18 import systeminfo
19 import BootServerRequest
20 import BootAPI
21
22
23 def Run(vars, upgrade, log):
24     """
25     Download core + extensions bootstrapfs tarballs and install on the hard drive
26
27     the upgrade boolean is True when we are upgrading a node root install while 
28     preserving its slice contents; in that case we just perform extra cleanup
29     before unwrapping the bootstrapfs
30     this is because the running system may have extraneous files
31     that is to say, files that are *not* present in the bootstrapfs
32     and that can impact/clobber the resulting upgrade
33     
34     Expect the following variables from the store:
35     SYSIMG_PATH          the path where the system image will be mounted
36     PARTITIONS           dictionary of generic part. types (root/swap)
37                          and their associated devices.
38     NODE_ID              the id of this machine
39     
40     Sets the following variables:
41     TEMP_BOOTCD_PATH     where the boot cd is remounted in the temp
42                          path
43     ROOT_MOUNTED         set to 1 when the the base logical volumes
44                          are mounted.
45     """
46
47     log.write("\n\nStep: Install: bootstrapfs tarball (upgrade={}).\n".format(upgrade))
48
49     # make sure we have the variables we need
50     try:
51         SYSIMG_PATH = vars["SYSIMG_PATH"]
52         if SYSIMG_PATH == "":
53             raise ValueError("SYSIMG_PATH")
54
55         PARTITIONS = vars["PARTITIONS"]
56         if PARTITIONS == None:
57             raise ValueError("PARTITIONS")
58
59         NODE_ID = vars["NODE_ID"]
60         if NODE_ID == "":
61             raise ValueError("NODE_ID")
62
63         VERSION = vars['VERSION'] or 'unknown'
64
65     except KeyError as var:
66         raise BootManagerException("Missing variable in vars: {}\n".format(var))
67     except ValueError as var:
68         raise BootManagerException("Variable in vars, shouldn't be: {}\n".format(var))
69
70
71     try:
72         # make sure the required partitions exist
73         val = PARTITIONS["root"]
74         val = PARTITIONS["swap"]
75         val = PARTITIONS["vservers"]
76     except KeyError as part:
77         log.write("Missing partition in PARTITIONS: {}\n".format(part))
78         return 0   
79
80     bs_request = BootServerRequest.BootServerRequest(vars)
81     
82     # in upgrade mode, since we skip InstallPartitionDisks
83     # we need to run this
84     if upgrade:
85         log.write("Running vgscan for devices (upgrade mode)\n")
86         systeminfo.get_block_devices_dict(vars, log)
87         utils.sysexec_noerr("vgscan", log)
88         
89     log.write("turning on swap space\n")
90     utils.sysexec("swapon {}".format(PARTITIONS["swap"]), log)
91
92     # make sure the sysimg dir is present
93     utils.makedirs(SYSIMG_PATH)
94
95     log.write("mounting root file system\n")
96     utils.sysexec("mount -t ext3 {} {}".format(PARTITIONS["root"], SYSIMG_PATH), log)
97
98     fstype = 'ext3' if vars['virt']=='vs' else 'btrfs'
99
100     one_partition = vars['ONE_PARTITION']=='1'
101
102     if (not one_partition):
103         log.write("mounting vserver partition in root file system (type {})\n".format(fstype))
104         utils.makedirs(SYSIMG_PATH + "/vservers")
105         utils.sysexec("mount -t {} {} {}/vservers"\
106                       .format(fstype, PARTITIONS["vservers"], SYSIMG_PATH), log)
107
108         if vars['virt']=='lxc':
109             # NOTE: btrfs quota is supported from version: >= btrfs-progs-0.20 (f18+)
110             #       older versions will not recongize the 'quota' command.
111             log.write("Enabling btrfs quota on {}/vservers\n".format(SYSIMG_PATH))
112             utils.sysexec_noerr("btrfs quota enable {}/vservers".format(SYSIMG_PATH))
113
114     vars['ROOT_MOUNTED'] = 1
115
116     # this is now retrieved in GetAndUpdateNodeDetails
117     nodefamily = vars['nodefamily']
118     extensions = vars['extensions']
119
120     # in upgrade mode: we need to cleanup the disk to make
121     # it safe to just untar the new bootstrapfs tarball again
122     # on top of the hard drive
123     if upgrade:
124         CleanupSysimgBeforeUpgrade(SYSIMG_PATH, nodefamily, log)
125
126     # the 'plain' option is for tests mostly
127     plain = vars['plain']
128     if plain:
129         download_suffix = ".tar"
130         uncompress_option = ""
131         log.write("Using plain bootstrapfs images\n")
132     else:
133         download_suffix = ".tar.bz2"
134         uncompress_option = "-j"
135         log.write("Using compressed bootstrapfs images\n")
136
137     log.write ("Using nodefamily={}\n".format(nodefamily))
138     if not extensions:
139         log.write("Installing only core software\n")
140     else:
141         log.write("Requested extensions {}\n".format(extensions))
142     
143     bootstrapfs_names = [ nodefamily ] + extensions
144
145     for name in bootstrapfs_names:
146         tarball = "bootstrapfs-{}{}".format(name, download_suffix)
147         source_file = "/boot/{}".format(tarball)
148         dest_file = "{}/{}".format(SYSIMG_PATH, tarball)
149
150         source_hash_file = "/boot/{}.sha1sum".format(tarball)
151         dest_hash_file = "{}/{}.sha1sum".format(SYSIMG_PATH, tarball)
152
153         time_beg = time.time()
154         log.write("downloading {}\n".format(source_file))
155         # 30 is the connect timeout, 14400 is the max transfer time in
156         # seconds (4 hours)
157         result = bs_request.DownloadFile(source_file, None, None,
158                                          1, 1, dest_file,
159                                          30, 14400)
160         time_end = time.time()
161         duration = int(time_end - time_beg)
162         log.write("Done downloading ({} seconds)\n".format(duration))
163         if result:
164             # Download SHA1 checksum file
165             log.write("downloading sha1sum for {}\n".format(source_file))
166             result = bs_request.DownloadFile(source_hash_file, None, None,
167                                              1, 1, dest_hash_file,
168                                              30, 14400)
169  
170             log.write("verifying sha1sum for {}\n".format(source_file))
171             if not utils.check_file_hash(dest_file, dest_hash_file):
172                 raise BootManagerException(
173                     "FATAL: SHA1 checksum does not match between {} and {}"\
174                     .format(source_file, source_hash_file))
175                 
176             
177             time_beg = time.time()
178             log.write("extracting {} in {}\n".format(dest_file, SYSIMG_PATH))
179             result = utils.sysexec("tar -C {} -xpf {} {}".format(SYSIMG_PATH, dest_file, uncompress_option), log)
180             time_end = time.time()
181             duration = int(time_end - time_beg)
182             log.write("Done extracting ({} seconds)\n".format(duration))
183             utils.removefile(dest_file)
184         else:
185             # the main tarball is required
186             if name == nodefamily:
187                 raise BootManagerException(
188                     "FATAL: Unable to download main tarball {} from server."\
189                     .format(source_file))
190             # for extensions, just issue a warning
191             else:
192                 log.write("WARNING: tarball for extension {} not found\n".format(name))
193
194     # copy resolv.conf from the base system into our temp dir
195     # so DNS lookups work correctly while we are chrooted
196     log.write("Copying resolv.conf to temp dir\n")
197     utils.sysexec("cp /etc/resolv.conf {}/etc/".format(SYSIMG_PATH), log)
198
199     # Copy the boot server certificate(s) and GPG public key to
200     # /usr/boot in the temp dir.
201     log.write("Copying boot server certificates and public key\n")
202
203     if os.path.exists("/usr/boot"):
204         # do nothing in case of upgrade
205         if not os.path.exists(SYSIMG_PATH + "/usr/boot"):
206             utils.makedirs(SYSIMG_PATH + "/usr")
207             shutil.copytree("/usr/boot", SYSIMG_PATH + "/usr/boot")
208     elif os.path.exists("/usr/bootme"):
209         # do nothing in case of upgrade
210         if not os.path.exists(SYSIMG_PATH + "/usr/bootme"):
211             utils.makedirs(SYSIMG_PATH + "/usr/boot")
212             boot_server = file("/usr/bootme/BOOTSERVER").readline().strip()
213             shutil.copy("/usr/bootme/cacert/" + boot_server + "/cacert.pem",
214                         SYSIMG_PATH + "/usr/boot/cacert.pem")
215             file(SYSIMG_PATH + "/usr/boot/boot_server", "w").write(boot_server)
216             shutil.copy("/usr/bootme/pubring.gpg", SYSIMG_PATH + "/usr/boot/pubring.gpg")
217         
218     # For backward compatibility
219     if os.path.exists("/usr/bootme"):
220         # do nothing in case of upgrade
221         if not os.path.exists(SYSIMG_PATH + "/mnt/cdrom/bootme"):
222             utils.makedirs(SYSIMG_PATH + "/mnt/cdrom")
223             shutil.copytree("/usr/bootme", SYSIMG_PATH + "/mnt/cdrom/bootme")
224
225     # ONE_PARTITION => new distribution type
226     if (vars['ONE_PARTITION'] != '1'):
227         # Import the GPG key into the RPM database so that RPMS can be verified
228         utils.makedirs(SYSIMG_PATH + "/etc/pki/rpm-gpg")
229         utils.sysexec("gpg --homedir=/root --export --armor"
230                       " --no-default-keyring --keyring {}/usr/boot/pubring.gpg"
231                       " > {}/etc/pki/rpm-gpg/RPM-GPG-KEY-planetlab".format(SYSIMG_PATH, SYSIMG_PATH), log)
232         utils.sysexec_chroot(SYSIMG_PATH, "rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-planetlab", log)
233
234     # keep a log on the installed hdd
235     stamp = file(SYSIMG_PATH + "/bm-install.txt", 'a')
236     now = time.strftime("%Y-%b-%d @ %H:%M %Z", time.gmtime())
237     stamp.write("Hard drive installed by BootManager {}\n".format(VERSION))
238     stamp.write("Finished extraction of bootstrapfs on {}\n".format(now))
239     # do not modify this, the upgrade code uses this line for checking compatibility
240     stamp.write("Using nodefamily {}\n".format(nodefamily))
241     stamp.close()
242
243     return 1
244
245 # the upgrade hook
246 def CleanupSysimgBeforeUpgrade(sysimg, target_nodefamily, log):
247
248     areas_to_cleanup = [
249         '/boot',
250         '/usr',
251         '/var',
252         '/etc',
253         '/run',
254         '/vsys',
255     ]
256
257     target_pldistro, target_fcdistro, target_arch = target_nodefamily.split('-')
258
259     # minimal check : not all configurations are possible...
260
261     installed_pldistro, installed_fcdistro, installed_arch = None, None, None
262     installed_virt = None
263     prefix = "Using nodefamily "
264     try:
265         with open("{}/bm-install.txt".format(sysimg)) as infile:
266             for line in infile:
267                 if line.startswith(prefix):
268                     installed_nodefamily = line.replace(prefix,"").strip()
269                     installed_pldistro, installed_fcdistro, installed_arch = installed_nodefamily.split('-')
270                     # do not break here, bm-install is additive, we want the last one..
271         with open("{}/etc/planetlab/virt".format(sysimg)) as infile:
272             installed_virt = infile.read().strip()
273     except Exception as e:
274         print_exc()
275         raise BootManagerException("Could not retrieve data about previous installation - cannot upgrade")
276
277     # moving from vservers to lxc also means another filesystem
278     # so plain reinstall is the only option
279     if installed_virt != 'lxc':
280         raise BootManagerException("Can only upgrade nodes running lxc containers (vservers not supported)")
281
282     # changing arch is not reasonable either
283     if target_arch != installed_arch:
284         raise BootManagerException("Cannot upgrade from arch={} to arch={}"
285                                    .format(installed_arch, target_arch))
286
287     if target_pldistro != installed_pldistro:
288         log.write("\nWARNING: upgrading across pldistros {} to {} - might not work well..\n"
289                   .format(installed_pldistro, target_pldistro))
290     
291     # otherwise at this point we do not do any more advanced checking
292     log.write("\n\nPseudo step CleanupSysimgBeforeUpgrade : cleaning up hard drive\n")
293     
294     for area in areas_to_cleanup:
295         utils.sysexec("rm -rf {}/{}".format(sysimg, area))