Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/backlog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
- name: Check SUSE QE Tools WIP-Limit
id: wip-limit
run: sh -ex backlog-check-wip-limit
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Check step status
id: step_check
if: always()
Expand All @@ -31,7 +34,7 @@ jobs:
if: (success() || failure()) && steps.step_check.outcome == 'failure'
continue-on-error: true
run: |
. chat_notify.sh "matrix.org" "${{steps.step_check.outputs.result}}" \
uv run python3 chat_notify.py "matrix.org" "${{steps.step_check.outputs.result}}" \
"${{secrets.MATRIX_ACCESS_TOKEN}}" "${{secrets.MATRIX_ROOM_ID}}"
set_suse_qe_tools_due_dates:
name: Set SUSE QE Tools due dates
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Setup enviroment
- name: Setup environment
run: uv sync --extra dev
- run: |
sudo apt-get install cpanminus html-xml-utils xmlstarlet
Expand All @@ -36,8 +36,9 @@ jobs:
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true
- name: Setup environment
run: uv sync --extra dev
- name: Static checks
run: |
git config --global --add safe.directory .
uv sync --extra dev
uv run make checkstyle
38 changes: 29 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
SH_FILES ?= $(shell file --mime-type $$(git ls-files) test/*.t | sed -n 's/^\(.*\):.*text\/x-shellscript.*$$/\1/p')
SH_SHELLCHECK_FILES ?= $(shell file --mime-type * | sed -n 's/^\(.*\):.*text\/x-shellscript.*$$/\1/p')
PY_FILES ?= $(shell git ls-files | xargs file --mime-type 2>/dev/null | grep -E 'text/x-script\.python|text/x-python' | cut -d: -f1)
RUNNER ?= uv run

ifndef CI
include .setup.mk
Expand Down Expand Up @@ -34,20 +35,26 @@ test-unit: test-bash test-python
test-bash: $(BPAN)
"${PROVE}" -r $(if $v,-v )$(test)

.PHONY: test-python
test-python:
py.test tests
PYTHONPATH=src:$(PYTHONPATH) $(RUNNER) pytest

test-online:
dry_run=1 bash -x ./openqa-label-known-issues-multi < ./tests/incompletes
dry_run=1 ./trigger-openqa_in_openqa
# Invalid JSON causes the job to abort with an error
-tw_openqa_host=example.com dry_run=1 ./trigger-openqa_in_openqa

checkstyle: test-shellcheck test-yaml checkstyle-python check-code-health test-gitlint
checkstyle: test-shellcheck test-yaml checkstyle-python check-code-health typecheck check-maintainability test-gitlint

shfmt:
shfmt -w ${SH_FILES}

.PHONY: tidy
tidy: ## Format code and fix linting issues
$(RUNNER) ruff format $(PY_FILES)
$(RUNNER) ruff check --fix $(PY_FILES)

test-shellcheck:
@which shfmt >/dev/null 2>&1 || echo "Command 'shfmt' not found, can not execute shell script formating checks"
shfmt -d ${SH_FILES}
Expand All @@ -58,10 +65,11 @@ test-yaml:
@which yamllint >/dev/null 2>&1 || echo "Command 'yamllint' not found, can not execute YAML syntax checks"
yamllint --strict $$(git ls-files "*.yml" "*.yaml" ":!external/")

.PHONY: checkstyle-python
checkstyle-python: check-ruff check-conventions check-ty
check-ruff:
@which ruff >/dev/null 2>&1 || echo "Command 'ruff' not found, can not execute python style checks"
@if [ -n "$(PY_FILES)" ]; then ruff format --check $(PY_FILES) && ruff check $(PY_FILES); fi
@if [ -n "$(PY_FILES)" ]; then $(RUNNER) ruff format --check $(PY_FILES) && $(RUNNER) ruff check $(PY_FILES); fi

check-conventions:
@if git grep -nE '^\s*@(unittest\.mock\.|mock\.)?patch' tests/; then \
Expand All @@ -76,7 +84,24 @@ check-ty: ## Run ty type checker

check-code-health:
@echo "Checking code health…"
@vulture $$(git ls-files "**.py") --min-confidence 80
@$(RUNNER) vulture $$(git ls-files "**.py") --min-confidence 80

.PHONY: typecheck
typecheck:
PYRIGHT_PYTHON_FORCE_VERSION=latest $(RUNNER) pyright --skipunannotated --warnings

.PHONY: check-maintainability
check-maintainability:
@echo "Checking maintainability (grade B or worse) …"
@$(RUNNER) radon mi ${PY_FILES} -n B | (! grep ".")

.PHONY: test-with-coverage
test-with-coverage:
PYTHONPATH=src:$(PYTHONPATH) $(RUNNER) pytest --cov=src/os-autoinst-scripts tests/

.PHONY: install-python-deps
install-python-deps:
$(RUNNER) sync

.PHONY: test-gitlint
test-gitlint: ## Run commit message checks using gitlint
Expand All @@ -85,11 +110,6 @@ test-gitlint: ## Run commit message checks using gitlint
BASE=$$(git merge-base --independent $$BASES | head -n 1); \
gitlint --commits "$$BASE..HEAD"

.PHONY: tidy
tidy: ## Format code and fix linting issues
ruff format $(PY_FILES)
ruff check --fix $(PY_FILES)

update-deps:
tools/update-deps --cpanfile cpanfile --specfile dist/rpm/os-autoinst-scripts-deps.spec

Expand Down
15 changes: 0 additions & 15 deletions chat_notify.sh

This file was deleted.

2 changes: 1 addition & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ main_requires:
perl(Mojo::File):
perl(Text::Markdown):
perl(YAML::PP):
python3-httpx:
python3-pynetbox:
python3-net-snmp:
python3-requests:
python3-sh:
python3-tenacity:
python3-typer:
python3-httpx:
retry:
sed:
sudo:
Expand Down
28 changes: 24 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ classifiers = [

dependencies = [
"httpx",
"requests",
"sh",
"pynetbox",
"tenacity",
"typer",
"requests",
]

[tool.setuptools]
packages = []

[project.optional-dependencies]
dev = [
"pyright",
"pytest",
"pytest-cov",
"pytest-mock",
Expand All @@ -50,6 +48,13 @@ fail_under = 100
show_missing = true
skip_covered = true

[tool.setuptools]
include-package-data = true
zip-safe = false

[tool.setuptools.packages]
find = {}

[tool.ruff]
line-length = 120
preview = true
Expand Down Expand Up @@ -159,3 +164,18 @@ unresolved-import = "ignore"

[tool.ty.terminal]
error-on-warning = true

[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = ["typer.Argument"]
[tool.pyright]
include = ["src", "tests", "*.py"]
exclude = [
"check-netbox-machine-state.py",
"openqa-powermanagement.py",
"tests/test_chat_notify.py",
"tests/test_openqa_bats_review.py",
"tests/test_trigger_bisect_jobs.py",
]

[tool.pytest.ini_options]
pythonpath = "src"
56 changes: 0 additions & 56 deletions reboot-stability-check

This file was deleted.

1 change: 1 addition & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Copyright SUSE LLC
1 change: 1 addition & 0 deletions src/os_autoinst_scripts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Copyright SUSE LLC
48 changes: 48 additions & 0 deletions src/os_autoinst_scripts/chat_notify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright SUSE LLC
import httpx
import typer

app = typer.Typer()


def send_message(server_url: str, message_body: str, access_token: str, room_id: str) -> None:
headers = {"Authorization": f"Bearer {access_token}"}
url = f"https://{server_url}/_matrix/client/r0/rooms/{room_id}/send/m.room.message"
json = {
"msgtype": "m.text",
"body": message_body,
"formatted_body": message_body,
"format": "org.matrix.custom.html",
}
try:
response = httpx.post(url, headers=headers, json=json)
response.raise_for_status()
response_json = response.json()
if "errcode" in response_json:
typer.secho(
f"[!] Something went wrong sending the message: {response_json.get('error', 'Unknown error')}",
err=True,
fg=typer.colors.RED,
)
raise typer.Exit(1)
typer.secho("[+] Message sent!", fg=typer.colors.GREEN)
except httpx.HTTPStatusError as e:
typer.secho(f"[!] HTTP error sending message: {e}", err=True, fg=typer.colors.RED)
raise typer.Exit(1) from e
except httpx.RequestError as e:
typer.secho(f"[!] Network error sending message: {e}", err=True, fg=typer.colors.RED)
raise typer.Exit(1) from e


@app.command()
def main(
server_url: str,
message_body: str,
access_token: str,
room_id: str,
) -> None:
send_message(server_url, message_body, access_token, room_id)


if __name__ == "__main__":
app()
65 changes: 65 additions & 0 deletions src/os_autoinst_scripts/reboot_stability_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/python3
# Copyright SUSE LLC
"""The script checks the reboot stability of a list of hosts."""

import sys
import time

import typer
from rich.console import Console
from sh import ErrorReturnCode, nc, ping, ssh
from tenacity import retry, stop_after_delay, wait_fixed

app = typer.Typer()
console = Console()


def check_one_host( # noqa: PLR0917 too-many-positional-arguments
host: str,
run: int,
boot_timeout: int,
ping_count: int,
sleep_time: int,
dry: bool, # noqa: FBT001 boolean-type-hint-positional-argument
) -> None:
"""Check reboot stability for a single host."""
console.print(f"run: {run}, {host}: ping .. ", end="")

@retry(reraise=True, stop=stop_after_delay(boot_timeout), wait=wait_fixed(1))
def retry_ping() -> None:
ping(f"-c{ping_count}", host)

@retry(reraise=True, stop=stop_after_delay(boot_timeout), wait=wait_fixed(1))
def retry_nc() -> None:
nc("-z", "-w", "1", host, "22")

retry_ping()
console.print("ok, ssh .. ", end="")
retry_nc()
console.print("ok, uptime/reboot: ", end="")
console.print(ssh("-o", "ConnectTimeout=10", host, f"uptime && {'echo -n would reboot' if dry else 'sudo reboot'}"))
time.sleep(sleep_time)


@app.command()
def main( # noqa: PLR0917 too-many-positional-arguments
hosts: list[str] = typer.Argument(..., help="List of hosts to check reboot stability for"),
start: int = typer.Option(1, help="The start run number"),
runs: int = typer.Option(30, help="The total number of runs"),
boot_timeout: int = typer.Option(600, help="Timeout for boot"),
ping_count: int = typer.Option(30, help="Number of pings"),
sleep_time: float = typer.Option(120, help="Sleep time between reboots"),
dry: bool = typer.Option(False, "--dry", help="Only check ping+ssh and simulate reboots"), # noqa: FBT001 FBT003 boolean-type-hint-positional-argument
) -> None:
"""Check the reboot stability of a list of hosts specified in HOSTS."""
try:
for run in range(start, runs + 1):
for host in hosts:
check_one_host(host, run, boot_timeout, ping_count, sleep_time, dry)
except ErrorReturnCode as e:
console.print(f"[bold red]Failed: {e.stderr.decode('utf-8').strip()}. Exiting.[/bold red]")
sys.exit(1)


if __name__ == "__main__":
app()
Loading
Loading