Source code for backend.frontend

import json
import time
import logging
from requests import get, post, put, RequestException

from backend.exceptions import FrontendClientException

# prolong the sleep time before asking frontend again
SLEEP_INCREMENT_TIME = 5
# reasonable timeout for requests that block backend daemon
BACKEND_TIMEOUT = 2*60

[docs]class FrontendClientRetryError(Exception): pass
[docs]class FrontendClient(object): """ Object to send data back to fronted """ # do we block the main daemon process? try_indefinitely = False def __init__(self, opts, logger=None, try_indefinitely=False): self.frontend_url = "{}/backend".format(opts.frontend_base_url) self.frontend_auth = opts.frontend_auth self.try_indefinitely = try_indefinitely self.msg = None self.logger = logger @property def log(self): 'return configured logger object, or no-op logger' if not self.logger: self.logger = logging.getLogger(__name__) self.logger.addHandler(logging.NullHandler()) return self.logger
[docs] def _frontend_request(self, url_path, data=None, authenticate=True, method='post'): headers = {"content-type": "application/json"} url = "{}/{}/".format(self.frontend_url, url_path) auth = ("user", self.frontend_auth) if authenticate else None try: kwargs = { 'auth': auth, 'headers': headers, } method = method.lower() if method in ['post', 'put']: kwargs['data'] = json.dumps(data) method = post if method == 'post' else put else: method = get response = method(url, **kwargs) except RequestException as ex: raise FrontendClientRetryError( "Requests error on {}: {}".format(url, str(ex))) if response.status_code >= 500: # Server error. Hopefully this is only temporary problem, we wan't # to re-try, and wait till the server works again. raise FrontendClientRetryError( "Request server error on {}: {} {}".format( url, response.status_code, response.reason)) if response.status_code >= 400: # Client error. The mistake is on our side, it doesn't make sense # to continue with retries. raise FrontendClientException( "Request client error on {}: {} {}".format( url, response.status_code, response.reason)) # TODO: Success, but tighten the redirects etc. return response
[docs] def _frontend_request_repeatedly(self, url_path, method='post', data=None, authenticate=True): """ Repeat the request until it succeeds, or timeout is reached. """ sleep = SLEEP_INCREMENT_TIME start = time.time() stop = start + BACKEND_TIMEOUT i = 0 while True: i += 1 if not self.try_indefinitely and time.time() > stop: raise FrontendClientException( "Attempt to talk to frontend timeouted " "(we gave it {} attempts)".format(i)) try: return self._frontend_request(url_path, data=data, authenticate=authenticate, method=method) except FrontendClientRetryError as ex: self.log.warning("Retry request #%s on %s: %s", i, url_path, str(ex)) time.sleep(sleep) sleep += SLEEP_INCREMENT_TIME
[docs] def _post_to_frontend_repeatedly(self, data, url_path): """ Repeat the request until it succeeds, or timeout is reached. """ return self._frontend_request_repeatedly(url_path, data=data)
[docs] def get(self, url_path): 'Issue relentless GET request to Frontend' return self._frontend_request_repeatedly(url_path, method='get')
[docs] def post(self, url_path, data): 'Issue relentless POST request to Frontend' return self._frontend_request_repeatedly(url_path, data=data)
[docs] def put(self, url_path, data): 'Issue relentless POST request to Frontend' return self._frontend_request_repeatedly(url_path, data=data, method='put')
[docs] def update(self, data): """ Send data to be updated in the frontend """ self._post_to_frontend_repeatedly(data, "update")
[docs] def starting_build(self, data): """ Announce to the frontend that a build is starting. :return: True if the build can start or False if the build can not start (can be cancelled or deleted). """ response = self._post_to_frontend_repeatedly(data, "starting_build") if "can_start" not in response.json(): raise FrontendClientException("Bad response from the frontend") return response.json()["can_start"]
[docs] def reschedule_build(self, build_id, task_id, chroot_name): """ Announce to the frontend that a build should be rescheduled (set pending state). """ data = {"build_id": build_id, "task_id": task_id, "chroot": chroot_name} self._post_to_frontend_repeatedly(data, "reschedule_build_chroot")
[docs] def reschedule_all_running(self): response = self._post_to_frontend_repeatedly({}, "reschedule_all_running") if response.status_code != 200: raise FrontendClientException("Failed to reschedule builds")