diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..ee95b0b4 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,260 @@ +pipeline { + agent any + + environment { + DOCKER_REGISTRY = '127.0.0.1:5001' + REPO_URL = 'https://github.com/ModelEngine-Group/unified-cache-management.git' + REPORT_DIR = 'tests/reports' + EMAIL_RECIPIENTS = 'duxiaolong22@mails.ucas.ac.cn' + DEVICE = '/dev/davinci7' + MODEL_PATH = '/home/models/Qwen3-0.6B' + PALTFORM = 'vllm-ascend' + } + + parameters { + string(name: 'MANUAL_BRANCH', defaultValue: 'main', description: 'Branch to build when triggered manually') + choice(name: 'TRIGGER_TYPE', choices: ['PR_VERIFY', 'DAILY_BUILD', 'FULL_TEST'], description: 'Manual build type') + booleanParam(name: 'SEND_EMAIL', defaultValue: true, description: 'Send e-mail notification?') + } + + triggers { + // Daily build at 00:00 + cron('H 0 * * *') + } + + stages { + /* ---------------------------------------------------------- */ + /* 1. PR Verification */ + /* ---------------------------------------------------------- */ + stage('PR Verification') { + when { + // Run ONLY when the branch name starts with “PR-” + // OR when a manual build explicitly chooses PR_VERIFY. + // exclude timer-triggered builds + anyOf { + expression { env.CHANGE_ID != null } // multibranch PR + allOf { + expression { params.TRIGGER_TYPE == 'PR_VERIFY' } + not { triggeredBy 'TimerTrigger' } + } + } + } + steps { + script { + echo '===== Starting PR Verification =====' + checkout scm + runSimpleTest() + } + } + post { + always { + generateSimpleReports() + cleanupResources() + } + success { + sendNotification('PR Verification', 'SUCCESS') + } + failure { + sendNotification('PR Verification', 'FAILURE') + } + } + } + + /* ---------------------------------------------------------- */ + /* 2. Daily Build & Smoke Test */ + /* ---------------------------------------------------------- */ + stage('Daily Build & Smoke Test') { + when { + anyOf { + triggeredBy 'TimerTrigger' + expression { params.TRIGGER_TYPE == 'DAILY_BUILD' } + } + } + steps { + script { + echo '===== Starting Daily Build & Smoke Test =====' + checkout([$class: 'GitSCM', + branches: [[name: '*/develop']], + userRemoteConfigs: [[url: "${REPO_URL}"]]]) + buildSimpleImage('hello-world-service') + runSimpleTest() + } + } + post { + always { + generateSimpleReports() + cleanupResources() + } + success { + sendNotification('Daily Build & Smoke Test', 'SUCCESS') + } + failure { + sendNotification('Daily Build & Smoke Test', 'FAILURE') + } + } + } + + /* ---------------------------------------------------------- */ + /* 3. Full Test */ + /* ---------------------------------------------------------- */ + stage('Full Test') { + when { expression { params.TRIGGER_TYPE == 'FULL_TEST' } } + steps { + script { + echo '===== Starting Full Test =====' + checkout([$class: 'GitSCM', + branches: [[name: '*/develop']], + userRemoteConfigs: [[url: "${REPO_URL}"]]]) + buildSimpleImage('hello-world-service') + runSimpleTest() + } + } + post { + always { + generateSimpleReports() + cleanupResources() + } + success { + sendNotification('Full Test', 'SUCCESS') + } + failure { + sendNotification('Full Test', 'FAILURE') + } + } + } + + /* ---------------------------------------------------------- */ + /* 4. Collect Reports */ + /* ---------------------------------------------------------- */ + stage('Collect Reports') { + steps { + script { + echo 'Collecting all logs and reports...' + sh "mkdir -p '${REPORT_DIR}'" + + archiveArtifacts artifacts: 'tests/reports/**', allowEmptyArchive: true + + publishHTML([allowMissing: false, + alwaysLinkToLastBuild: true, + keepAll: true, + reportDir: 'tests/reports', + reportFiles: 'test_report.html', + reportName: 'Test Report', + reportTitles: 'Pytest Test Results']) + } + } + } + } + + post { + always { + echo "Pipeline Finished: ${currentBuild.currentResult}" + cleanWs() + } + } +} + +// ====================================================================== +// Helper functions +// ====================================================================== + +/* Build a docker image and tag it */ +def buildSimpleImage(String imageName) { + def fullImageName = "${DOCKER_REGISTRY}/${imageName}" + def dateTag = "daily-${new Date().format('yyyyMMdd')}" + def gitCommit = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() + + echo "Building Docker image: ${fullImageName}" + + sh """ + docker build -t ${fullImageName}:${dateTag} . + docker tag ${fullImageName}:${dateTag} ${fullImageName}:${gitCommit} + docker tag ${fullImageName}:${dateTag} ${fullImageName}:latest + """ +} + +/* Run the test suite inside docker-compose */ +def runSimpleTest() { + echo '===== Running Simple Test Suite =====' + try { + sh 'ls -la' + sh 'pwd' + + sh ''' + cd /test + pytest --stage=0 > build.log + ''' + + sh ''' + echo "=== Check Report results ===" + ls -la tests/reports/ || echo "tests/reports missing" + echo "=== Check complete ===" + ''' + } catch (Exception e) { + echo "Test execution failed: ${e.message}" + sh 'cat build.log || true' + currentBuild.result = 'FAILURE' + throw e + } +} + +/* Collect test artefacts into REPORT_DIR */ +def generateSimpleReports() { + echo 'Generating test reports...' + sh "mkdir -p '${REPORT_DIR}'" + + sh ''' + echo "=== Collect test reports ===" + ls -la tests/reports/ 2>/dev/null || echo "No reports directory" + cp -r tests/reports/* "${REPORT_DIR}/" 2>/dev/null || echo "No reports to copy" + + ''' +} + +/* Send e-mail notification */ +def sendNotification(String testType, String status) { + if (!params.SEND_EMAIL) return + + echo "Sending e-mail notification for ${testType} - ${status}" + + def subject = "[Jenkins] ${testType} - ${status} - ${env.JOB_NAME} #${env.BUILD_NUMBER}" + def body = """ + +

Jenkins Build Notification

+ + + + + + + + + + +
Project:${env.JOB_NAME}
Build Number:#${env.BUILD_NUMBER}
Status:${status}
Test Type:${testType}
Build Time:${new Date().format("yyyy-MM-dd HH:mm:ss")}
Commit ID:${sh(returnStdout: true, script: 'git rev-parse HEAD').trim()}
Commit Message:${sh(returnStdout: true, script: 'git log --oneline -1').trim()}
Build URL:${env.BUILD_URL}
+ +

Test Summary

+

Please refer to the attached artifacts or click on the links above for detailed results.

+ +

Best regards,
Jenkins CI/CD System

+ + """ + + try { + emailext subject: subject, + body: body, + to: "${EMAIL_RECIPIENTS}", + mimeType: 'text/html' + } catch (Exception e) { + echo "Failed to send e-mail: ${e.message}" + } +} + +/* Clean up containers and dangling images */ +def cleanupResources() { + echo 'Cleaning up resources...' + sh ''' + docker-compose -f docker-compose.test.yml down -v || true + docker system prune -f || true + ''' +} \ No newline at end of file diff --git a/ci/vllm-ascend/Dockerfile b/ci/vllm-ascend/Dockerfile new file mode 100644 index 00000000..f0baaa12 --- /dev/null +++ b/ci/vllm-ascend/Dockerfile @@ -0,0 +1,27 @@ +# Set to other image if needed +FROM quay.io/ascend/vllm-ascend:v0.9.2rc1-openeuler + +ARG PIP_INDEX_URL="https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple " + +WORKDIR /vllm-workspace + +# Install unified-cache-management +COPY . /vllm-workspace/unified-cache-management + +RUN pip config set global.index-url ${PIP_INDEX_URL} + +RUN export PLATFORM="ascend" && \ + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/Ascend/ascend-toolkit/latest/`uname -i`-linux/devlib && \ + pip install -v -e /vllm-workspace/unified-cache-management --no-build-isolation + +# Apply patch for vLLM +RUN cd /vllm-workspace/vllm \ + && git apply /vllm-workspace/unified-cache-management/ucm/integration/vllm/patch/0.9.2/vllm-adapt-pc.patch \ + && git apply /vllm-workspace/unified-cache-management/ucm/integration/vllm/patch/0.9.2/vllm-adapt-aggre.patch \ + && git apply /vllm-workspace/unified-cache-management/ucm/integration/vllm/patch/0.9.2/vllm-adapt-sparse.patch + +# Apply patch for vLLM-Ascend +RUN cd /vllm-workspace/vllm-ascend \ + && git apply /vllm-workspace/unified-cache-management/ucm/integration/vllm/patch/0.9.2/vllm-ascend-adapt.patch + +CMD ["vllm", "serve", "/home/models/Qwen3-0.6B", "--trust-remote-code"] \ No newline at end of file diff --git a/ci/vllm-ascend/start_docker.sh b/ci/vllm-ascend/start_docker.sh new file mode 100644 index 00000000..395298dd --- /dev/null +++ b/ci/vllm-ascend/start_docker.sh @@ -0,0 +1,18 @@ +export DEVICE=/dev/davinci7 +export MODEL_PATH=/home/models/Qwen3-0.6B + +docker run -d \ + --name ucm_ascend_demo \ + --network=host \ + --device=$DEVICE \ + --device=/dev/davinci_manager \ + --device=/dev/devmm_svm \ + --device=/dev/hisi_hdc \ + -v /usr/local/dcmi:/usr/local/dcmi \ + -v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \ + -v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \ + -v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \ + -v /etc/ascend_install.info:/etc/ascend_install.info \ + -v $MODEL_PATH:$MODEL_PATH \ + ucm_ascend:1110 \ + vllm serve $MODEL_PATH --trust-remote-code \ No newline at end of file