diff options
Diffstat (limited to 'lorrycontroller/givemejob.py')
-rw-r--r-- | lorrycontroller/givemejob.py | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/lorrycontroller/givemejob.py b/lorrycontroller/givemejob.py new file mode 100644 index 0000000..43abcc8 --- /dev/null +++ b/lorrycontroller/givemejob.py @@ -0,0 +1,130 @@ +# 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) + + readdb = self.open_statedb() + if readdb.get_running_queue() and not self.max_jobs_reached(readdb): + statedb = self.open_statedb() + with 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.GitanoCommand('localhost', 'ssh', None, None) + 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 = self.new_gitano_command(statedb, lorry_info['from_trovehost']) + local = lorrycontroller.GitanoCommand('localhost', 'ssh', None, None) + + 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: The following is commented out, for now. We need + # a good way to report such errors. However, we probably + # don't want to fail the request. + if False: + bottle.abort(500) + + 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 |