Skip to content

[UT](feat) Guard buffer builder against legacy ToBufferOp #97

[UT](feat) Guard buffer builder against legacy ToBufferOp

[UT](feat) Guard buffer builder against legacy ToBufferOp #97

name: Ascend950 Pipeline Tests
run-name: >-
${{
(github.event_name == 'workflow_run' && github.event.workflow_run.display_title)
|| format('run {0}', github.run_id)
}}
# Orchestration after Wheels finishes:
# publish: download wheel artifact → upload to obs://.../triton-ascend/<N>/
# trigger: needs publish; calls blue-yellow /start, polls /query, then
# removes obs://.../triton-ascend/<N>/ once tests finish.
#
# Runs in base-repo context, so secrets are available even for fork PRs.
# NEVER check out the PR head SHA — fork code must not execute under the
# secret-bearing token.
on:
workflow_run:
workflows: ["Ascend950 Wheels Build"]
types: [completed]
concurrency:
# cancel-in-progress is false because /start is non-idempotent (a cancelled
# task can collide with a follow-up one).
group: pr-pipeline-${{ github.event.workflow_run.head_sha }}
cancel-in-progress: false
permissions: {}
jobs:
publish:
if: >-
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success'
runs-on: linux-aarch64-cpu-1
container:
image: swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/ascend/triton:github_action
permissions:
contents: read
actions: read
pull-requests: read
statuses: write
outputs:
pr_num: ${{ steps.pr.outputs.num }}
kind: ${{ steps.pr.outputs.kind }}
head_branch: ${{ steps.pr.outputs.head_branch }}
wheel_name: ${{ steps.wheel.outputs.name }}
steps:
- uses: actions/checkout@v4
- name: Mark cascade run on PR head SHA
# Writes a pending commit status on the upstream's head SHA so /retry
# can locate this cascade run for a given PR (GitHub strips PR linkage
# from workflow_run runs; statuses persist on the head commit and
# survive the rewrite). The finalize job overwrites this same context
# with the terminal state later.
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.event.workflow_run.head_sha }}
TARGET: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
gh api -X POST "repos/${{ github.repository }}/statuses/${SHA}" \
-f state=pending \
-f context='Ascend950 Pipeline Tests' \
-f description='Pipeline starting' \
-f target_url="$TARGET"
- name: Resolve publish identifier
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPSTREAM_EVENT: ${{ github.event.workflow_run.event }}
HEAD_OWNER: ${{ github.event.workflow_run.head_repository.owner.login }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
# PR runs key on PR number; branch pushes (main, release/**) key on
# the head SHA. `num` is the OBS path component for both; `kind`
# lets the trigger job decide whether to look up PR metadata or use
# the branch/SHA directly. `head_branch` is only meaningful in the
# branch-push case.
if [ "${UPSTREAM_EVENT}" = "pull_request" ]; then
NUM=$(gh api "/repos/${{ github.repository }}/pulls?state=open&head=${HEAD_OWNER}:${HEAD_BRANCH}" \
--jq 'if length > 0 then .[0].number else empty end')
if [ -z "${NUM}" ]; then
echo "No open PR found for ${HEAD_OWNER}:${HEAD_BRANCH}; nothing to publish."
exit 0
fi
echo "num=${NUM}" >> "$GITHUB_OUTPUT"
echo "kind=pr" >> "$GITHUB_OUTPUT"
else
echo "num=${HEAD_SHA}" >> "$GITHUB_OUTPUT"
echo "kind=branch" >> "$GITHUB_OUTPUT"
echo "head_branch=${HEAD_BRANCH}" >> "$GITHUB_OUTPUT"
fi
- name: Download wheel artifact
if: steps.pr.outputs.num != ''
uses: actions/download-artifact@v4
with:
pattern: cibw-wheels-*
path: wheelhouse
merge-multiple: true
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Resolve wheel filename
id: wheel
if: steps.pr.outputs.num != ''
# PR build matrix produces a single wheel; capture its filename and
# forward it to the trigger job via job outputs (no need to upload a
# separate manifest to OBS — yellow pipeline reads obs_url directly).
run: echo "name=$(basename wheelhouse/*.whl)" >> "$GITHUB_OUTPUT"
- name: Setup obsutil and upload to OBS
if: steps.pr.outputs.num != ''
shell: bash
env:
AK: ${{ secrets.OBS_AK }}
SK: ${{ secrets.OBS_SK }}
PR_NUM: ${{ steps.pr.outputs.num }}
run: |
set -eu
if [ -z "${AK}" ] || [ -z "${SK}" ]; then
echo "OBS credentials are empty."
exit 1
fi
curl -fL "https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_arm64.tar.gz" -o obsutil.tar.gz
gzip -t obsutil.tar.gz || { echo "obsutil.tar.gz is not a valid gzip"; exit 1; }
tar xzf obsutil.tar.gz
rm -f obsutil.tar.gz
mv obsutil_linux_* obsutil
obsutil/obsutil config -i="${AK}" -k="${SK}" -e=https://obs.cn-southwest-2.myhuaweicloud.com
obsutil/obsutil cp -u wheelhouse/*.whl "obs://triton-ascend-artifacts/triton-ascend/${PR_NUM}/"
trigger:
needs: publish
if: >-
needs.publish.result == 'success' &&
needs.publish.outputs.pr_num != ''
runs-on: linux-aarch64-cpu-1
container:
image: swr.cn-southwest-2.myhuaweicloud.com/modelfoundry/ascend/triton:github_action
permissions:
pull-requests: write
outputs:
# Surface BY pipeline outcome so the finalize job can write a meaningful
# commit status description without re-reading the by-status file.
by_status: ${{ steps.final.outputs.status }}
by_msg: ${{ steps.final.outputs.msg }}
steps:
# Default checkout pulls the base repo at the workflow's ref (safe).
# Do not pass ref: head_sha — that would execute fork code.
- uses: actions/checkout@v4
- name: Resolve PR context
id: ctx
shell: bash
env:
GH_TOKEN: ${{ github.token }}
PR_ID: ${{ needs.publish.outputs.pr_num }}
KIND: ${{ needs.publish.outputs.kind }}
HEAD_BRANCH: ${{ needs.publish.outputs.head_branch }}
run: |
set -euo pipefail
if [ "$KIND" = "pr" ]; then
# Title/body are mutable by the PR author at any time — same race
# the original artifact collector had.
P=$(gh pr view "$PR_ID" --repo "${{ github.repository }}" \
--json title,baseRefName,headRefOid)
PR_TITLE=$(echo "$P" | jq -r .title)
BRANCH_B=$(echo "$P" | jq -r .baseRefName)
HEAD_SHA=$(echo "$P" | jq -r .headRefOid)
else
# Branch push (main / release/**): PR_ID is the head SHA itself.
PR_TITLE="${HEAD_BRANCH} @ ${PR_ID:0:8}"
BRANCH_B="$HEAD_BRANCH"
HEAD_SHA="$PR_ID"
fi
{
echo "pr_id=$PR_ID"
echo "branch_b=$BRANCH_B"
echo "head_sha=$HEAD_SHA"
} >> "$GITHUB_OUTPUT"
# PR_TITLE may contain newlines; pass via env file as multi-line values.
{
echo "PR_TITLE<<__EOF__"
printf '%s\n' "$PR_TITLE"
echo "__EOF__"
} >> "$GITHUB_ENV"
- name: Trigger pipeline
id: run
shell: bash
env:
BASE_URL: ${{ secrets.BLUE_YELLOW_BASE_URL }}
APP_CODE: ${{ secrets.BLUE_YELLOW_APP_CODE }}
APP_KEY: ${{ secrets.BLUE_YELLOW_APP_KEY }}
APP_SECRET: ${{ secrets.BLUE_YELLOW_APP_SECRET }}
YELLOW_PIPELINE_ID: ${{ vars.YELLOW_PIPELINE_ID }}
YELLOW_GROUP_ID: ${{ vars.YELLOW_GROUP_ID }}
PR_ID: ${{ steps.ctx.outputs.pr_id }}
URL_B: ${{ github.server_url }}/${{ github.repository }}
BRANCH_B: ${{ steps.ctx.outputs.branch_b }}
WHEEL_NAME: ${{ needs.publish.outputs.wheel_name }}
# PR_TITLE inherited from $GITHUB_ENV (set in Resolve PR context).
run: |
# Blue/Yellow cross-region pipeline trigger.
# Flow: POST /start -> poll /query -> exit on terminal status (SUCCESS=0).
set -euo pipefail
STATUS_FILE="$RUNNER_TEMP/by-status"
H_CODE="X-Apig-AppCode: ${APP_CODE}"
H_KEY="AppKey: ${APP_KEY}"
H_SEC="AppSecret: ${APP_SECRET}"
H_JSON="Content-Type: application/json"
TS=$(date +%Y%m%d%H%M%S)
BRI="pr-${PR_ID}-${TS}"
BTI="task-${PR_ID}-${TS}"
write_status(){
# $3 (yellowPipelineUrl) only populated on terminal SUCCESS; blank otherwise.
printf 'status=%s\nmsg=%s\nblueRecordId=%s\nyellowPipelineUrl=%s\n' \
"$1" "$2" "$BRI" "${3:-}" > "$STATUS_FILE"
}
# JSON-escape arbitrary text (PR titles can contain quotes/newlines).
PR_TITLE_J=$(printf '%s' "${PR_TITLE}" | jq -Rs .)
if [ -z "${WHEEL_NAME:-}" ]; then
echo "WHEEL_NAME is empty"
write_status "START_FAILURE" "wheel filename not produced by publish"
exit 1
fi
WHEEL_NAME_ENC=$(printf '%s' "$WHEEL_NAME" | jq -sRr @uri)
OBS_URL="https://triton-ascend-artifacts.obs.cn-southwest-2.myhuaweicloud.com/triton-ascend/${PR_ID}/${WHEEL_NAME_ENC}"
START_BODY=$(cat <<EOF
{
"bluePipelineId": "null",
"blueRecordId": "${BRI}",
"blueRecordTaskId": "${BTI}",
"blueRecordTaskName": "PR-${PR_ID}-CI",
"yellowPipelineId": "${YELLOW_PIPELINE_ID}",
"yellowGroupId": "${YELLOW_GROUP_ID}",
"starter": "p_TritonCI",
"branch": "main",
"parameter": {
"pr": "${PR_ID}",
"title": ${PR_TITLE_J},
"description": "${PR_ID}",
"url_b": "${URL_B}",
"branch_b": "${BRANCH_B}",
"obs_url": "${OBS_URL}"
}
}
EOF
)
# /start retry: only on gateway/network failures (502/504,
# APIG.0203, curl=000); any other response is terminal. BRI/BTI are
# reused across attempts so the backend can dedupe a request the
# gateway lost mid-flight. --max-time 70 exceeds APIG's ~60s backend
# timeout so curl waits out the 502 instead of giving up first.
START_OK=false
LAST_R=""; LAST_BODY=""
DUP_RE='(exist|duplicate|already)'
for ((I=1; I<=5; I++)); do
R=$(curl -sS --max-time 70 \
-o /tmp/by_start.body -w '%{http_code}' \
-H "$H_CODE" -H "$H_KEY" -H "$H_SEC" -H "$H_JSON" \
-X POST "${BASE_URL}/start" -d "${START_BODY}" || echo "000")
BODY=$(cat /tmp/by_start.body 2>/dev/null || true)
LAST_R="$R"; LAST_BODY="$BODY"
echo "[start #${I}] HTTP=${R} response: ${BODY}"
if [ "$R" = "200" ] && echo "$BODY" | grep -q '"code":200'; then
START_OK=true
break
fi
if echo "$BODY" | grep -Eiq "\"(error_msg|msg|message)\":\"[^\"]*${DUP_RE}"; then
echo "[start #${I}] backend reports duplicate task — treating as already-accepted"
START_OK=true
break
fi
# Retry on gateway/backend timeout (APIG.0203, 502) or curl network
# failure (HTTP=000). All other errors are non-retryable.
if [ "$R" = "502" ] || [ "$R" = "504" ] || [ "$R" = "000" ] \
|| echo "$BODY" | grep -q 'APIG\.0203'; then
SLEEP=$(( I * 5 ))
echo "[start #${I}] retryable failure — sleeping ${SLEEP}s before retry"
sleep "$SLEEP"
continue
fi
echo "[start #${I}] non-retryable failure — aborting"
break
done
if [ "$START_OK" != "true" ]; then
write_status "START_FAILURE" "HTTP=${LAST_R} body=${LAST_BODY}"
exit 1
fi
QUERY_BODY=$(cat <<EOF
{"blueRecordId":"${BRI}","blueRecordTaskId":"${BTI}"}
EOF
)
ST=""; MSG=""; DONE=false
for ((N=1; N<=720; N++)); do
sleep 60
QHTTP=$(curl -sS --retry 3 --retry-all-errors --retry-delay 5 \
-o /tmp/by_query.body -w '%{http_code}' \
-H "$H_CODE" -H "$H_KEY" -H "$H_SEC" -H "$H_JSON" \
-X POST "${BASE_URL}/query" -d "${QUERY_BODY}" || true)
QR=$(cat /tmp/by_query.body 2>/dev/null || true)
ST=$(printf '%s' "$QR" | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
MSG=$(printf '%s' "$QR" | grep -o '"msg":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
YPURL=$(printf '%s' "$QR" | grep -o '"yellowPipelineUrl":"[^"]*"' | head -1 | cut -d'"' -f4 || true)
echo "[#${N}] HTTP=${QHTTP} status=${ST:-pending} msg=${MSG}"
echo " body: ${QR}"
write_status "${ST:-PENDING}" "${MSG}" "${YPURL}"
case "${ST}" in
SUCCESS|FAILURE|MQS_SEND_FAILURE|UNAUTHORIZED|START_FAILURE|TIMEOUT|ABORT)
DONE=true; break;;
esac
done
if [ "$DONE" != "true" ]; then
write_status "TIMEOUT" "polling exceeded 720 attempts without a terminal status"
exit 1
fi
echo "terminal status: ${ST} - ${MSG}"
[ "$ST" = "SUCCESS" ] || exit 1
- name: Read final status
if: always() && steps.ctx.outputs.pr_id != ''
id: final
run: |
F="$RUNNER_TEMP/by-status"
if [ -f "$F" ]; then
cat "$F" >> "$GITHUB_OUTPUT"
else
{
echo "status=UNKNOWN"
echo "msg=no status file produced"
echo "blueRecordId="
echo "yellowPipelineUrl="
} >> "$GITHUB_OUTPUT"
fi
- name: Remove PR wheels from OBS
if: always() && steps.ctx.outputs.pr_id != ''
shell: bash
env:
AK: ${{ secrets.OBS_AK }}
SK: ${{ secrets.OBS_SK }}
PR_NUM: ${{ steps.ctx.outputs.pr_id }}
run: |
# Guard against a missing/malformed PR_NUM: an empty value would
# collapse the URL to .../triton-ascend/ and `rm -rf` would wipe
# every namespace. Accept a positive integer (PR number) or a full
# git SHA (main-branch run).
if [[ ! "${PR_NUM:-}" =~ ^([1-9][0-9]*|[0-9a-f]{40})$ ]]; then
echo "Refusing to delete: PR_NUM='${PR_NUM:-}' is not a PR number or SHA" >&2
exit 1
fi
curl -fL "https://obs-community.obs.cn-north-1.myhuaweicloud.com/obsutil/current/obsutil_linux_arm64.tar.gz" -o obsutil.tar.gz
gzip -t obsutil.tar.gz || { echo "obsutil.tar.gz is not a valid gzip"; exit 1; }
tar xzf obsutil.tar.gz
rm -f obsutil.tar.gz
mv obsutil_linux_* obsutil
obsutil/obsutil config -i="${AK}" -k="${SK}" -e=https://obs.cn-southwest-2.myhuaweicloud.com
obsutil/obsutil rm -r -f "obs://triton-ascend-artifacts/triton-ascend/${PR_NUM}/" || true
resolve-failed:
# When the upstream build did NOT succeed, publish/trigger are skipped, so
# nobody resolves an 'Ascend950 Pipeline Tests' commit status that
# rebuild-tasks may have pre-set to pending on restart — it would sit stuck
# on a yellow pending forever. Flip it to failure, but only when it is
# currently pending, so the normal (no pre-set) path is left untouched.
if: >-
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion != 'success'
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- name: Fail a stuck pending status
env:
GH_TOKEN: ${{ github.token }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
CONCLUSION: ${{ github.event.workflow_run.conclusion }}
CONTEXT: Ascend950 Pipeline Tests
REPO: ${{ github.repository }}
TARGET: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}
run: |
set -euo pipefail
# workflow_run.head_sha is the build's head commit — the same PR head
# SHA rebuild-tasks wrote the pending status against.
STATE=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/statuses?per_page=100" \
--jq "[.[] | select(.context==\"${CONTEXT}\")] | sort_by(.updated_at) | last | .state // \"\"")
if [ "$STATE" != "pending" ]; then
echo "No pending '${CONTEXT}' status on ${HEAD_SHA} (state='${STATE:-none}'); nothing to resolve."
exit 0
fi
gh api -X POST "repos/${REPO}/statuses/${HEAD_SHA}" \
-f state=failure \
-f context="${CONTEXT}" \
-f description="Upstream Wheels Build ${CONCLUSION}; pipeline not run" \
-f target_url="${TARGET}"
# Single source of truth for the final commit status. Runs always() so that
# publish-side failures and cancellations don't leave the marker pending
# forever. Only runs on the workflow_run cascade path — workflow_dispatch
# is a manual operator action and doesn't need a PR-visible status.
finalize:
needs: [publish, trigger]
if: >-
always()
&& github.event_name == 'workflow_run'
&& github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
statuses: write
steps:
- name: Write final commit status
env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.event.workflow_run.head_sha }}
TARGET: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
PUBLISH_RESULT: ${{ needs.publish.result }}
TRIGGER_RESULT: ${{ needs.trigger.result }}
BY_STATUS: ${{ needs.trigger.outputs.by_status }}
BY_MSG: ${{ needs.trigger.outputs.by_msg }}
run: |
set -euo pipefail
# Map (publish, trigger) job results → commit status state + desc.
# GitHub status API truncates description at 140 chars on its own.
if [ "$PUBLISH_RESULT" != "success" ]; then
STATE=failure
DESC="Publish ${PUBLISH_RESULT}"
elif [ "$TRIGGER_RESULT" != "success" ]; then
STATE=failure
DESC="${BY_STATUS:-Trigger ${TRIGGER_RESULT}}${BY_MSG:+: ${BY_MSG}}"
else
STATE=success
DESC="${BY_STATUS:-OK}"
fi
gh api -X POST "repos/${{ github.repository }}/statuses/${SHA}" \
-f state="$STATE" \
-f context='Ascend950 Pipeline Tests' \
-f description="$DESC" \
-f target_url="$TARGET"