summaryrefslogtreecommitdiffstats
path: root/py/mock/plugins/root_cache.py
blob: 60b813862474486cf12a930a31d99673b3fa22d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# vim:expandtab:autoindent:tabstop=4:shiftwidth=4:filetype=python:textwidth=0:
# License: GPL2 or later see COPYING
# Written by Michael Brown
# Copyright (C) 2007 Michael E Brown <mebrown@michaels-house.net>

# python library imports
import fcntl
import os
import time
from glob import glob

# our imports
from mock.trace_decorator import decorate, traceLog, getLog
import mock.util

requires_api_version = "1.0"

# plugin entry point
decorate(traceLog())
def init(rootObj, conf):
    RootCache(rootObj, conf)

# classes
class RootCache(object):
    """caches root environment in a tarball"""
    decorate(traceLog())
    def __init__(self, rootObj, conf):
        self.rootObj = rootObj
        self.root_cache_opts = conf
        self.rootSharedCachePath = self.root_cache_opts['dir'] % self.root_cache_opts
        self.rootCacheFile = os.path.join(self.rootSharedCachePath, "cache.tar")
        self.rootCacheLock = None
        self.compressProgram = self.root_cache_opts['compress_program']
        if self.compressProgram:
             self.compressArgs = ['--use-compress-program', self.compressProgram]
             self.rootCacheFile = self.rootCacheFile + self.root_cache_opts['extension']
        else:
             self.compressArgs = []
        self.state = rootObj.state
        rootObj.rootCacheObj = self
        rootObj.addHook("preinit", self._rootCachePreInitHook)
        rootObj.addHook("postinit", self._rootCachePostInitHook)

    # =============
    # 'Private' API
    # =============
    decorate(traceLog())
    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)

    decorate(traceLog())
    def _rootCacheUnlock(self):
        fcntl.lockf(self.rootCacheLock.fileno(), fcntl.LOCK_UN)

    decorate(traceLog())
    def _rootCachePreInitHook(self):
        getLog().info("enabled root cache")
        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 status
        try:
            # see if it aged out
            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']:
                getLog().info("root cache aged out! cache will be rebuilt")
                os.unlink(self.rootCacheFile)
            else:
                # make sure no config file is newer than the cache file
                for cfg in self.rootObj.configs:
                    if os.stat(cfg).st_mtime > statinfo.st_mtime:
                        getLog().info("%s newer than root cache; cache will be rebuilt" % cfg)
                        os.unlink(self.rootCacheFile)
                        break
        except OSError:
            pass

        # optimization: dont unpack root cache if chroot was not cleaned
        if os.path.exists(self.rootCacheFile) and self.rootObj.chrootWasCleaned:
            self.state("unpacking root cache")
            self._rootCacheLock()
            mock.util.do(
                ["tar"] + self.compressArgs + ["-xf", self.rootCacheFile, "-C", self.rootObj.makeChrootPath()],
                shell=False
                )
            self._rootCacheUnlock()
            self.chroot_setup_cmd = "update"
            self.rootObj.chrootWasCleaned = False

    decorate(traceLog())
    def _rootCachePostInitHook(self):
        try:
            self._rootCacheLock(shared=0)
            # nuke any rpmdb tmp files
            for tmp in glob(self.rootObj.makeChrootPath('var/lib/rpm/__db*')):
                os.unlink(tmp)

            
            # never rebuild cache unless it was a clean build.
            if self.rootObj.chrootWasCleaned:
                mock.util.do(["sync"], shell=False)
                self.state("creating cache")
                try:
                    mock.util.do(
                        ["tar"] + self.compressArgs + ["-cf", self.rootCacheFile,
                                                       "-C", self.rootObj.makeChrootPath(), 
                                                       "--exclude=./proc",
                                                       "--exclude=./sys",
                                                       "--exclude=./dev",
                                                       "--exclude=./tmp/ccache",
                                                       "--exclude=./var/cache/yum",
                                                       "."],
                        shell=False
                        )
                except:
                    if os.path.exists(self.rootCacheFile):
                        os.remove(self.rootCacheFile)
                    raise
        finally:
            self._rootCacheUnlock()