#!/usr/bin/python # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # (c) 2007 Red Hat. Written by skvidal@fedoraproject.org # TODO: break it out a little better for gui-ification # TODO: gpgcheck downloaded pkgs # TODO: conf-file-it # TODO: after it works get rid of need for yum-cli routines in yum-util # TODO: GTK/guification # TODO: probably have some defensive checks before we start anything # system sanity checks, depsolver checks against current rpmdb # TODO: deal with multiple repos getting bumped (think updates-released, livna) # TODO: deal with conflicts and try to walk around them # TODO: generate local metadata # TODO: figure out a way to tell anaconda to look over here for the packages import sys import os import os.path from ConfigParser import ConfigParser sys.path.insert(0,'/usr/share/yum-cli/') import yum import yum.Errors from yum.parser import varReplace from yum.constants import * from cli import * from utils import YumUtilBase # this needs to be in config file somewhere # we need a release identifier and baseurls/mirrorlists # that's really it - maybe a gpgkey path.... release_map = { '7': ('http://mirrors.fedoraproject.org/mirrorlist?repo=fedora-7&arch=$basearch',None), 'rawhide': ('http://mirrors.fedoraproject.org/mirrorlist?repo=rawhide&arch=$basearch',None), 'development': ('http://mirrors.fedoraproject.org/mirrorlist?repo=rawhide&arch=$basearch',None), 'devel': ('http://mirrors.fedoraproject.org/mirrorlist?repo=rawhide&arch=$basearch',None), 'f8test1': (None,'http://koji.fedoraproject.org/rel-eng/trees/f8-test1/Fedora/$basearch/os/') } localpath='/boot' basedownloadpath='/tmp/preupgrade' def isParaVirt(): """ This function determines if the running system is a paravirt guest. This code is borrowed heavily from rhnreg.py (packaged with rhn-setup). 2. Check /sys/hypervisor/uuid. If exists and is non-zero, we know the system is a para-virt guest; exit. """ isGuest = False if os.path.exists("/proc/xen/xsd_port"): return False if os.path.exists("/sys/hypervisor/uuid"): try: uuid_file = open('/sys/hypervisor/uuid', 'r') uuid = uuid_file.read() uuid_file.close() if len(uuid.strip("0-\n")) != 0: isGuest = True except IOError: # Failed; must not be para-virt. pass return isGuest def grubby(kernel,initrd,args=''): cmd='/sbin/grubby --make-default --title="upgrade fedora"' if args != '': cmd=cmd+' --add-kernel=%s --initrd=%s --args="%s"' % (kernel,initrd,args) else: cmd=cmd+' --add-kernel=%s --initrd=%s' % (kernel,initrd) return os.system(cmd) class PreUpgrade(YumUtilBase): NAME = 'PreUpgrade' VERSION = '0.1' USAGE = '"usage: preupgrade [options] release [path]' def __init__(self): YumUtilBase.__init__(self, PreUpgrade.NAME, PreUpgrade.VERSION, PreUpgrade.USAGE) self.logger = logging.getLogger("yum.verbose.cli.myyumutil") # all the work/process # setup the goo we need self.main() def retrieve_treeinfo(self): # take our repo # find a .treeinfo in them # download it # parse it # store relative path to the right kernel and initrd # there can be only one # info we need: # initrd.img path # kernel path # stage2.img path # min image # there's only one - - this could change in the future repo = self.repos.listEnabled()[0] tif = repo.grab.urlopen('/.treeinfo') myarch = self.conf.yumvar['basearch'] cp = ConfigParser() cp.readfp(tif) if isParaVirt(): myarch='xen' mysection = 'images-%s' % myarch self.kernel = cp.get(mysection, 'kernel') self.initrd = cp.get(mysection, 'initrd') if cp.has_section('stage2'): self.instimage = cp.get('stage2', 'instimage') self.mainimage = cp.get('stage2', 'mainimage') else: print 'stage2 not found - taking our best guess as to its path' self.instimage = 'images/minstg2.img' self.mainimage = 'images/stage2.img' tif.close() def _retrieve_file(self, fileinfo): repo = self.repos.listEnabled()[0] if not os.path.exists(localpath): os.makedirs(localpath) # for each file - get the filesize = check for space available in localpath # then go to the next one. For kernel and initrd out-of-space is fatal # for stage2 - not so fatal item_fname = os.path.basename(fileinfo) local = localpath + '/' + item_fname if os.path.exists(local): # we shouldn't have it - print and skip it print "skipping %s download to %s b/c file appears to exist" % (item_fname, local) # we should be doing some integrity checks but we don't have # anything to check it against - la la la la return True # check for space available here tmp = repo.grab.urlopen(fileinfo) mysize = tmp.hdr.dict['content-length'] dirstat = os.statvfs(localpath) if (dirstat.f_bavail * dirstat.f_bsize) <= long(mysize): print "insufficient space in %s to download %s" % (localpath, item_fname) return False try: repo._getFile(relative=fileinfo, local=local) except yum.Errors.RepoError, e: print "failure downloading: %s" % e # clean up the garbage os.unlink(local) return False return True def retrieve_boot_files(self): # relative tmp FIXME XXX for item in (self.kernel, self.initrd): if not self._retrieve_file(item): print "could not download/save %s - fatal" % item sys.exit(1) for item in (self.mainimage, self.instimage): if not self._retrieve_file(item): print "could not download/save %s - continuing" % item def setup_update_repo(self): # make our new repo obj # set this to wherever we want the files to go downloadpath = basedownloadpath '/' + self.pu_release + '-preupgrade' newrepo = yum.yumRepo.YumRepository(self.pu_release) newrepo.name = self.pu_release if self.pu_mirrorlist: self.pu_mirrorlist = varReplace(self.pu_mirrorlist, self.conf.yumvar) newrepo.mirrorlist = self.pu_mirrorlist if self.pu_baseurl: self.pu_baseurl = varReplace(self.pu_baseurl, self.conf.yumvar) newrepo.baseurl = self.pu_baseurl newrepo.basecachedir = downloadpath # disable all the other repos self.repos.disableRepo('*') # add our new repo self.repos.add(newrepo) # enable that repo self.repos.enableRepo(newrepo.id) # setup the progress bars for this repo self.setupProgessCallbacks() # setup the repo dirs/etc self.doRepoSetup(thisrepo=newrepo.id) def _valid_entries(self): print "valid entries include:" for rel in release_map.keys(): print ' %s' % rel def getconfig(self): # Add util commandline options to the yum-cli ones parser = self.getOptionParser() # Parse the commandline option and setup the basics. opts = self.doUtilConfigSetup() if len(self.cmds) < 1: print "please give a release to try to pre-upgrade to" self._valid_entries() sys.exit(1) self.pu_release = self.cmds[0] if not release_map.has_key(self.pu_release): print "no release named %s available" % self.pu_release self._valid_entries() sys.exit(1) def setup(self): self.pu_mirrorlist = release_map[self.pu_release][0] self.pu_baseurl = release_map[self.pu_release][1] self.setup_update_repo() def figure_out_what_we_need(self): # update all self.update() # try and remove all the old kernels self.remove(name='kernel') self.remove(name='kernel-xen') # build a transaction self.buildTransaction() # print out what we would be doing def main(self): self.getconfig() self.setup() self.figure_out_what_we_need() downloadpkgs = [] downloadpkgs = map(lambda txmbr: txmbr.po, self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES)) del(self.tsInfo) self.listPkgs(downloadpkgs, 'Packages we need to download', 'list') # print out the size to download self.reportDownloadSize(downloadpkgs) # download the pkgs # confirm with user print 'Download packages? ' if self._promptWanted(): if not self.userconfirm(): print 'Exiting on user command' sys.exit(1) problems = self.downloadPkgs(downloadpkgs) # find tree info data self.retrieve_treeinfo() # grab kernel and initrd and stage2 self.retrieve_boot_files() # make it bootable mykernel = localpath + '/' + os.path.basename(self.kernel) myinitrd = localpath + '/' + os.path.basename(self.initrd) grubby(mykernel, myinitrd) # prompt user for reboot if __name__ == '__main__': util = PreUpgrade()