Skip to content

Commit 8050d01

Browse files
committed
wip, generate config with capture
1 parent ca51101 commit 8050d01

File tree

5 files changed

+182
-12
lines changed

5 files changed

+182
-12
lines changed

.github/actions/build/action.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
name: Build
2+
description: Build MTL and ecosystem plugins
23
runs:
34
using: composite
45
steps:
@@ -16,6 +17,32 @@ runs:
1617
path: 'ffmpeg'
1718
- name: 'configuration: Install the build dependency'
1819
run: |
20+
set -euo pipefail
21+
22+
# Self-hosted runners can keep dpkg state between jobs. If `ntp` is half-configured,
23+
# any subsequent apt operation can fail with: "dpkg returned an error code (1)".
24+
# Also, avoid starting services during package install (and especially avoid time-sync
25+
# daemons fighting over UDP/123).
26+
sudo tee /usr/sbin/policy-rc.d >/dev/null <<'EOF'
27+
#!/bin/sh
28+
exit 101
29+
EOF
30+
sudo chmod +x /usr/sbin/policy-rc.d
31+
32+
sudo systemctl stop ntp 2>/dev/null || true
33+
sudo systemctl disable ntp 2>/dev/null || true
34+
sudo systemctl stop systemd-timesyncd 2>/dev/null || true
35+
sudo systemctl stop chrony 2>/dev/null || true
36+
sudo systemctl stop chronyd 2>/dev/null || true
37+
38+
if dpkg-query -W -f='${Status}' ntp 2>/dev/null | grep -q '^install'; then
39+
sudo dpkg --remove --force-remove-reinstreq ntp 2>/dev/null || true
40+
sudo apt-get remove --purge -y ntp || true
41+
fi
42+
43+
sudo dpkg --configure -a || true
44+
sudo apt-get -f install -y || true
45+
1946
sudo apt update
2047
sudo apt-get remove -y pipenv || true
2148
sudo apt-get install -y \
@@ -43,6 +70,8 @@ runs:
4370
libgstreamer-plugins-base1.0-dev \
4471
patch \
4572
unzip
73+
74+
sudo rm -f /usr/sbin/policy-rc.d
4675
shell: bash
4776
- name: 'installation: Build mtl'
4877
run: |

.github/workflows/smoke-tests.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ jobs:
6363
run: |
6464
runner_name=${{ runner.name }}
6565
echo "SESSION_ID=${runner_name##*-}" >> "$GITHUB_ENV"
66+
67+
- name: Export GitHub job id for PCAP naming
68+
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
69+
shell: bash
70+
run: |
71+
echo "MTL_GITHUB_JOB=${{ github.workflow }}:${{ github.job }}:${{ matrix.nic }}" >> "$GITHUB_ENV"
72+
73+
- name: Install requirements
74+
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
75+
shell: bash
76+
run: |
77+
set -euo pipefail
78+
sudo apt-get update -y
79+
sudo apt-get install -y --no-install-recommends linuxptp ethtool netsniff-ng jq
6680
- name: Set PCI device env variable
6781
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
6882
run: |
@@ -82,19 +96,71 @@ jobs:
8296
--build ${{ secrets.RUNNER_BUILD }} \
8397
--mtl_path ${{ secrets.RUNNER_MTL_PATH }} \
8498
--pci_device ${{ env.PCI_DEVICE }} \
99+
--ebu_ip ${{ secrets.RUNNER_EBU_LIST_IP }} \
100+
--ebu_user ${{ secrets.RUNNER_EBU_LIST_USER }} \
101+
--ebu_password ${{ secrets.RUNNER_EBU_LIST_PASSWORD }} \
85102
--ip_address 127.0.0.1 \
86103
--username ${{ secrets.RUNNER_USERNAME }} \
87104
--key_path ${{ secrets.RUNNER_KEY_PATH }}
105+
106+
- name: Detect capture PTP device (2nd PF)
107+
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
108+
shell: bash
109+
run: |
110+
set -euo pipefail
111+
112+
echo "PCI_DEVICE=${PCI_DEVICE}"
113+
114+
mapfile -t bdfs < <(lspci -Dn | awk -v id="$PCI_DEVICE" '$3 == id { print $1 }' | sort)
115+
if [ "${#bdfs[@]}" -lt 1 ]; then
116+
echo "ERROR: no PCI functions found matching ${PCI_DEVICE}"
117+
exit 1
118+
fi
119+
120+
# Prefer the 2nd PF when present.
121+
capture_bdf="${bdfs[1]:-${bdfs[0]}}"
122+
if [ ! -d "/sys/bus/pci/devices/${capture_bdf}/net" ]; then
123+
echo "ERROR: no netdevs under /sys/bus/pci/devices/${capture_bdf}/net"
124+
ls -la "/sys/bus/pci/devices/${capture_bdf}" || true
125+
exit 1
126+
fi
127+
128+
capture_iface="$(ls "/sys/bus/pci/devices/${capture_bdf}/net" | head -n 1)"
129+
if [ -z "${capture_iface}" ]; then
130+
echo "ERROR: failed to detect interface name for ${capture_bdf}"
131+
exit 1
132+
fi
133+
134+
ptp_idx="$(sudo ethtool -T "${capture_iface}" 2>/dev/null | awk -F': ' '/PTP Hardware Clock:/ {print $2; exit}')"
135+
if ! [[ "${ptp_idx}" =~ ^[0-9]+$ ]]; then
136+
echo "ERROR: failed to parse PTP Hardware Clock index for ${capture_iface}"
137+
sudo ethtool -T "${capture_iface}" || true
138+
exit 1
139+
fi
140+
141+
echo "CAPTURE_BDF=${capture_bdf}" >> "$GITHUB_ENV"
142+
echo "CAPTURE_IFACE=${capture_iface}" >> "$GITHUB_ENV"
143+
echo "CAPTURE_PTP=/dev/ptp${ptp_idx}" >> "$GITHUB_ENV"
144+
echo "Capture interface: ${capture_iface} (${capture_bdf}), PTP: /dev/ptp${ptp_idx}"
88145
- name: 'preparation: Kill MtlManager and pytest routines'
89146
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
90147
run: |
91148
sudo killall -SIGINT pipenv || true
92149
sudo killall -SIGINT pytest || true
93150
sudo killall -SIGINT MtlManager || true
151+
sudo killall -SIGINT phc2sys || true
94152
- name: 'preparation: Start MtlManager at background'
95153
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
96154
run: |
97155
sudo MtlManager &
156+
157+
- name: 'preparation: Start phc2sys for capture interface'
158+
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
159+
shell: bash
160+
run: |
161+
set -euo pipefail
162+
echo "Starting phc2sys: ${CAPTURE_PTP} -> CLOCK_REALTIME (iface=${CAPTURE_IFACE})"
163+
sudo phc2sys -s "${CAPTURE_PTP}" -c CLOCK_REALTIME -O 0 -m &
98164
- name: 'execution: Run validation-bare-metal tests in virtual environment'
99165
if: ${{ needs.smoke-check-for-changes.outputs.changed == 'true' }}
100166
run: |

tests/validation/configs/gen_config.py

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,35 @@
33
import yaml
44

55

6-
def gen_test_config(session_id: int, build: str, mtl_path: str) -> str:
6+
def gen_test_config(
7+
session_id: int,
8+
build: str,
9+
mtl_path: str,
10+
pci_device: str,
11+
ebu_ip: str,
12+
ebu_user: str,
13+
ebu_password: str,
14+
) -> str:
715
test_config = {
816
"session_id": session_id,
917
"build": build,
1018
"mtl_path": mtl_path,
1119
"media_path": "/mnt/media",
1220
"ramdisk": {
1321
"media": {"mountpoint": "/mnt/ramdisk/media", "size_gib": 32},
14-
"pcap": {"mountpoint": "/mnt/ramdisk/pcap", "size_gib": 768},
22+
"tmpfs_size_gib": 12,
23+
"pcap_dir": "/mnt/ramdisk/pcap",
24+
},
25+
"compliance": True,
26+
"capture_cfg": {
27+
"enable": True,
28+
"pcap_dir": "/mnt/ramdisk/pcap",
29+
"sniff_pci_device": pci_device,
1530
},
16-
"compliance": False,
17-
"capture_cfg": {"enable": False, "pcap_dir": "/mnt/ramdisk/pcap"},
1831
"ebu_server": {
19-
"ebu_ip": "ebu_ip",
20-
"user": "user",
21-
"password": "password",
32+
"ebu_ip": ebu_ip,
33+
"user": ebu_user,
34+
"password": ebu_password,
2235
"proxy": False,
2336
},
2437
}
@@ -39,6 +52,10 @@ def gen_topology_config(
3952
{
4053
"pci_device": pci_device,
4154
"interface_index": 0,
55+
},
56+
{
57+
"pci_device": pci_device,
58+
"interface_index": 1,
4259
}
4360
],
4461
"connections": [
@@ -91,6 +108,24 @@ def main() -> None:
91108
required=True,
92109
help="specify PCI ID of the NIC",
93110
)
111+
parser.add_argument(
112+
"--ebu_ip",
113+
type=str,
114+
required=True,
115+
help="EBU LIST server IP/hostname (RUNNER_EBU_LIST_IP)",
116+
)
117+
parser.add_argument(
118+
"--ebu_user",
119+
type=str,
120+
required=True,
121+
help="EBU LIST username (RUNNER_EBU_LIST_USER)",
122+
)
123+
parser.add_argument(
124+
"--ebu_password",
125+
type=str,
126+
required=True,
127+
help="EBU LIST password (RUNNER_EBU_LIST_PASSWORD)",
128+
)
94129
parser.add_argument(
95130
"--ip_address",
96131
type=str,
@@ -121,7 +156,13 @@ def main() -> None:
121156
with open("test_config.yaml", "w") as file:
122157
file.write(
123158
gen_test_config(
124-
session_id=args.session_id, build=args.build, mtl_path=args.mtl_path
159+
session_id=args.session_id,
160+
build=args.build,
161+
mtl_path=args.mtl_path,
162+
pci_device=args.pci_device,
163+
ebu_ip=args.ebu_ip,
164+
ebu_user=args.ebu_user,
165+
ebu_password=args.ebu_password,
125166
)
126167
)
127168
with open("topology_config.yaml", "w") as file:

tests/validation/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def pcap_capture(request, media_file, test_config, hosts, mtl_path):
374374
f" --ip {ebu_ip}"
375375
f" --user {ebu_login}"
376376
f" --password {ebu_passwd}"
377-
f" --pcap {capturer.pcap_file}{proxy_cmd}",
377+
f" --pcap '{capturer.pcap_file}'{proxy_cmd}",
378378
cwd=f"{str(mtl_path)}",
379379
)
380380
if compliance_upl.return_code != 0:

