[UT](feat) Guard buffer builder against legacy ToBufferOp #97
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |