summaryrefslogtreecommitdiffstats
path: root/py/mock/plugins/root_cache.py
blob: 8bcff23b789c58622244d5c1c5c11a0f15b5260b (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
# 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",
                                                       "."],
                        shell=False
                        )
                except:
                    if os.path.exists(self.rootCacheFile):
                        os.remove(self.rootCacheFile)
                    raise
        finally:
            self._rootCacheUnlock()