Viewing file: rollbacks.py (14.58 KB) -rwxr-xr-x Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
#!/usr/bin/python # # Module for doing rpm rollbacks # Copyright (c) 2000-2002 Red Hat, Inc. # # Author: Adrian Likins <alikins@redhat.com> #
#>From python, this "works" # ts = rpm.TransactionSet() # print ts.IDTXload() # print ts.IDTXglob() #and (untested, this will execute transaction to "transactionid" rollback goal, #so be careful) # ts.Rollback(transactionid) # #On to rpmtsSolve() callback bindings ... import os import sys sys.path.insert(0, "/usr/share/rhn/") sys.path.insert(1,"/usr/share/rhn/up2date_client")
import rpm import transaction import time import up2dateErrors from rhpl.translate import _, N_ import glob import up2dateErrors import up2dateLog import wrapperUtils import rpmUtils
import up2dateUtils
# ************* NOTE: ************# # for the sake of clarity, the names "added/removed" as used here # are indicative of what happened when the original transaction was # ran. Aka, if you "up2date foobar" and it updates foobar-1-0 with # foobar-2-0, you added foobar-2-0 and removed foobar-1-0 # # The reason I mention this explicitly is the trouble of describing # what happens when you rollback the transaction, which is basically # the opposite, and leads to plenty of confusion #
# This is a class used to represent a rpm rollback transaction... It's # designed to be somewhat marshable class RollbackTransaction: def __init__(self): # these list are lists of tuples of (hdr, filename or instance) self.addedPackages = [] self.removedPackages = [] self.tid = None self.removedList = None self.addedList = None self.ts = None
def getData(self): data = {} self.removedList, self.addedList = self.genNames() data['added'] = self.addedList data['removed'] = self.removedList data['tid'] = self.tid return data
# method to generate a datastructure useful for transporting #over the wire, and/or displaying to the user def genNames(self): data = {} removedList = [] addedList = [] for (hdr,instance) in self.removedPackages: epoch = "" if hdr['epoch']: epoch = "%s" % hdr['epoch'] pkgInfo = (hdr['name'],hdr['version'],hdr['release'],epoch, hdr['arch'], "-") removedList.append(pkgInfo) for (hdr,filename) in self.addedPackages: epoch = "" if hdr['epoch']: epoch = "%s" % hdr['epoch'] pkgInfo = (hdr['name'],hdr['version'],hdr['release'],epoch, hdr['arch'], "+") addedList.append(pkgInfo) return (removedList, addedList)
def display(self): out = "" out = out + "install time: %s\t tid:%s\n" % (time.ctime(self.tid), self.tid) removedList, addedList = self.genNames() for pkgInfo in removedList: out = out + "\t\t[%s] %s-%s-%s:%s\n" % (pkgInfo[5], pkgInfo[0], pkgInfo[1], pkgInfo[2], pkgInfo[3]) for pkgInfo in addedList: out = out + "\t\t[%s] %s-%s-%s:%s\n" % (pkgInfo[5], pkgInfo[0], pkgInfo[1], pkgInfo[2], pkgInfo[3]) out = out + "\n" return out
def genTransaction(self): self.getData() self.ts = rpm.TransactionSet() self.ts.setVSFlags(~(rpm.RPMVSF_NOMD5))
tsflags = (rpm.RPMPROB_FILTER_OLDPACKAGE|rpm.RPMTRANS_FLAG_NOMD5) probflags = rpm.RPMPROB_FILTER_OLDPACKAGE|rpm.RPMPROB_FILTER_REPLACENEWFILES| \ rpm.RPMPROB_FILTER_REPLACEOLDFILES| \ rpm.RPMPROB_FILTER_REPLACEPKG #FIXME: Insert policy issues here # possibilities: # max size of transactions # pkg/file list to never rollback # date to never rollback past # protected tids?
self.kernels = [] # remove the added packages and # install the removed packages. That almost # makes sense for (hdr,instance) in self.addedPackages: # print "name: %s-%s-%s:%s instance: %s" % (hdr['name'],hdr['version'], # hdr['release'], hdr['epoch'], # instance) self.ts.addErase(instance) for (hdr, filename) in self.removedPackages: # print "name: %s-%s-%s:%s filename: %s" % (hdr['name'],hdr['version'], # hdr['release'], hdr['epoch'], # filename) if "kernel" in hdr['Providename']: self.kernels.append(hdr) self.ts.addInstall(hdr, hdr, 'i') else: # assume we always "upgrade" packages until # told otherwise
#FIXME: Need logic here to detect things like # case4 and 5 in the policy examples, aka detect # if were doing a "force" rollback epoch="" if hdr['epoch']: epoch = hdr['epoch'] # this is a cool function, but it may be way too slow hdrlist = rpmUtils.installedHeaderByKeyword(name=hdr['name'], version=hdr['version'], release=hdr['release'], arch=hdr['arch']) if hdrlist: # print "Congrats: that package is already installed, maybe I should force it?" # do the equilivent of "--force" probflags = rpm.RPMPROB_FILTER_OLDPACKAGE|rpm.RPMPROB_FILTER_REPLACENEWFILES| \ rpm.RPMPROB_FILTER_REPLACEOLDFILES| \ rpm.RPMPROB_FILTER_REPLACEPKG self.ts.addInstall(hdr, hdr, "u")
deps = self.ts.check() # print "deps1: %s" % deps if deps: # print "deps2: %s" % deps raise up2dateErrors.DependencyError(_("Dependencies should have already been resolved, but they are not."), deps)
# turn off md5/rsa/dsa self.ts.setVSFlags(rpm.RPMVSF_NODSA|rpm.RPMVSF_NOMD5|rpm.RPMVSF_NORSA|rpm.RPMVSF_NEEDPAYLOAD)
# FIXME: self.ts.addTSFlags (coming to bindings) to persistently set # stuff like "--oldpackage" # print "tsflags: %s" % tsflags self.ts.setFlags(tsflags) if probflags: self.ts.setProbFilter(probflags) return self.ts
class Rollback: def __init__(self, ts = None): self.log = up2dateLog.initLog() if ts: self.ts = ts else: self.ts = transaction.initReadOnlyTransaction() #self.ts = None #self.ts.Debug(1) self.ts.pushVSFlags(-1) self.__genAvailableTidHdrDict() self.__genInstalledTidHdrDict() self.__genTransDict()
# def __del__(self): # del self.ts # del self.transDict # del self.installedTidHdrDict # del self.tidHdrDict # del self.availableTidHdrDict # self.ts = None # self.transDict = None # self.installedTidHdrDict = None # self.tidHdrDict = None # self.availableTidHdrDict = None
def __genTransDict(self): # generate a dict with tid as the key and a "transaction" object # as the value self.transDict = {} for tid in self.availableTidHdrDict.keys(): # need has_key? rollbackTransaction = RollbackTransaction() if self.installedTidHdrDict.has_key(tid): rollbackTransaction.addedPackages = self.installedTidHdrDict[tid] if self.availableTidHdrDict.has_key(tid): rollbackTransaction.removedPackages = self.availableTidHdrDict[tid] rollbackTransaction.tid = tid self.transDict[tid] = rollbackTransaction
# this is a map of tid -> list of packages installed with that tid def __genInstalledTidHdrDict(self): self.installedTidHdrDict = {} tidlist = self.ts.IDTXload() for (tid, hdr, instance) in tidlist: if hdr['removetid']: self.log.log_debug("""Warning: name: %s: removedtid %s: installedtime: %s installedtid: %s removetid found in hdr in db. """ % \ (hdr['name'], hdr['removetid'], hdr['installtime'], hdr['installtid']), 1) continue if self.installedTidHdrDict.has_key(tid): self.installedTidHdrDict[tid].append((hdr,instance)) else: self.installedTidHdrDict[tid] = [(hdr, instance)] tidlist = None
def __genAvailableTidHdrDict(self): # this generates of tids that we can actually rollback to, # ie, the ones on disk self.tidHdrDict = {} self.availableTidHdrDict = {} transList = self.ts.IDTXglob() if not transList: return for (tid, hdr, filename) in transList: if hdr['installtid']: print """Warning: name: %s: removedtid %s: installedtime: %s installedtid: %s installedtid found in hdr in repackage dir """ % \ (hdr['name'], hdr['removetid'], hdr['installtime'], hdr['installtid']) if self.availableTidHdrDict.has_key(tid): self.availableTidHdrDict[tid].append((hdr,filename)) else: self.availableTidHdrDict[tid] = [(hdr,filename)]
def __getitem__(self, item): return self.availableTidHdrDict[item]
def getPackages(self, item): hdrlist = self.availableTidHdrDict[item] tmplist = []
return tmplist def keys(self): return self.availableTidHdrDict.keys()
def values(self): return self.availableTidHdrDict.values()
def has_key(self, key): return self.availableTidHdrDict.has_key(key) def printTransactions(self): tidlist = self.availableTidHdrDict.keys() tidlist.sort() ret = "" for tid in tidlist: ret = ret + self.showTransaction(tid) return ret def showTransaction(self, tid): if self.transDict.has_key(tid): return self.transDict[tid].display()
# return a list of transactions as data structs def getTransactionsData(self): data = {} for tid in self.transDict.keys(): data["%s" % tid] = self.transDict[tid].getData() return data def getLatestTid(self): tidlist = self.availableTidHdrDict.keys() tidlist.sort() if len(tidlist): return tidlist[-1] else: return None
def getTidsSince(self, tid): tidlist = self.availableTidHdrDict.keys() sinceList = [] for i in tidlist: if i >= tid: sinceList.append(i) sinceList.sort() sinceList.reverse() return sinceList
def rollback(self, tid): trans = self.transDict[tid].genTransaction() #FIXME: probabably need to change this to allow a # rpmCallback to be passed, ditto for progress bars, etc. rpmCallback = wrapperUtils.RpmCallback() # FIXME: need to get transdir from the rpm macro? ret = rpmUtils.runTransaction(trans,rpmCallback.callback, transdir="/var/spool/repackage") #FIXME: needs better error handling probabaly # cleanup the packages we no longer need self.removeStaleTransaction(tid)
return ret
def doRollback(self,tid): ret = None # default to rolling back all transactions to the run specified tidlist = self.getTidsSince(tid)
# do # tidlist is a list of tids sorted newest first for i in tidlist: #FIXME: figure a way to handle errors properly ret = self.rollback(i)
return ret
def removeStaleTransaction(self, tid): trans = self.transDict[tid] stalePackages = trans.removedList
for pkg in trans.removedList: self.__removePackage(pkg) def __removePackage(self, pkg): # first see if it is on disk: fileNames = glob.glob("%s/%s.%s.*" % ("/var/spool/repackage", up2dateUtils.pkgToString(pkg), pkg[4])) for fileName in fileNames: self.log.log_me("deleting %s" % fileName) try: os.remove(fileName) except: raise up2dateErrors.FileError(_("cannot remove %s") % fileName)
# this *Info methods are for actions def getTidInfo(self,tid): #packages = self.getPackages(tid) trans = self.transDict[tid] data = trans.getData() infodict = {'tid': tid, 'removed_packages':data['removed'], 'added_packages':data['added']} return infodict
def getUndoInfo(self): tid = self.getLatestTid() if not tid: raise up2dateErrors.NoRollbacksToUndoError("No transactions available to rollback") return self.getTidInfo(tid) def previewUndo(self): tid = self.getLatestTid() if tid: return self.showTransaction(tid) else: return "" def undo(self): tid = self.getLatestTid() if tid: self.doRollback(tid) else: raise up2dateErrors.NoRollbacksToUndoError("There are no transactions to undo")
def main(): import sys
# let me rollback deletions rpm.addMacro("_unsafe_rollbacks", "1") rollback = Rollback() foo = rollback.getLatestTid()
foo = 1037758271 # print rollback.getTransactionsData() print "latesttid: %s" % foo # print print rollback.printTransactions()
transdict = rollback.transDict[foo] print "rollback.transDict[foo]: %s" % transdict trans = rollback.transDict[foo].genTransaction() print "rollback.transDict[foo].genTransaction: %s" % trans print "rollback.transDict[foo].display(): %s" % rollback.transDict[foo].display()
print rollback.doRollback(foo) # print # print "rolling back tid: %s with packages: %s" % (foo, rollback.getPackages(foo)) # print # sys.exit() # bleah = rollback.transDict[foo] # print bleah.display()
# import pprint # pprint.pprint(bleah.getData())
# ts = bleah.genTransaction() # print rollback.undo()
if __name__ == "__main__": main()
|