disabled btrfs quota because of a bug making the node gain a very high load
[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("Upgrade mode init : Scanning for devices\n")
86         systeminfo.get_block_devices_dict(vars, log)
87         utils.sysexec_noerr("vgscan --mknodes", log)
88         utils.sysexec_noerr("vgchange -ay", log)
89
90     # debugging info - show in either mode
91     utils.display_disks_status(PARTITIONS, "In InstallBootstrapFS", log)
92
93     utils.breakpoint("we need to make /dev/mapper/* appear")
94
95     log.write("turning on swap space\n")
96     utils.sysexec("swapon {}".format(PARTITIONS["swap"]), log)
97
98     # make sure the sysimg dir is present
99     utils.makedirs(SYSIMG_PATH)
100
101     log.write("mounting root file system\n")
102     utils.sysexec("mount -t ext3 {} {}".format(PARTITIONS["root"], SYSIMG_PATH), log)
103
104     fstype = 'ext3' if vars['virt']=='vs' else 'btrfs'
105
106     one_partition = vars['ONE_PARTITION']=='1'
107
108     if (not one_partition):
109         log.write("mounting vserver partition in root file system (type {})\n".format(fstype))
110         utils.makedirs(SYSIMG_PATH + "/vservers")
111         utils.sysexec("mount -t {} {} {}/vservers"\
112                       .format(fstype, PARTITIONS["vservers"], SYSIMG_PATH), log)
113
114         #if vars['virt']=='lxc':
115             # NOTE: btrfs quota is supported from version: >= btrfs-progs-0.20 (f18+)
116             #       older versions will not recongize the 'quota' command.
117             #log.write("Enabling btrfs quota on {}/vservers\n".format(SYSIMG_PATH))
118             #utils.sysexec_noerr("btrfs quota enable {}/vservers".format(SYSIMG_PATH))
119
120     vars['ROOT_MOUNTED'] = 1
121
122     # this is now retrieved in GetAndUpdateNodeDetails
123     nodefamily = vars['nodefamily']
124     extensions = vars['extensions']
125
126     # in upgrade mode: we need to cleanup the disk to make
127     # it safe to just untar the new bootstrapfs tarball again
128     # on top of the hard drive
129     if upgrade:
130         CleanupSysimgBeforeUpgrade(SYSIMG_PATH, nodefamily, log)
131
132     # the 'plain' option is for tests mostly
133     plain = vars['plain']
134     if plain:
135         download_suffix = ".tar"
136         uncompress_option = ""
137         log.write("Using plain bootstrapfs images\n")
138     else:
139         download_suffix = ".tar.bz2"
140         uncompress_option = "-j"
141         log.write("Using compressed bootstrapfs images\n")
142
143     log.write ("Using nodefamily={}\n".format(nodefamily))
144     if not extensions:
145         log.write("Installing only core software\n")
146     else:
147         log.write("Requested extensions {}\n".format(extensions))
148     
149     bootstrapfs_names = [ nodefamily ] + extensions
150
151     for name in bootstrapfs_names:
152         tarball = "bootstrapfs-{}{}".format(name, download_suffix)
153         source_file = "/boot/{}".format(tarball)
154         dest_file = "{}/{}".format(SYSIMG_PATH, tarball)
155
156         source_hash_file = "/boot/{}.sha1sum".format(tarball)
157         dest_hash_file = "{}/{}.sha1sum".format(SYSIMG_PATH, tarball)
158
159         time_beg = time.time()
160         log.write("downloading {}\n".format(source_file))
161         # 30 is the connect timeout, 14400 is the max transfer time in
162         # seconds (4 hours)
163         result = bs_request.DownloadFile(source_file, None, None,
164                                          1, 1, dest_file,
165                                          30, 14400)
166         time_end = time.time()
167         duration = int(time_end - time_beg)
168         log.write("Done downloading ({} seconds)\n".format(duration))
169         if result:
170             # Download SHA1 checksum file
171             log.write("downloading sha1sum for {}\n".format(source_file))
172             result = bs_request.DownloadFile(source_hash_file, None, None,
173                                              1, 1, dest_hash_file,
174                                              30, 14400)
175  
176             log.write("verifying sha1sum for {}\n".format(source_file))
177             if not utils.check_file_hash(dest_file, dest_hash_file):
178                 raise BootManagerException(
179                     "FATAL: SHA1 checksum does not match between {} and {}"\
180                     .format(source_file, source_hash_file))
181                 
182             
183             time_beg = time.time()
184             log.write("extracting {} in {}\n".format(dest_file, SYSIMG_PATH))
185             result = utils.sysexec("tar -C {} -xpf {} {}".format(SYSIMG_PATH, dest_file, uncompress_option), log)
186             time_end = time.time()
187             duration = int(time_end - time_beg)
188             log.write("Done extracting ({} seconds)\n".format(duration))
189             utils.removefile(dest_file)
190         else:
191             # the main tarball is required
192             if name == nodefamily:
193                 raise BootManagerException(
194                     "FATAL: Unable to download main tarball {} from server."\
195                     .format(source_file))
196             # for extensions, just issue a warning
197             else:
198                 log.write("WARNING: tarball for extension {} not found\n".format(name))
199
200     # copy resolv.conf from the base system into our temp dir
201     # so DNS lookups work correctly while we are chrooted
202     log.write("Copying resolv.conf to temp dir\n")
203     utils.sysexec("cp /etc/resolv.conf {}/etc/".format(SYSIMG_PATH), log)
204
205     # Copy the boot server certificate(s) and GPG public key to
206     # /usr/boot in the temp dir.
207     log.write("Copying boot server certificates and public key\n")
208
209     if os.path.exists("/usr/boot"):
210         # do nothing in case of upgrade
211         if not os.path.exists(SYSIMG_PATH + "/usr/boot"):
212             utils.makedirs(SYSIMG_PATH + "/usr")
213             shutil.copytree("/usr/boot", SYSIMG_PATH + "/usr/boot")
214     elif os.path.exists("/usr/bootme"):
215         # do nothing in case of upgrade
216         if not os.path.exists(SYSIMG_PATH + "/usr/bootme"):
217             utils.makedirs(SYSIMG_PATH + "/usr/boot")
218             boot_server = file("/usr/bootme/BOOTSERVER").readline().strip()
219             shutil.copy("/usr/bootme/cacert/" + boot_server + "/cacert.pem",
220                         SYSIMG_PATH + "/usr/boot/cacert.pem")
221             file(SYSIMG_PATH + "/usr/boot/boot_server", "w").write(boot_server)
222             shutil.copy("/usr/bootme/pubring.gpg", SYSIMG_PATH + "/usr/boot/pubring.gpg")
223         
224     # For backward compatibility
225     if os.path.exists("/usr/bootme"):
226         # do nothing in case of upgrade
227         if not os.path.exists(SYSIMG_PATH + "/mnt/cdrom/bootme"):
228             utils.makedirs(SYSIMG_PATH + "/mnt/cdrom")
229             shutil.copytree("/usr/bootme", SYSIMG_PATH + "/mnt/cdrom/bootme")
230
231     # ONE_PARTITION => new distribution type
232     if (vars['ONE_PARTITION'] != '1'):
233         # Import the GPG key into the RPM database so that RPMS can be verified
234         utils.makedirs(SYSIMG_PATH + "/etc/pki/rpm-gpg")
235         utils.sysexec("gpg --homedir=/root --export --armor"
236                       " --no-default-keyring --keyring {}/usr/boot/pubring.gpg"
237                       " > {}/etc/pki/rpm-gpg/RPM-GPG-KEY-planetlab".format(SYSIMG_PATH, SYSIMG_PATH), log)
238         utils.sysexec_chroot(SYSIMG_PATH, "rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-planetlab", log)
239
240     # keep a log on the installed hdd
241     stamp = file(SYSIMG_PATH + "/bm-install.txt", 'a')
242     now = time.strftime("%Y-%b-%d @ %H:%M %Z", time.gmtime())
243     stamp.write("Hard drive installed by BootManager {}\n".format(VERSION))
244     stamp.write("Finished extraction of bootstrapfs on {}\n".format(now))
245     # do not modify this, the upgrade code uses this line for checking compatibility
246     stamp.write("Using nodefamily {}\n".format(nodefamily))
247     stamp.close()
248
249     return 1
250
251 # the upgrade hook
252 def CleanupSysimgBeforeUpgrade(sysimg, target_nodefamily, log):
253
254     areas_to_cleanup = [
255         '/boot',
256         '/usr',
257         '/var',
258         '/etc',
259         '/run',
260         '/vsys',
261     ]
262
263     target_pldistro, target_fcdistro, target_arch = target_nodefamily.split('-')
264
265     # minimal check : not all configurations are possible...
266
267     installed_pldistro, installed_fcdistro, installed_arch = None, None, None
268     installed_virt = None
269     prefix = "Using nodefamily "
270     try:
271         with open("{}/bm-install.txt".format(sysimg)) as infile:
272             for line in infile:
273                 if line.startswith(prefix):
274                     installed_nodefamily = line.replace(prefix,"").strip()
275                     installed_pldistro, installed_fcdistro, installed_arch = installed_nodefamily.split('-')
276                     # do not break here, bm-install is additive, we want the last one..
277         with open("{}/etc/planetlab/virt".format(sysimg)) as infile:
278             installed_virt = infile.read().strip()
279     except Exception as e:
280         traceback.print_exc()
281         raise BootManagerException("Could not retrieve data about previous installation - cannot upgrade")
282
283     # moving from vservers to lxc also means another filesystem
284     # so plain reinstall is the only option
285     if installed_virt != 'lxc':
286         message = """Can only upgrade nodes already running lxc containers
287 a node running vservers has its /vservers/ partition formatted as ext3 
288 and we need btrfs to move to containers
289 your only option here is reinstall"""
290         raise BootManagerException(message)
291
292     # changing arch is not reasonable either
293     if target_arch != installed_arch:
294         raise BootManagerException("Cannot upgrade from arch={} to arch={}"
295                                    .format(installed_arch, target_arch))
296
297     if target_pldistro != installed_pldistro:
298         log.write("\nWARNING: upgrading across pldistros {} to {} - might not work well..\n"
299                   .format(installed_pldistro, target_pldistro))
300     
301     # otherwise at this point we do not do any more advanced checking
302     log.write("\n\nPseudo step CleanupSysimgBeforeUpgrade : cleaning up hard drive\n")
303     
304     for area in areas_to_cleanup:
305         utils.sysexec("rm -rf {}/{}".format(sysimg, area))