From 3bb259bb8990f7d47c865e2130ef8a12dec69182 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 18 Nov 2017 20:49:07 +0100 Subject: Refactor: move WorkAPI into its own module --- ick2/__init__.py | 2 +- ick2/controllerapi.py | 205 +--------------------------------------------- ick2/workapi.py | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++ without-tests | 1 + 4 files changed, 222 insertions(+), 205 deletions(-) create mode 100644 ick2/workapi.py diff --git a/ick2/__init__.py b/ick2/__init__.py index 6c19bed..2b4bae8 100644 --- a/ick2/__init__.py +++ b/ick2/__init__.py @@ -32,9 +32,9 @@ from .apibase import APIbase, ResourceApiBase from .buildsapi import BuildsAPI from .logapi import LogAPI from .versionapi import VersionAPI +from .workapi import WorkAPI from .workerapi import WorkerAPI from .controllerapi import ( ControllerAPI, ProjectAPI, - WorkAPI, ) diff --git a/ick2/controllerapi.py b/ick2/controllerapi.py index 5d5e86d..4283e82 100644 --- a/ick2/controllerapi.py +++ b/ick2/controllerapi.py @@ -33,7 +33,7 @@ class ControllerAPI: '/builds': ick2.BuildsAPI, '/logs': ick2.LogAPI, '/projects': ProjectAPI, - '/work': WorkAPI, + '/work': ick2.WorkAPI, '/workers': ick2.WorkerAPI, } @@ -114,206 +114,3 @@ class ProjectAPI(ick2.ResourceApiBase): 'project': project, 'builds': p.get('builds', []), } - - -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': '{}/'.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 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 . + + +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': '{}/'.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 diff --git a/without-tests b/without-tests index 51270a1..760d380 100644 --- a/without-tests +++ b/without-tests @@ -7,4 +7,5 @@ ick2/logging.py ick2/responses.py ick2/version.py ick2/versionapi.py +ick2/workapi.py ick2/workerapi.py -- cgit v1.2.1