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