tests/validation/create_pcap_file/netsniff.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# SPDX-License-Identifier: BSD-3-Clause
22
# Copyright 2025 Intel Corporation
3+
import datetime
34
import logging
45
import os
6+
import re
57
from time import sleep
68

79
from mfd_connect.exceptions import (
@@ -59,7 +61,7 @@ def __init__(
5961
self.host = host
6062
self.test_name = test_name
6163
self.pcap_dir = pcap_dir
62-
self.pcap_file = f"'{os.path.join(pcap_dir, test_name)}.pcap'"
64+
self.pcap_file = None
6365
if interface is not None:
6466
self.interface = interface
6567
else:
@@ -70,20 +72,52 @@ def __init__(
7072
self.packets_capture = packets_capture
7173
self.capture_time = capture_time
7274

75+
@staticmethod
76+
def _sanitize_filename_component(value: str, *, max_len: int = 64) -> str:
77+
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", (value or "").strip())
78+
cleaned = cleaned.strip("-._")
79+
if not cleaned:
80+
cleaned = "unknown"
81+
return cleaned[:max_len]
82+
83+
def _get_remote_hostname(self) -> str:
84+
try:
85+
res = self.host.connection.execute_command("uname -n")
86+
hostname = (res.stdout or "").strip().splitlines()[0]
87+
if hostname:
88+
return hostname
89+
except Exception:
90+
pass
91+
return str(getattr(self.host, "name", "unknown"))
92+
93+
def _build_pcap_path(self) -> str:
94+
hostname = self._sanitize_filename_component(self._get_remote_hostname())
95+
timestamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ")
96+
job = os.environ.get("MTL_GITHUB_JOB") or os.environ.get("GITHUB_JOB") or ""
97+
job = self._sanitize_filename_component(job, max_len=96) if job else ""
98+
99+
test = self._sanitize_filename_component(self.test_name, max_len=128)
100+
parts = [test, hostname, timestamp]
101+
if job:
102+
parts.append(job)
103+
filename = "__".join(parts) + ".pcap"
104+
return os.path.join(self.pcap_dir, filename)
105+
73106
def start(self):
74107
"""
75108
Starts the netsniff-ng
76109
"""
77110
if not self.netsniff_process or not self.netsniff_process.running:
78111
connection = self.host.connection
79112
try:
113+
self.pcap_file = self._build_pcap_path()
80114
cmd = [
81115
"netsniff-ng",
82116
"--silent" if self.silent else "",
83117
"--in",
84118
str(self.interface),
85119
"--out",
86-
self.pcap_file,
120+
f"'{self.pcap_file}'",
87121
(
88122
f"--num {self.packets_capture}"
89123
if self.packets_capture is not None
@@ -96,7 +130,7 @@ def start(self):
96130
" ".join(cmd), stderr_to_stdout=True
97131
)
98132
logger.info(
99-
f"PCAP file will be saved at: {os.path.abspath(self.pcap_file)}"
133+
f"PCAP file will be saved at: {self.pcap_file}"
100134
)
101135

102136
if not self.netsniff_process.running:

0 commit comments

Comments
 (0)