summaryrefslogtreecommitdiff
path: root/lorrycontroller/givemejob.py
blob: 755def08977164ca9938821371df21849251220b (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
# Copyright (C) 2014  Codethink Limited
#
# 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; version 2 of the License.
#
# 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.


import collections
import logging
import re
import time

import bottle
import cliapp

import lorrycontroller


class GiveMeJob(lorrycontroller.LorryControllerRoute):

    http_method = 'POST'
    path = '/1.0/give-me-job'

    def run(self, **kwargs):
        logging.info('%s %s called', self.http_method, self.path)

        statedb = self.open_statedb()
        with statedb:
            if statedb.get_running_queue() and not self.max_jobs_reached(statedb):
                lorry_infos = statedb.get_all_lorries_info()
                now = statedb.get_current_time()
                for lorry_info in lorry_infos:
                    if self.ready_to_run(lorry_info, now):
                        self.create_repository_in_local_trove(
                            statedb, lorry_info)
                        if lorry_info['from_trovehost']:
                            self.copy_repository_metadata(statedb, lorry_info)
                        self.give_job_to_minion(statedb, lorry_info, now)
                        logging.info(
                            'Giving job %s to lorry %s to MINION %s:%s',
                            lorry_info['job_id'],
                            lorry_info['path'],
                            bottle.request.forms.host,
                            bottle.request.forms.pid)
                        return lorry_info

        logging.info('No job to give MINION')
        return { 'job_id': None }

    def max_jobs_reached(self, statedb):
        max_jobs = statedb.get_max_jobs()
        if max_jobs is None:
            return False
        running_jobs = statedb.get_running_jobs()
        return len(running_jobs) >= max_jobs

    def ready_to_run(self, lorry_info, now):
        due = lorry_info['last_run'] + lorry_info['interval']
        return (lorry_info['running_job'] is None and due <= now)

    def create_repository_in_local_trove(self, statedb, lorry_info):
        # Create repository on local Trove. If it fails, assume
        # it failed because the repository already existed, and
        # ignore the failure (but log message).

        local = lorrycontroller.LocalTroveGitanoCommand()
        try:
            local.create(lorry_info['path'])
        except lorrycontroller.GitanoCommandFailure as e:
            logging.debug(
                'Ignoring error creating %s on local Trove: %s',
                lorry_info['path'], e)
        else:
            logging.info('Created %s on local repo', lorry_info['path'])

    def copy_repository_metadata(self, statedb, lorry_info):
        '''Copy project.head and project.description to the local Trove.'''

        assert lorry_info['from_trovehost']
        assert lorry_info['from_path']

        remote = lorrycontroller.new_gitano_command(statedb, lorry_info['from_trovehost'])
        local = lorrycontroller.LocalTroveGitanoCommand()

        try:
            remote_config = remote.get_gitano_config(lorry_info['from_path'])
            local_config = local.get_gitano_config(lorry_info['path'])

            if remote_config['project.head'] != local_config['project.head']:
                local.set_gitano_config(
                    lorry_info['path'],
                    'project.head',
                    remote_config['project.head'])

            if not local_config['project.description']:
                desc = '{host}: {desc}'.format(
                    host=lorry_info['from_trovehost'],
                    desc=remote_config['project.description'])
                local.set_gitano_config(
                    lorry_info['path'],
                    'project.description',
                    desc)
        except lorrycontroller.GitanoCommandFailure as e:
            logging.error('ERROR: %s' % str(e))
            # FIXME: We need a good way to report these errors to the
            # user. However, we probably don't want to fail the
            # request, so that's not the way to do this. Needs
            # thinking.

    def give_job_to_minion(self, statedb, lorry_info, now):
        path = lorry_info['path']
        minion_host = bottle.request.forms.host
        minion_pid = bottle.request.forms.pid
        running_job = statedb.get_next_job_id()
        statedb.set_running_job(path, running_job)
        statedb.add_new_job(
            running_job, minion_host, minion_pid, path, int(now))
        lorry_info['job_id'] = running_job
        return lorry_info