From c7711285040dc4f9e6a1c660dd76fede965cc5d1 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 10 Oct 2019 12:53:15 -0700 Subject: [PATCH] =?UTF-8?q?[7.4]=20[ci/failed=5Ftests=5Freporter]=20when?= =?UTF-8?q?=20Github=20500s,=20retry=20up=20to=E2=80=A6=20(#47854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ci/failed_tests_reporter] when Github 500s, retry up to 5 times * remove unused import * properly handle non-string headers --- .../src/failed_tests_reporter/github_api.ts | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/packages/kbn-test/src/failed_tests_reporter/github_api.ts b/packages/kbn-test/src/failed_tests_reporter/github_api.ts index 4f8b339072ef4..6bf72f6fc7c38 100644 --- a/packages/kbn-test/src/failed_tests_reporter/github_api.ts +++ b/packages/kbn-test/src/failed_tests_reporter/github_api.ts @@ -21,7 +21,7 @@ import Url from 'url'; import Axios, { AxiosRequestConfig } from 'axios'; import parseLinkHeader from 'parse-link-header'; -import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils'; +import { ToolingLog, isAxiosResponseError, isAxiosRequestError } from '@kbn/dev-utils'; const BASE_URL = 'https://api.github.com/repos/elastic/kibana/'; @@ -33,7 +33,11 @@ export interface GithubIssue { body: string; } -type RequestOptions = AxiosRequestConfig & { safeForDryRun?: boolean }; +type RequestOptions = AxiosRequestConfig & { + safeForDryRun?: boolean; + maxAttempts?: number; + attempt?: number; +}; export class GithubApi { private readonly x = Axios.create({ @@ -78,7 +82,8 @@ export class GithubApi { issues.push(issue); } - const parsed = parseLinkHeader(resp.headers.link); + const parsed = + typeof resp.headers.link === 'string' ? parseLinkHeader(resp.headers.link) : undefined; if (parsed && parsed.next && parsed.next.url) { nextRequest = { safeForDryRun: true, @@ -139,31 +144,64 @@ export class GithubApi { return resp.data.html_url; } - private async request(options: RequestOptions, dryRunResponse: T) { + private async request( + options: RequestOptions, + dryRunResponse: T + ): Promise<{ + status: number; + statusText: string; + headers: Record; + data: T; + }> { const executeRequest = !this.dryRun || options.safeForDryRun; + const maxAttempts = options.maxAttempts || 5; + const attempt = options.attempt || 1; + this.log.verbose('Github API', executeRequest ? 'Request' : 'Dry Run', options); - if (executeRequest) { - try { - return await this.x.request(options); - } catch (error) { - if (isAxiosResponseError(error)) { - throw new Error( - `[${error.config.method} ${error.config.url}] ${error.response.status} ${ - error.response.statusText - } Error: ${JSON.stringify(error.response.data)}` - ); + if (!executeRequest) { + return { + status: 200, + statusText: 'OK', + headers: {}, + data: dryRunResponse, + }; + } + + try { + return await this.x.request(options); + } catch (error) { + const unableToReachGithub = isAxiosRequestError(error); + const githubApiFailed = isAxiosResponseError(error) && error.response.status >= 500; + const errorResponseLog = + isAxiosResponseError(error) && + `[${error.config.method} ${error.config.url}] ${error.response.status} ${error.response.statusText} Error`; + + if ((unableToReachGithub || githubApiFailed) && attempt < maxAttempts) { + const waitMs = 1000 * attempt; + + if (errorResponseLog) { + this.log.error(`${errorResponseLog}: waiting ${waitMs}ms to retry`); + } else { + this.log.error(`Unable to reach github, waiting ${waitMs}ms to retry`); } - throw error; + await new Promise(resolve => setTimeout(resolve, waitMs)); + return await this.request( + { + ...options, + maxAttempts, + attempt: attempt + 1, + }, + dryRunResponse + ); } - } - return { - status: 200, - statusText: 'OK', - headers: {}, - data: dryRunResponse, - }; + if (errorResponseLog) { + throw new Error(`${errorResponseLog}: ${JSON.stringify(error.response.data)}`); + } + + throw error; + } } }