mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-09-06 13:38:59 +00:00
* feat(ci): add aliyun ecs ephemeral runner path for query regression Signed-off-by: paomian <xpaomian@gmail.com> * fix: improve condition for query-regression job execution in workflow * feat: update Docker installation to use official repository and add GPG key handling * Refactor query regression runner setup and configuration - Removed deprecated PersistentVolumeClaim for build cache. - Introduced a new bootstrap script for setting up the ECS runner host. - Deleted obsolete Helm values files for runner configuration. - Updated the Aliyun ECS runner provisioning script to reflect new cache paths. - Modified GitHub workflows to use the new Aliyun ECS runner setup. - Adjusted documentation to clarify the new runner lifecycle and provisioning process. * fix: enhance runner service management during bootstrap process * fix: update alibabacloud_tea_openapi dependency version in metadata * feat: enhance ECS runner scripts with region_id and resource_group_id support * fix: move containerd content store to data root for improved storage management * feat: rename query-regression runner to ephemeral-github runner and update related scripts * fix: update sentinel polling method to use serial console output for improved reliability * fix: add environment variable checks for Alibaba Cloud access keys in ECS client * fix: improve error handling in GitHub API requests for better diagnostics * fix: improve cache disk detection logic for Aliyun ECS instances * fix: enhance cache disk waiting logic with detailed output and error handling * fix: update dependency version for alibabacloud_tea_openapi in teardown script * fix: enhance cache disk waiting logic for better compatibility and clarity * fix: enhance console output handling and add incremental logging during instance provisioning * fix: add PATH environment variable for runner jobs in service and provision script * fix: add machine telemetry sampling and logging during query regression jobs * fix: update query regression documentation and provision script for cache disk handling * fix: update SCCACHE_CACHE_SIZE validation to 10G for improved caching efficiency * fix: remove outdated cache size checks and cleanup logic for fresh system disk runs * fix: enhance instance deletion logic with region handling and console output export * fix: add swap file setup and OOM handling for ECS runner to improve stability * fix: update OOM handling and service restart logic for ECS runner to enhance stability * fix: increase system disk size to 100 GiB for cold double nightly builds to prevent ENOSPC errors * fix: increase system disk size to 150 GiB for ECS runner to prevent ENOSPC errors * fix: add keep_instance option to preserve ECS instance for post-mortem debugging * fix: disable unattended upgrades to prevent job cancellations during library updates * fix: reduce system disk size to 40 GiB for ECS runner to prevent ENOSPC errors * feat: Refactor Aliyun ECS runner provisioning and introduce nightly regression comparison - Update `aliyun-ecs-runner-provision.py` to remove cache disk handling, simplifying the provisioning process. - Introduce `query-regression-nightly-refs.py` to resolve and compare SHAs from successful nightly builds. - Create `query-regression-nightly.yml` workflow to trigger nightly comparisons based on successful builds. - Enhance `query-regression.yml` to include a `test-tooling` job for validating Python scripts before provisioning. - Update tests for the new nightly reference selection logic and refactor existing tests to align with the new caching strategy. - Modify documentation to reflect changes in caching and nightly comparison workflows. * fix: enhance runner image tool verification with detailed checks * fix: improve error handling in runner image tool verification * fix: update tool versions in ECS image and workflow for consistency * fix: correct typo in error message for unparseable ECS creation time * fix: update README and workflow files for query regression tests and image hygiene --------- Signed-off-by: paomian <xpaomian@gmail.com>
271 lines
8.7 KiB
Python
271 lines
8.7 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright 2023 Greptime Team
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Resolve consecutive successful Nightly Build SHAs for query-regression.
|
|
|
|
Query regression builds both binaries from git; this script only picks the
|
|
head SHAs of two Nightly Build workflow runs (today vs the previous success).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
import urllib.error
|
|
import urllib.parse
|
|
import urllib.request
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
|
|
NIGHTLY_WORKFLOW = "nightly-build.yml"
|
|
NIGHTLY_EVENTS = frozenset({"schedule", "workflow_dispatch"})
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class WorkflowRun:
|
|
id: int
|
|
head_sha: str
|
|
head_branch: str
|
|
html_url: str
|
|
created_at: str
|
|
conclusion: str
|
|
event: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class RefPair:
|
|
base: WorkflowRun | None
|
|
candidate: WorkflowRun | None
|
|
skip: bool
|
|
reason: str
|
|
|
|
|
|
def parse_run(payload: dict[str, Any]) -> WorkflowRun:
|
|
return WorkflowRun(
|
|
id=int(payload["id"]),
|
|
head_sha=str(payload["head_sha"]),
|
|
head_branch=str(payload.get("head_branch") or ""),
|
|
html_url=str(payload.get("html_url") or ""),
|
|
created_at=str(payload.get("created_at") or ""),
|
|
conclusion=str(payload.get("conclusion") or ""),
|
|
event=str(payload.get("event") or ""),
|
|
)
|
|
|
|
|
|
def github_get(token: str, path: str, query: dict[str, str] | None = None) -> dict[str, Any]:
|
|
encoded = f"?{urllib.parse.urlencode(query)}" if query else ""
|
|
request = urllib.request.Request(
|
|
f"https://api.github.com{path}{encoded}",
|
|
method="GET",
|
|
headers={
|
|
"Accept": "application/vnd.github+json",
|
|
"Authorization": f"Bearer {token}",
|
|
"X-GitHub-Api-Version": "2022-11-28",
|
|
},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(request, timeout=30) as response:
|
|
return json.loads(response.read().decode("utf-8"))
|
|
except urllib.error.HTTPError as error:
|
|
body = error.read().decode("utf-8", "replace")
|
|
raise SystemExit(
|
|
f"GitHub API GET {path} failed: HTTP {error.code}: {body}"
|
|
) from error
|
|
|
|
|
|
def list_successful_nightly_runs(
|
|
token: str,
|
|
repo: str,
|
|
*,
|
|
branch: str | None = None,
|
|
per_page: int = 30,
|
|
) -> list[WorkflowRun]:
|
|
query = {
|
|
"status": "success",
|
|
"per_page": str(per_page),
|
|
}
|
|
if branch:
|
|
query["branch"] = branch
|
|
payload = github_get(
|
|
token,
|
|
f"/repos/{repo}/actions/workflows/{NIGHTLY_WORKFLOW}/runs",
|
|
query,
|
|
)
|
|
runs = [parse_run(item) for item in payload.get("workflow_runs") or []]
|
|
return [
|
|
run
|
|
for run in runs
|
|
if run.conclusion == "success" and run.event in NIGHTLY_EVENTS and run.head_sha
|
|
]
|
|
|
|
|
|
def fetch_run(token: str, repo: str, run_id: int) -> WorkflowRun:
|
|
payload = github_get(token, f"/repos/{repo}/actions/runs/{run_id}")
|
|
return parse_run(payload)
|
|
|
|
|
|
def select_base_and_candidate(
|
|
runs: list[WorkflowRun],
|
|
*,
|
|
candidate_run_id: int | None = None,
|
|
candidate: WorkflowRun | None = None,
|
|
) -> RefPair:
|
|
"""Pick candidate (newest / requested) and the previous successful run.
|
|
|
|
`runs` must be newest-first. Same-SHA consecutive nightlies are skipped
|
|
(no commits that day).
|
|
"""
|
|
if candidate is None:
|
|
if candidate_run_id is not None:
|
|
candidate = next((run for run in runs if run.id == candidate_run_id), None)
|
|
if candidate is None:
|
|
return RefPair(
|
|
None,
|
|
None,
|
|
True,
|
|
f"candidate nightly run {candidate_run_id} was not a successful "
|
|
f"{NIGHTLY_WORKFLOW} run",
|
|
)
|
|
elif runs:
|
|
candidate = runs[0]
|
|
else:
|
|
return RefPair(None, None, True, "no successful Nightly Build runs")
|
|
|
|
branch = candidate.head_branch
|
|
older = [
|
|
run
|
|
for run in runs
|
|
if run.id != candidate.id
|
|
and (not branch or not run.head_branch or run.head_branch == branch)
|
|
and (not candidate.created_at or run.created_at <= candidate.created_at)
|
|
]
|
|
if not older:
|
|
return RefPair(
|
|
None,
|
|
candidate,
|
|
True,
|
|
"no previous successful Nightly Build to compare against",
|
|
)
|
|
base = older[0]
|
|
if base.head_sha.lower() == candidate.head_sha.lower():
|
|
return RefPair(
|
|
base,
|
|
candidate,
|
|
True,
|
|
f"previous nightly SHA {base.head_sha} matches candidate; nothing to compare",
|
|
)
|
|
return RefPair(base, candidate, False, "")
|
|
|
|
|
|
def _override_run(sha: str) -> WorkflowRun:
|
|
return WorkflowRun(
|
|
id=0,
|
|
head_sha=sha,
|
|
head_branch="",
|
|
html_url="",
|
|
created_at="",
|
|
conclusion="success",
|
|
event="workflow_dispatch",
|
|
)
|
|
|
|
|
|
def override_pair(base_ref: str, candidate_ref: str) -> RefPair:
|
|
return RefPair(_override_run(base_ref), _override_run(candidate_ref), False, "explicit refs")
|
|
|
|
|
|
def write_outputs(pair: RefPair) -> None:
|
|
values = {
|
|
"skip": "true" if pair.skip else "false",
|
|
"reason": pair.reason,
|
|
"base_sha": pair.base.head_sha if pair.base else "",
|
|
"candidate_sha": pair.candidate.head_sha if pair.candidate else "",
|
|
"base_run_url": pair.base.html_url if pair.base else "",
|
|
"candidate_run_url": pair.candidate.html_url if pair.candidate else "",
|
|
"base_run_id": str(pair.base.id) if pair.base and pair.base.id else "",
|
|
"candidate_run_id": str(pair.candidate.id) if pair.candidate and pair.candidate.id else "",
|
|
}
|
|
output_path = os.environ.get("GITHUB_OUTPUT")
|
|
if output_path:
|
|
with open(output_path, "a", encoding="utf-8") as handle:
|
|
for key, value in values.items():
|
|
handle.write(f"{key}={value}\n")
|
|
for key, value in values.items():
|
|
print(f"{key}={value}")
|
|
|
|
|
|
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--repo", default=os.environ.get("GITHUB_REPOSITORY"))
|
|
parser.add_argument(
|
|
"--token",
|
|
default=os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN"),
|
|
)
|
|
parser.add_argument("--branch", default=os.environ.get("NIGHTLY_BRANCH") or "")
|
|
parser.add_argument(
|
|
"--candidate-run-id",
|
|
default=os.environ.get("CANDIDATE_RUN_ID") or "",
|
|
)
|
|
parser.add_argument("--base-ref", default=os.environ.get("BASE_REF") or "")
|
|
parser.add_argument("--candidate-ref", default=os.environ.get("CANDIDATE_REF") or "")
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = parse_args(argv)
|
|
base_ref = args.base_ref.strip()
|
|
candidate_ref = args.candidate_ref.strip()
|
|
if base_ref and candidate_ref:
|
|
write_outputs(override_pair(base_ref, candidate_ref))
|
|
return 0
|
|
if bool(base_ref) != bool(candidate_ref):
|
|
print("Provide both --base-ref and --candidate-ref, or neither.", file=sys.stderr)
|
|
return 2
|
|
if not args.repo or not args.token:
|
|
print("--repo and --token (or GITHUB_REPOSITORY / GITHUB_TOKEN) are required.", file=sys.stderr)
|
|
return 2
|
|
|
|
candidate_run_id = int(args.candidate_run_id) if str(args.candidate_run_id).strip() else None
|
|
candidate: WorkflowRun | None = None
|
|
branch = args.branch.strip() or None
|
|
if candidate_run_id is not None:
|
|
candidate = fetch_run(args.token, args.repo, candidate_run_id)
|
|
if candidate.conclusion != "success":
|
|
write_outputs(
|
|
RefPair(
|
|
None,
|
|
candidate,
|
|
True,
|
|
f"nightly run {candidate_run_id} conclusion is {candidate.conclusion}",
|
|
)
|
|
)
|
|
return 0
|
|
branch = branch or candidate.head_branch or None
|
|
|
|
runs = list_successful_nightly_runs(args.token, args.repo, branch=branch)
|
|
pair = select_base_and_candidate(
|
|
runs,
|
|
candidate_run_id=candidate_run_id if candidate is None else None,
|
|
candidate=candidate,
|
|
)
|
|
write_outputs(pair)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|