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