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