اضافه شدن ورک فلو تست شبکه
flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Successful in 7m20s
flash-aminpa-V1.5.yml / Hardware flashing - Amin Pardaz Asia v1.5 (push) Successful in 7m20s
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
name: Software test - Amin Pardaz Asia v1.5
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- flash-aminpa-V1.5.yml
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ping-device:
|
||||
if: ${{ gitea.event_name == 'workflow_dispatch' || gitea.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: serial-runner
|
||||
|
||||
steps:
|
||||
- name: Checkout tested revision
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ gitea.event.workflow_run.head_sha || gitea.sha }}
|
||||
|
||||
- name: Wait up to three minutes for device ping
|
||||
env:
|
||||
TEST_INTERFACE: ${{ vars.TEST_INTERFACE || 'eno1' }}
|
||||
TEST_HOST_IP: ${{ vars.TEST_HOST_IP || '192.168.75.15' }}
|
||||
TEST_DEVICE_IP: ${{ vars.TEST_DEVICE_IP }}
|
||||
run: |
|
||||
/usr/bin/python3 scripts/software_test.py --interface "$TEST_INTERFACE" --host-ip "$TEST_HOST_IP" --device-ip "$TEST_DEVICE_IP"
|
||||
@@ -0,0 +1,39 @@
|
||||
# فلش و تست دستگاه Amin Pardaz Asia v1.5
|
||||
|
||||
workflow اصلی `flash-aminpa-V1.5.yml` با push به شاخهٔ `main` اجرا میشود.
|
||||
workflow تست نرمافزاری برای اجرا پس از موفقیت آن و همچنین اجرای دستی تعریف شده است.
|
||||
|
||||
## متغیرهای Gitea برای تست نرمافزاری
|
||||
|
||||
متغیرها را در تنظیمات مخزن Gitea، بخش **Actions → Variables** تعریف کنید.
|
||||
نام متغیرها باید دقیقاً مطابق جدول باشد.
|
||||
|
||||
| نام متغیر | کاربرد | پیشفرض | الزامی |
|
||||
| --- | --- | --- | --- |
|
||||
| `TEST_INTERFACE` | نام کارت شبکهٔ میزبان runner برای تست | `eno1` | خیر |
|
||||
| `TEST_HOST_IP` | آدرس IPv4 میزبان runner روی کارت شبکهٔ تست | `192.168.75.15` | خیر |
|
||||
| `TEST_DEVICE_IP` | آدرس IPv4 دستگاه فلششده که باید به ping پاسخ دهد | ندارد | بله |
|
||||
|
||||
`TEST_HOST_IP` آدرس میزبان است، نه دستگاه هدف. مقدار `TEST_DEVICE_IP` باید
|
||||
با آن متفاوت باشد و با IP دستگاه پس از بوت سیستمعامل مطابقت داشته باشد؛
|
||||
این مقدار لزوماً همان `ipaddr` موقت در U-Boot نیست.
|
||||
|
||||
IP میزبان باید از قبل روی کارت شبکهٔ انتخابشده تنظیم شده باشد و مسیر رسیدن
|
||||
به دستگاه از همان کارت شبکه عبور کند. اسکریپت تنظیمات شبکهٔ میزبان را تغییر نمیدهد.
|
||||
مقادیر IP بهصورت آدرس IPv4 وارد میشوند، نه رنج یا CIDR.
|
||||
|
||||
اسکریپت با پایتون سیستم و ابزارهای سیستم `ip` و `ping` اجرا میشود.
|
||||
پس از بررسی تنظیمات میزبان، حداکثر ۱۸۰ ثانیه منتظر پاسخ ping دستگاه میماند؛
|
||||
اولین پاسخ موفق نتیجهٔ `PASS` دارد. نبود پاسخ تا پایان مهلت یا خطای تنظیمات
|
||||
باعث `FAIL` و شکست workflow میشود.
|
||||
|
||||
نمونهٔ اجرای دستی روی runner با جایگزینکردن IP واقعی دستگاه:
|
||||
|
||||
```bash
|
||||
/usr/bin/python3 scripts/software_test.py \
|
||||
--interface eno1 \
|
||||
--host-ip 192.168.75.15 \
|
||||
--device-ip <DEVICE_IP>
|
||||
```
|
||||
|
||||
برای محل فایلهای firmware و نقشهٔ فلش، [راهنمای firmware](firmware/README.md) را ببینید.
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Wait up to three minutes for the flashed device to answer ICMP."""
|
||||
import argparse
|
||||
import ipaddress
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def test_device(interface, host_ip, device_ip):
|
||||
host = str(ipaddress.IPv4Address(host_ip))
|
||||
device = str(ipaddress.IPv4Address(device_ip))
|
||||
if host == device:
|
||||
raise ValueError('Device IP must differ from the test host IP')
|
||||
if not interface or interface.startswith('-'):
|
||||
raise ValueError('Invalid network interface')
|
||||
result = subprocess.run(
|
||||
['ip', '-j', '-4', 'addr', 'show', 'dev', interface],
|
||||
check=True, capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
addresses = json.loads(result.stdout)
|
||||
if not any(info.get('local') == host for entry in addresses for info in entry.get('addr_info', [])):
|
||||
raise ValueError(f'Host IP {host} is not configured on {interface}')
|
||||
result = subprocess.run(
|
||||
['ip', '-j', '-4', 'route', 'get', device, 'from', host],
|
||||
check=True, capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
routes = json.loads(result.stdout)
|
||||
if not routes or any(route.get('dev') != interface for route in routes):
|
||||
raise ValueError(f'Route to {device} does not use {interface}')
|
||||
print(f'Waiting up to 180 seconds: {host} ({interface}) -> {device}', flush=True)
|
||||
# -c 1 exits on the first reply; -w bounds the entire ping run.
|
||||
result = subprocess.run(
|
||||
['ping', '-4', '-n', '-B', '-I', host, '-c', '1', '-i', '1', '-W', '1', '-w', '180', device],
|
||||
timeout=180,
|
||||
)
|
||||
if result.returncode:
|
||||
raise RuntimeError(f'FAIL: {device} did not pass the ping test (exit {result.returncode})')
|
||||
print(f'PASS: device {device} answered ping', flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--interface', default='eno1')
|
||||
parser.add_argument('--host-ip', default='192.168.75.15')
|
||||
parser.add_argument('--device-ip', required=True)
|
||||
args = parser.parse_args()
|
||||
test_device(args.interface, args.host_ip, args.device_ip)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError) as exc:
|
||||
print(f'FAIL: {exc}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
مرجع در شماره جدید
مسدود کردن کاربر