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

# This file is part of Buildbot.  Buildbot 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, version 2. 

# 

# 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 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., 51 

# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 

# 

# Copyright Buildbot Team Members 

 

import textwrap 

from twisted.internet import defer 

from twisted.python import log 

from twisted.application import internet, service 

from buildbot import config 

from buildbot.db import enginestrategy 

from buildbot.db import pool, model, changes, schedulers, sourcestamps, sourcestampsets 

from buildbot.db import state, buildsets, buildrequests, builds, users 

 

class DatabaseNotReadyError(Exception): 

    pass 

 

upgrade_message = textwrap.dedent("""\ 

 

    The Buildmaster database needs to be upgraded before this version of 

    buildbot can run.  Use the following command-line 

 

        buildbot upgrade-master path/to/master 

 

    to upgrade the database, and try starting the buildmaster again.  You may 

    want to make a backup of your buildmaster before doing so. 

    """).strip() 

 

class DBConnector(config.ReconfigurableServiceMixin, service.MultiService): 

    # The connection between Buildbot and its backend database.  This is 

    # generally accessible as master.db, but is also used during upgrades. 

    # 

    # Most of the interesting operations available via the connector are 

    # implemented in connector components, available as attributes of this 

    # object, and listed below. 

 

    # Period, in seconds, of the cleanup task.  This master will perform 

    # periodic cleanup actions on this schedule. 

    CLEANUP_PERIOD = 3600 

 

    def __init__(self, master, basedir): 

        service.MultiService.__init__(self) 

        self.setName('db') 

        self.master = master 

        self.basedir = basedir 

 

        # not configured yet - we don't build an engine until the first 

        # reconfig 

        self.configured_url = None 

 

        # set up components 

        self._engine = None # set up in reconfigService 

        self.pool = None # set up in reconfigService 

        self.model = model.Model(self) 

        self.changes = changes.ChangesConnectorComponent(self) 

        self.schedulers = schedulers.SchedulersConnectorComponent(self) 

        self.sourcestamps = sourcestamps.SourceStampsConnectorComponent(self) 

        self.sourcestampsets = sourcestampsets.SourceStampSetsConnectorComponent(self) 

        self.buildsets = buildsets.BuildsetsConnectorComponent(self) 

        self.buildrequests = buildrequests.BuildRequestsConnectorComponent(self) 

        self.state = state.StateConnectorComponent(self) 

        self.builds = builds.BuildsConnectorComponent(self) 

        self.users = users.UsersConnectorComponent(self) 

 

        self.cleanup_timer = internet.TimerService(self.CLEANUP_PERIOD, 

                self._doCleanup) 

        self.cleanup_timer.setServiceParent(self) 

 

 

    def setup(self, check_version=True, verbose=True): 

        db_url = self.configured_url = self.master.config.db['db_url'] 

 

        log.msg("Setting up database with URL %r" % (db_url,)) 

 

        # set up the engine and pool 

        self._engine = enginestrategy.create_engine(db_url, 

                                basedir=self.basedir) 

        self.pool = pool.DBThreadPool(self._engine, verbose=verbose) 

 

        # make sure the db is up to date, unless specifically asked not to 

        if check_version: 

            d = self.model.is_current() 

            def check_current(res): 

                if not res: 

                    for l in upgrade_message.split('\n'): 

                        log.msg(l) 

                    raise DatabaseNotReadyError() 

            d.addCallback(check_current) 

        else: 

            d = defer.succeed(None) 

 

        return d 

 

 

    def reconfigService(self, new_config): 

        # double-check -- the master ensures this in config checks 

        assert self.configured_url == new_config.db['db_url'] 

 

        return config.ReconfigurableServiceMixin.reconfigService(self, 

                                                            new_config) 

 

 

    def _doCleanup(self): 

        """ 

        Perform any periodic database cleanup tasks. 

 

        @returns: Deferred 

        """ 

        # pass on this if we're not configured yet 

        if not self.configured_url: 

            return 

 

        d = self.changes.pruneChanges(self.master.config.changeHorizon) 

        d.addErrback(log.err, 'while pruning changes') 

        return d