diff options
author | Lars Wirzenius <liw@liw.fi> | 2017-11-18 20:49:07 +0100 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2017-11-18 20:49:07 +0100 |
commit | 3bb259bb8990f7d47c865e2130ef8a12dec69182 (patch) | |
tree | ffe681e452624e85acea0e7c911941ff5ea065f7 /ick2/workapi.py | |
parent | 5f26bbcc37eb4bf78c1ab1e231a55ec98f2e3d4e (diff) | |
download | ick2-3bb259bb8990f7d47c865e2130ef8a12dec69182.tar.gz |
Refactor: move WorkAPI into its own module
Diffstat (limited to 'ick2/workapi.py')
-rw-r--r-- | ick2/workapi.py | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/ick2/workapi.py b/ick2/workapi.py new file mode 100644 index 0000000..3f0b195 --- /dev/null +++ b/ick2/workapi.py @@ -0,0 +1,219 @@ +# Copyright (C) 2017 Lars Wirzenius +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + + +import ick2 + + +class WorkAPI(ick2.APIbase): + + def __init__(self, state): + super().__init__(state) + self._type_name = 'work' + + def get_routes(self, path): # pragma: no cover + return [ + { + 'method': 'GET', + 'path': '{}/<worker>'.format(path), + 'callback': self.GET(self.get_work), + }, + { + 'method': 'POST', + 'path': path, + 'callback': self.POST(self.update_work), + }, + ] + + def get_work(self, worker): + worker_state = self._get_worker(worker) + if not worker_state.get('doing'): + project, pipeline = self._pick_triggered_pipeline() + if project is None: + doing = {} + else: + pipeline['status'] = 'building' + self._update_project(project) + + build_id = self._start_build(project, pipeline, worker) + self._start_log(build_id) + + index = 0 + doing = { + 'build_id': build_id, + 'worker': worker, + 'project': project['project'], + 'pipeline': pipeline['name'], + 'step': pipeline['actions'][index], + 'step_index': index, + 'log': '/logs/{}'.format(build_id), + } + + worker_state = { + 'worker': worker, + 'doing': doing, + } + self._update_worker(worker_state) + + return worker_state['doing'] + + def _get_worker(self, worker): # pragma: no cover + try: + return self._state.get_resource('workers', worker) + except ick2.NotFound: + return { + 'worker': worker, + } + + def _update_worker(self, worker_state): + self._state.update_resource( + 'workers', worker_state['worker'], worker_state) + + def _pick_triggered_pipeline(self): + projects = self._get_projects() + for project in projects: + for pipeline in project['pipelines']: + if pipeline.get('status') == 'triggered': + return project, pipeline + return None, None + + def _get_projects(self): + return self._state.get_resources('projects') + + def _update_project(self, project): + self._state.update_resource('projects', project['project'], project) + + def update_work(self, update): + if 'worker' not in update: # pragma: no cover + raise ick2.BadUpdate('no worker specified') + + worker_state = self._get_worker(update['worker']) + doing = worker_state.get('doing', {}) + self._check_work_update(doing, update) + + project, pipeline = self._get_pipeline( + update['project'], update['pipeline']) + self._append_to_build_log(update) + + ick2.log.log( + 'trace', + msg_text='xxx update_work', + update=update, + project=project, + pipeline=pipeline, + doing=doing) + + exit_code = update.get('exit_code') + if exit_code == 0: + index = doing['step_index'] + 1 + actions = pipeline['actions'] + if index >= len(actions): + pipeline['status'] = 'idle' + doing = {} + self._finish_build(update) + else: + doing['step_index'] = index + doing['step'] = actions[index] + self._update_project(project) + + worker_state = { + 'worker': update['worker'], + 'doing': doing, + } + self._update_worker(worker_state) + elif exit_code is not None: + assert isinstance(exit_code, int) + assert exit_code != 0 + pipeline['status'] = 'idle' + self._update_project(project) + self._finish_build(update) + + worker_state = { + 'worker': update['worker'], + 'doing': {}, + } + self._update_worker(worker_state) + + def _check_work_update(self, doing, update): # pragma: no cover + must_match = ['worker', 'project', 'pipeline', 'build_id'] + for name in must_match: + if name not in update: + raise ick2.BadUpdate('{} not specified'.format(name)) + if doing.get(name) != update[name]: + raise ick2.BadUpdate( + '{} differs from current work: {} vs {}'.format( + name, doing.get(name), update[name])) + + def _get_pipeline(self, project, pipeline): # pragma: no cover + projects = self._get_projects() + for p in projects: + for pl in p['pipelines']: + if pl.get('name') == pipeline: + return p, pl + raise ick2.NotFound() + + def _start_build(self, project, pipeline, worker): + ick2.log.log('info', msg_text='Starting new build') + build_id = project.get('build_id', 0) + build_id += 1 + project['build_id'] = build_id + self._update_project(project) + build = { + 'build_id': build_id, + 'log': '/logs/{}'.format(build_id), + 'worker': worker, + 'project': project['project'], + 'pipeline': pipeline['name'], + 'status': 'building', + } + self._state.add_resource('builds', str(build_id), build) + return build_id + + def _start_log(self, build_id): + ick2.log.log('info', msg_text='Starting new log', build_id=build_id) + log = { + 'build_id': build_id, + 'log': '', + } + self._state.add_resource('log', str(build_id), log) + return build_id + + def _append_to_build_log(self, update): + build_id = update['build_id'] + log = self._state.get_resource('log', str(build_id)) + for kind in ['stdout', 'stderr']: + text = update.get(kind, '') + if text is not None: + log['log'] += text + self._state.update_resource('log', str(build_id), log) + + def _finish_build(self, update): + build = self._state.get_resource('builds', str(update['build_id'])) + build['status'] = update['exit_code'] + self._state.update_resource('builds', str(update['build_id']), build) + + def create(self, *args, **kwargs): # pragma: no cover + pass + + def update(self, *args, **kwargs): # pragma: no cover + pass + + def list(self, *args, **kwargs): # pragma: no cover + pass + + def show(self, *args, **kwargs): # pragma: no cover + pass + + def delete(self, *args, **kwargs): # pragma: no cover + pass |