diff options
| author | Michael E Brown <mebrown@michaels-house.net> | 2007-10-21 05:00:27 -0500 |
|---|---|---|
| committer | Michael E Brown <mebrown@michaels-house.net> | 2007-10-21 05:00:27 -0500 |
| commit | 03b315f0c466b577cd4c27cac2f645106b7b84fb (patch) | |
| tree | 4d0eb1250098cb03b9ec374a98dbe21e5aa23f9a /src | |
| parent | 15cd1d4715777110a86467a2f9ecf0e64bce91f9 (diff) | |
| download | mock-03b315f0c466b577cd4c27cac2f645106b7b84fb.tar.gz mock-03b315f0c466b577cd4c27cac2f645106b7b84fb.tar.xz mock-03b315f0c466b577cd4c27cac2f645106b7b84fb.zip | |
create plugin infrastructure. move all caching related stuff to plugins.
Diffstat (limited to 'src')
| -rwxr-xr-x | src/mock.py | 41 | ||||
| -rw-r--r-- | src/py-libs/backend.py | 216 | ||||
| -rw-r--r-- | src/py-libs/plugins/ccache.py | 90 | ||||
| -rw-r--r-- | src/py-libs/plugins/root_cache.py | 107 | ||||
| -rw-r--r-- | src/py-libs/plugins/yum_cache.py | 106 |
5 files changed, 356 insertions, 204 deletions
diff --git a/src/mock.py b/src/mock.py index 0970606..e44c928 100755 --- a/src/mock.py +++ b/src/mock.py @@ -49,7 +49,7 @@ log = logging.getLogger() logging.basicConfig() @traceLog(log) -def command_parse(): +def command_parse(config_opts): """return options and args from parsing the command line""" usage = """ @@ -89,10 +89,10 @@ def command_parse(): default=None, help="Fail build if rpmbuild takes longer than 'timeout' seconds ") # caching - parser.add_option("--enable-cache", action="append", dest="enabled_caches", type="string", - default=[], help="Disable types of caching. Valid values: 'root_cache', 'yum_cache', 'ccache'") - parser.add_option("--disable-cache", action="append", dest="disabled_caches", type="string", - default=[], help="Disable types of caching. Valid values: 'root_cache', 'yum_cache', 'ccache'") + parser.add_option("--enable-plugin", action="append", dest="enabled_plugins", type="string", + default=[], help="Enable plugin. Currently-available plugins: %s" % repr(config_opts['plugins'])) + parser.add_option("--disable-plugin", action="append", dest="disabled_plugins", type="string", + default=[], help="Disable plugin. Currently-available plugins: %s" % repr(config_opts['plugins'])) return parser.parse_args() @@ -120,12 +120,16 @@ def setup_default_config_opts(config_opts): # (global) caching-related config options config_opts['cache_topdir'] = '/var/lib/mock/cache' - config_opts['enable_ccache'] = True - config_opts['ccache_opts'] = {'max_age_days': 15, 'max_cache_size': "4G"} - config_opts['enable_yum_cache'] = True - config_opts['yum_cache_opts'] = {'max_age_days': 15} - config_opts['enable_root_cache'] = True - config_opts['root_cache_opts'] = {'max_age_days': 15} + config_opts['plugins'] = ('ccache', 'yum_cache', 'root_cache') + config_opts['plugin_dir'] = os.path.join(PKGPYTHONDIR, "plugins") + config_opts['plugin_conf'] = { + 'enable_ccache': True, + 'ccache_opts': {'max_age_days': 15, 'max_cache_size': "4G"}, + 'enable_yum_cache': True, + 'yum_cache_opts': {'max_age_days': 15}, + 'enable_root_cache': True, + 'root_cache_opts': {'max_age_days': 15}, + } # dependent on guest OS config_opts['use_host_resolv'] = True @@ -154,14 +158,13 @@ def set_config_opts_per_cmdline(config_opts, options): if options.rpmbuild_timeout is not None: config_opts['rpmbuild_timeout'] = options.rpmbuild_timeout - legalCacheOpts = ("yum_cache", "root_cache", "ccache") - for i in options.disabled_caches: - if i not in legalCacheOpts: - raise mock.exception.BadCmdline("Bad option for '--disable-cache=%s'. Expecting one of: %s" % (i, legalCacheOpts)) + for i in options.disabled_plugins: + if i not in config_opts['plugins']: + raise mock.exception.BadCmdline("Bad option for '--disable-plugins=%s'. Expecting one of: %s" % (i, config_opts['plugins'])) config_opts['enable_%s' % i] = False - for i in options.enabled_caches: - if i not in legalCacheOpts: - raise mock.exception.BadCmdline("Bad option for '--enable-cache=%s'. Expecting one of: %s" % (i, legalCacheOpts)) + for i in options.enabled_plugins: + if i not in config_opts['plugins']: + raise mock.exception.BadCmdline("Bad option for '--enable-plugins=%s'. Expecting one of: %s" % (i, config_opts['plugins'])) config_opts['enable_%s' % i] = True if options.cleanup_after and not options.resultdir: @@ -218,7 +221,7 @@ def main(retParams): # defaults config_opts = {} setup_default_config_opts(config_opts) - (options, args) = command_parse() + (options, args) = command_parse(config_opts) # config path -- can be overridden on cmdline config_path=MOCKCONFDIR diff --git a/src/py-libs/backend.py b/src/py-libs/backend.py index ea87655..542dad4 100644 --- a/src/py-libs/backend.py +++ b/src/py-libs/backend.py @@ -21,6 +21,7 @@ # python library imports import fcntl import glob +import imp import logging import os import shutil @@ -79,15 +80,11 @@ class Root(object): self.macros = config['macros'] self.more_buildreqs = config['more_buildreqs'] self.cache_topdir = config['cache_topdir'] + self.cachedir = os.path.join(self.cache_topdir, self.sharedRootName) - self.enable_ccache = config['enable_ccache'] - self.ccache_opts = config['ccache_opts'] - - self.enable_yum_cache = config['enable_yum_cache'] - self.yum_cache_opts = config['yum_cache_opts'] - - self.enable_root_cache = config['enable_root_cache'] - self.root_cache_opts = config['root_cache_opts'] + self.plugins = config['plugins'] + self.pluginConf = config['plugin_conf'] + self.pluginDir = config['plugin_dir'] # mount/umount self.umountCmds = ['umount -n %s/proc' % self.rootdir, @@ -99,12 +96,21 @@ class Root(object): 'mount -n -t sysfs mock_chroot_sysfs %s/sys' % self.rootdir, ] + self.state("init plugins") + self._initPlugins() + # officially set state so it is logged self.state("start") # ============= # 'Public' API # ============= + @traceLog(moduleLog) + def addHook(self, stage, function): + hooks = self._hooks.get(stage, []) + if function not in hooks: + hooks.append(function) + self._hooks[stage] = hooks @traceLog(moduleLog) def state(self, newState = None): @@ -150,6 +156,7 @@ class Root(object): self.uidManager.becomeUser(0) # create our base directory heirarchy + mock.util.mkdirIfAbsent(self.cachedir) mock.util.mkdirIfAbsent(self.basedir) mock.util.mkdirIfAbsent(self.rootdir) @@ -170,8 +177,7 @@ class Root(object): self.root_log.debug('rootdir = %s' % self.rootdir) self.root_log.debug('resultdir = %s' % self.resultdir) - # set up cache dirs: - self._initCache() + # set up plugins: self._callHooks('preinit') # create skeleton dirs @@ -389,181 +395,21 @@ class Root(object): hook() @traceLog(moduleLog) - def _addHook(self, stage, function): - hooks = self._hooks.get(stage, []) - if function not in hooks: - hooks.append(function) - self._hooks[stage] = hooks - - @traceLog(moduleLog) - def _initCache(self): - self.cachedir = os.path.join(self.cache_topdir, self.sharedRootName) - if self.enable_root_cache or self.enable_yum_cache or self.enable_ccache: - mock.util.mkdirIfAbsent(self.cachedir) - - if self.enable_root_cache: - self._setupRootCache() - - if self.enable_yum_cache: - self._setupYumCache() - - if self.enable_ccache: - self._setupCcache() - - @traceLog(moduleLog) - def _rootCacheLock(self, shared=1): - lockType = fcntl.LOCK_EX - if shared: lockType = fcntl.LOCK_SH - try: - fcntl.lockf(self.rootCacheLock.fileno(), lockType | fcntl.LOCK_NB) - except IOError, e: - oldState = self.state() - self.state("Waiting for rootcache lock") - fcntl.lockf(self.rootCacheLock.fileno(), lockType) - self.state(oldState) - - @traceLog(moduleLog) - def _rootCacheUnlock(self): - fcntl.lockf(self.rootCacheLock.fileno(), fcntl.LOCK_UN) - - @traceLog(moduleLog) - def _rootCachePreInitHook(self): - if os.path.exists(self.rootCacheFile): - self.state("unpacking cache") - self._rootCacheLock() - mock.util.do("tar xzf %s -C %s" % (self.rootCacheFile, self.rootdir)) - self._rootCacheUnlock() - self.chroot_setup_cmd = "update" - self.chrootWasCleaned = False - - @traceLog(moduleLog) - def _rootCachePostInitHook(self): - # never rebuild cache unless it was a clean build. - if self.chrootWasCleaned: - self.state("creating cache") - self._rootCacheLock(shared=0) - mock.util.do("tar czf %s -C %s ." % (self.rootCacheFile, self.rootdir)) - self._rootCacheUnlock() - - @traceLog(moduleLog) - def _setupRootCache(self): - self._addHook("preinit", self._rootCachePreInitHook) - self._addHook("postinit", self._rootCachePostInitHook) - self.rootSharedCachePath = os.path.join(self.cachedir, "root_cache") - self.rootCacheFile = os.path.join(self.rootSharedCachePath, "cache.tar.gz") - mock.util.mkdirIfAbsent(self.rootSharedCachePath) - - # lock so others dont accidentally use root cache while we operate on it. - self.rootCacheLock = open(os.path.join(self.rootSharedCachePath, "rootcache.lock"), "a+") - - # check cache age: - self.state("enabling root cache") - try: - statinfo = os.stat(self.rootCacheFile) - file_age_days = (time.time() - statinfo.st_ctime) / (60 * 60 * 24) - if file_age_days > self.root_cache_opts['max_age_days']: - os.unlink(self.rootCacheFile) - except OSError: - pass - - # lock the shared yum cache (when enabled) before any access - # by yum, and prior to cleaning it. This prevents simultaneous access from - # screwing things up. This can possibly happen, eg. when running multiple - # mock instances with --uniqueext= - @traceLog(moduleLog) - def _yumCachePreYumHook(self): - try: - fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError, e: - oldState = self.state() - self.state("Waiting for yumcache lock") - fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_EX) - self.state(oldState) - - @traceLog(moduleLog) - def _yumCachePostYumHook(self): - fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_UN) - - @traceLog(moduleLog) - def _setupYumCache(self): - self._addHook("preyum", self._yumCachePreYumHook) - self._addHook("postyum", self._yumCachePostYumHook) - self.yumSharedCachePath = os.path.join(self.cachedir, "yum_cache") - mock.util.mkdirIfAbsent(os.path.join(self.rootdir, 'var/cache/yum')) - mock.util.mkdirIfAbsent(self.yumSharedCachePath) - self.umountCmds.append('umount -n %s/var/cache/yum' % self.rootdir) - self.mountCmds.append('mount -n --bind %s %s/var/cache/yum' % (self.yumSharedCachePath, self.rootdir)) - - # lock so others dont accidentally use yum cache while we operate on it. - self.yumCacheLock = open(os.path.join(self.yumSharedCachePath, "yumcache.lock"), "a+") - self._yumCachePreYumHook() - - self.state("enabled yum cache, cleaning yum metadata") - for (dirpath, dirnames, filenames) in os.walk(self.yumSharedCachePath): - for filename in filenames: - fullPath = os.path.join(dirpath, filename) - statinfo = os.stat(fullPath) - file_age_days = (time.time() - statinfo.st_ctime) / (60 * 60 * 24) - # prune repodata so yum redownloads. - # prevents certain errors where yum gets stuck due to bad metadata - for ext in (".sqllite", ".xml", ".bz2", ".gz"): - if filename.endswith(ext) and file_age_days > 1: - os.unlink(fullPath) - fullPath = None - break - - if fullPath is None: continue - if file_age_days > self.ccache_opts['max_age_days']: - os.unlink(fullPath) - continue - - self._yumCachePostYumHook() - - # set the max size before we actually use it during a build. - # ccache itself manages size and settings. - @traceLog(moduleLog) - def _ccacheBuildHook(self): - self.doChroot("ccache -M %s" % self.ccache_opts['max_cache_size']) - - # install ccache rpm after buildroot set up. - @traceLog(moduleLog) - def _ccachePostInitHook(self): - #self.state("installing ccache") - #self._yum('install ccache') - self.preExistingDeps = "ccache" - - # basic idea here is that we add 'cc', 'gcc', 'g++' shell scripts to - # to /tmp/ccache, which is bind-mounted from a shared location. - # we then add this to the front of the path. - # we also set a few admin variables used by ccache to find the shared - # cache. - @traceLog(moduleLog) - def _setupCcache(self): - self._addHook("prebuild", self._ccacheBuildHook) - self._addHook("postinit", self._ccachePostInitHook) - self.ccachePath = os.path.join(self.cachedir, "ccache") - mock.util.mkdirIfAbsent(os.path.join(self.rootdir, 'tmp/ccache')) - mock.util.mkdirIfAbsent(self.ccachePath) - self.umountCmds.append('umount -n %s/tmp/ccache' % self.rootdir) - self.mountCmds.append('mount -n --bind %s %s/tmp/ccache' % (self.ccachePath, self.rootdir)) - os.environ['PATH'] = "/tmp/ccache:%s" % (os.environ['PATH']) - os.environ['CCACHE_DIR'] = "/tmp/ccache" - os.environ['CCACHE_UMASK'] = "002" - self._dumpToFile(os.path.join(self.ccachePath, "cc"), - '#!/bin/sh\nexec ccache /usr/bin/cc "$@"\n', mode=0555) - self._dumpToFile(os.path.join(self.ccachePath, "gcc"), - '#!/bin/sh\nexec ccache /usr/bin/gcc "$@"\n', mode=0555) - self._dumpToFile(os.path.join(self.ccachePath, "g++"), - '#!/bin/sh\nexec ccache /usr/bin/g++ "$@"\n', mode=0555) - - @traceLog(moduleLog) - def _dumpToFile(self, filename, contents, *args, **kargs): - fd = open(filename, "w+") - fd.write(contents) - fd.close() - mode = kargs.get("mode", None) - if mode is not None: - os.chmod(filename, mode) + def _initPlugins(self): + # Import plugins (simplified copy of what yum does). Can add yum + # features later when we prove we need them. + for modname, modulefile in [ (p, os.path.join(self.pluginDir, "%s.py" % p)) for p in self.plugins ]: + if not self.pluginConf.get("enable_%s"%modname): continue + fp, pathname, description = imp.find_module(modname, [self.pluginDir]) + try: + module = imp.load_module(modname, fp, pathname, description) + finally: + fp.close() + + if not hasattr(module, 'requires_api_version'): + raise mock.exception.Error('Plugin "%s" doesn\'t specify required API version' % modname) + + module.init(self, self.pluginConf["%s_opts" % modname]) @traceLog(moduleLog) def _mountall(self): diff --git a/src/py-libs/plugins/ccache.py b/src/py-libs/plugins/ccache.py new file mode 100644 index 0000000..f2f9d63 --- /dev/null +++ b/src/py-libs/plugins/ccache.py @@ -0,0 +1,90 @@ +#!/usr/bin/python -tt +# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0: +# +# 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. +# Written by Seth Vidal +# Sections taken from Mach by Thomas Vander Stichele +# revised and adapted by Michael Brown + +# python library imports +import logging +import os + +# our imports +from mock.trace_decorator import traceLog +import mock.util + +# set up logging, module options +moduleLog = logging.getLogger("mock") +requires_api_version = "1.0" + +# plugin entry point +def init(rootObj, conf): + ccache = CCache(rootObj, conf) + +# classes +class CCache(object): + """enables ccache in buildroot/rpmbuild""" + @traceLog(moduleLog) + def __init__(self, rootObj, conf): + self.rootObj = rootObj + self.ccache_opts = conf + self.ccachePath = os.path.join(rootObj.cachedir, "ccache") + self.rootdir = rootObj.rootdir + rootObj.ccacheObj = self + rootObj.preExistingDeps = "ccache" + rootObj.addHook("prebuild", self._ccacheBuildHook) + rootObj.addHook("preinit", self._ccachePreInitHook) + rootObj.umountCmds.append('umount -n %s/tmp/ccache' % rootObj.rootdir) + rootObj.mountCmds.append('mount -n --bind %s %s/tmp/ccache' % (self.ccachePath, rootObj.rootdir)) + + # ============= + # 'Private' API + # ============= + # set the max size before we actually use it during a build. + # ccache itself manages size and settings. + @traceLog(moduleLog) + def _ccacheBuildHook(self): + self.rootObj.doChroot("ccache -M %s" % self.ccache_opts['max_cache_size']) + + # basic idea here is that we add 'cc', 'gcc', 'g++' shell scripts to + # to /tmp/ccache, which is bind-mounted from a shared location. + # we then add this to the front of the path. + # we also set a few admin variables used by ccache to find the shared + # cache. + @traceLog(moduleLog) + def _ccachePreInitHook(self): + mock.util.mkdirIfAbsent(os.path.join(self.rootdir, 'tmp/ccache')) + mock.util.mkdirIfAbsent(self.ccachePath) + os.environ['PATH'] = "/tmp/ccache:%s" % (os.environ['PATH']) + os.environ['CCACHE_DIR'] = "/tmp/ccache" + os.environ['CCACHE_UMASK'] = "002" + self._dumpToFile(os.path.join(self.ccachePath, "cc"), + '#!/bin/sh\nexec ccache /usr/bin/cc "$@"\n', mode=0555) + self._dumpToFile(os.path.join(self.ccachePath, "gcc"), + '#!/bin/sh\nexec ccache /usr/bin/gcc "$@"\n', mode=0555) + self._dumpToFile(os.path.join(self.ccachePath, "g++"), + '#!/bin/sh\nexec ccache /usr/bin/g++ "$@"\n', mode=0555) + + @traceLog(moduleLog) + def _dumpToFile(self, filename, contents, *args, **kargs): + fd = open(filename, "w+") + fd.write(contents) + fd.close() + mode = kargs.get("mode", None) + if mode is not None: + os.chmod(filename, mode) + + diff --git a/src/py-libs/plugins/root_cache.py b/src/py-libs/plugins/root_cache.py new file mode 100644 index 0000000..add7b19 --- /dev/null +++ b/src/py-libs/plugins/root_cache.py @@ -0,0 +1,107 @@ +#!/usr/bin/python -tt +# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0: +# +# 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. +# Written by Seth Vidal +# Sections taken from Mach by Thomas Vander Stichele +# revised and adapted by Michael Brown + +# python library imports +import fcntl +import logging +import os +import time + +# our imports +from mock.trace_decorator import traceLog +import mock.util + +# set up logging, module options +moduleLog = logging.getLogger("mock") +requires_api_version = "1.0" + +# plugin entry point +def init(rootObj, conf): + rootCache = RootCache(rootObj, conf) + +# classes +class RootCache(object): + """caches root environment in a tarball""" + @traceLog(moduleLog) + def __init__(self, rootObj, conf): + self.rootObj = rootObj + self.root_cache_opts = conf + self.rootSharedCachePath = os.path.join(rootObj.cachedir, "root_cache") + self.rootCacheFile = os.path.join(self.rootSharedCachePath, "cache.tar.gz") + self.rootCacheLock = None + self.state = rootObj.state + self.rootdir = rootObj.rootdir + rootObj.rootCacheObj = self + rootObj.addHook("preinit", self._rootCachePreInitHook) + rootObj.addHook("postinit", self._rootCachePostInitHook) + + # ============= + # 'Private' API + # ============= + @traceLog(moduleLog) + def _rootCacheLock(self, shared=1): + lockType = fcntl.LOCK_EX + if shared: lockType = fcntl.LOCK_SH + try: + fcntl.lockf(self.rootCacheLock.fileno(), lockType | fcntl.LOCK_NB) + except IOError, e: + oldState = self.state() + self.state("Waiting for rootcache lock") + fcntl.lockf(self.rootCacheLock.fileno(), lockType) + self.state(oldState) + + @traceLog(moduleLog) + def _rootCacheUnlock(self): + fcntl.lockf(self.rootCacheLock.fileno(), fcntl.LOCK_UN) + + @traceLog(moduleLog) + def _rootCachePreInitHook(self): + mock.util.mkdirIfAbsent(self.rootSharedCachePath) + # lock so others dont accidentally use root cache while we operate on it. + if self.rootCacheLock is None: + self.rootCacheLock = open(os.path.join(self.rootSharedCachePath, "rootcache.lock"), "a+") + + # check cache age: + self.state("enabling root cache") + try: + statinfo = os.stat(self.rootCacheFile) + file_age_days = (time.time() - statinfo.st_ctime) / (60 * 60 * 24) + if file_age_days > self.root_cache_opts['max_age_days']: + os.unlink(self.rootCacheFile) + except OSError: + pass + + if os.path.exists(self.rootCacheFile): + self.state("unpacking cache") + self._rootCacheLock() + mock.util.do("tar xzf %s -C %s" % (self.rootCacheFile, self.rootdir)) + self._rootCacheUnlock() + self.chroot_setup_cmd = "update" + self.chrootWasCleaned = False + + @traceLog(moduleLog) + def _rootCachePostInitHook(self): + # never rebuild cache unless it was a clean build. + if self.chrootWasCleaned: + self.state("creating cache") + self._rootCacheLock(shared=0) + mock.util.do("tar czf %s -C %s ." % (self.rootCacheFile, self.rootdir)) + self._rootCacheUnlock() + diff --git a/src/py-libs/plugins/yum_cache.py b/src/py-libs/plugins/yum_cache.py new file mode 100644 index 0000000..e375a69 --- /dev/null +++ b/src/py-libs/plugins/yum_cache.py @@ -0,0 +1,106 @@ +#!/usr/bin/python -tt +# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0: +# +# 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. +# Written by Seth Vidal +# Sections taken from Mach by Thomas Vander Stichele +# revised and adapted by Michael Brown + +# python library imports +import logging +import fcntl +import time +import os + +# our imports +from mock.trace_decorator import traceLog +import mock.util + +# set up logging, module options +moduleLog = logging.getLogger("mock") +requires_api_version = "1.0" + +# plugin entry point +def init(rootObj, conf): + yumCache = YumCache(rootObj, conf) + +# classes +class YumCache(object): + """caches root environment in a tarball""" + @traceLog(moduleLog) + def __init__(self, rootObj, conf): + self.rootObj = rootObj + self.yum_cache_opts = conf + self.yumSharedCachePath = os.path.join(rootObj.cachedir, "yum_cache") + self.state = rootObj.state + self.rootdir = rootObj.rootdir + rootObj.yum_cacheObj = self + rootObj.addHook("preyum", self._yumCachePreYumHook) + rootObj.addHook("postyum", self._yumCachePostYumHook) + rootObj.addHook("preinit", self._yumCachePreInitHook) + rootObj.umountCmds.append('umount -n %s/var/cache/yum' % rootObj.rootdir) + rootObj.mountCmds.append('mount -n --bind %s %s/var/cache/yum' % (self.yumSharedCachePath, rootObj.rootdir)) + + # ============= + # 'Private' API + # ============= + # lock the shared yum cache (when enabled) before any access + # by yum, and prior to cleaning it. This prevents simultaneous access from + # screwing things up. This can possibly happen, eg. when running multiple + # mock instances with --uniqueext= + @traceLog(moduleLog) + def _yumCachePreYumHook(self): + try: + fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError, e: + oldState = self.state() + self.state("Waiting for yumcache lock") + fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_EX) + self.state(oldState) + + @traceLog(moduleLog) + def _yumCachePostYumHook(self): + fcntl.lockf(self.yumCacheLock.fileno(), fcntl.LOCK_UN) + + @traceLog(moduleLog) + def _yumCachePreInitHook(self): + mock.util.mkdirIfAbsent(os.path.join(self.rootdir, 'var/cache/yum')) + mock.util.mkdirIfAbsent(self.yumSharedCachePath) + + # lock so others dont accidentally use yum cache while we operate on it. + self.yumCacheLock = open(os.path.join(self.yumSharedCachePath, "yumcache.lock"), "a+") + self._yumCachePreYumHook() + + self.state("enabled yum cache, cleaning yum metadata") + for (dirpath, dirnames, filenames) in os.walk(self.yumSharedCachePath): + for filename in filenames: + fullPath = os.path.join(dirpath, filename) + statinfo = os.stat(fullPath) + file_age_days = (time.time() - statinfo.st_ctime) / (60 * 60 * 24) + # prune repodata so yum redownloads. + # prevents certain errors where yum gets stuck due to bad metadata + for ext in (".sqllite", ".xml", ".bz2", ".gz"): + if filename.endswith(ext) and file_age_days > 1: + os.unlink(fullPath) + fullPath = None + break + + if fullPath is None: continue + if file_age_days > self.yum_cache_opts['max_age_days']: + os.unlink(fullPath) + continue + + self._yumCachePostYumHook() + |
