From b2328b8ac7f8c2f45233411ae92062a1bd41caae Mon Sep 17 00:00:00 2001 From: dolpher Date: Fri, 6 Sep 2024 16:06:21 +0800 Subject: [PATCH] v0.9 charts release (#352) * v0.9 charts release Signed-off-by: Dolpher Du --- .github/code_spell_ignore.txt | 0 .github/pull_request_template.md | 23 - .github/workflows/_gmc-e2e.yaml | 69 -- .github/workflows/_gmc-image-build.yaml | 68 -- .github/workflows/_infra-workflow.yaml | 107 --- .github/workflows/docker/code-scan.dockerfile | 20 - .github/workflows/docker/ut.dockerfile | 21 - .github/workflows/manual-cd-workflow.yaml | 50 -- .github/workflows/manual-image-build.yaml | 52 -- .github/workflows/manual-trellix.yml | 30 - .github/workflows/pr-chart-e2e.yaml | 192 ----- .github/workflows/pr-chart-validate.yaml | 56 -- .github/workflows/pr-code-scan.yml | 61 -- .github/workflows/pr-gmc-helm.yaml | 106 --- .github/workflows/pr-go-e2e.yaml | 40 - .github/workflows/pr-go-unittests.yaml | 61 -- .github/workflows/push-image-build.yaml | 60 -- .github/workflows/scripts/change_color | 80 -- .github/workflows/scripts/codeScan/bandit.sh | 22 - .../workflows/scripts/codeScan/hadolint.sh | 17 - .github/workflows/scripts/codeScan/trellix.sh | 49 -- .github/workflows/scripts/e2e/chart_test.sh | 91 --- .../workflows/scripts/e2e/gmc_gaudi_test.sh | 472 ------------ .github/workflows/scripts/e2e/gmc_install.sh | 97 --- .../workflows/scripts/e2e/gmc_xeon_test.sh | 697 ------------------ .../scripts/e2e/manifest_gaudi_test.sh | 278 ------- .../scripts/e2e/manifest_xeon_test.sh | 278 ------- .github/workflows/scripts/e2e/utils.sh | 137 ---- .github/workflows/scripts/go-coverage.sh | 32 - .github/workflows/scripts/test_ut.sh | 29 - .pre-commit-config.yaml | 108 +++ README.md | 3 +- asr-0.9.0.tgz | Bin 0 -> 5132 bytes chatqna-0.9.0.tgz | Bin 0 -> 27624 bytes chatqna-ui-0.9.0.tgz | Bin 0 -> 2957 bytes codegen-0.9.0.tgz | Bin 0 -> 8428 bytes codetrans-0.9.0.tgz | Bin 0 -> 8135 bytes data-prep-0.9.0.tgz | Bin 0 -> 7334 bytes docsum-0.9.0.tgz | Bin 0 -> 8028 bytes embedding-usvc-0.9.0.tgz | Bin 0 -> 5773 bytes index.old | 596 +++++++++++++++ index.yaml | 337 ++++++++- llm-uservice-0.9.0.tgz | Bin 0 -> 6415 bytes redis-vector-db-0.9.0.tgz | Bin 0 -> 3083 bytes reranking-usvc-0.9.0.tgz | Bin 0 -> 5768 bytes retriever-usvc-0.9.0.tgz | Bin 0 -> 7437 bytes speecht5-0.9.0.tgz | Bin 0 -> 3799 bytes tei-0.9.0.tgz | Bin 0 -> 4334 bytes teirerank-0.9.0.tgz | Bin 0 -> 4265 bytes tgi-0.9.0.tgz | Bin 0 -> 4706 bytes tts-0.9.0.tgz | Bin 0 -> 5081 bytes update_chart_repo.sh | 10 + web-retriever-0.9.0.tgz | Bin 0 -> 5877 bytes whisper-0.9.0.tgz | Bin 0 -> 3850 bytes 54 files changed, 1023 insertions(+), 3326 deletions(-) delete mode 100644 .github/code_spell_ignore.txt delete mode 100644 .github/pull_request_template.md delete mode 100644 .github/workflows/_gmc-e2e.yaml delete mode 100644 .github/workflows/_gmc-image-build.yaml delete mode 100644 .github/workflows/_infra-workflow.yaml delete mode 100644 .github/workflows/docker/code-scan.dockerfile delete mode 100644 .github/workflows/docker/ut.dockerfile delete mode 100644 .github/workflows/manual-cd-workflow.yaml delete mode 100644 .github/workflows/manual-image-build.yaml delete mode 100644 .github/workflows/manual-trellix.yml delete mode 100644 .github/workflows/pr-chart-e2e.yaml delete mode 100644 .github/workflows/pr-chart-validate.yaml delete mode 100644 .github/workflows/pr-code-scan.yml delete mode 100644 .github/workflows/pr-gmc-helm.yaml delete mode 100644 .github/workflows/pr-go-e2e.yaml delete mode 100644 .github/workflows/pr-go-unittests.yaml delete mode 100644 .github/workflows/push-image-build.yaml delete mode 100644 .github/workflows/scripts/change_color delete mode 100644 .github/workflows/scripts/codeScan/bandit.sh delete mode 100644 .github/workflows/scripts/codeScan/hadolint.sh delete mode 100644 .github/workflows/scripts/codeScan/trellix.sh delete mode 100755 .github/workflows/scripts/e2e/chart_test.sh delete mode 100755 .github/workflows/scripts/e2e/gmc_gaudi_test.sh delete mode 100755 .github/workflows/scripts/e2e/gmc_install.sh delete mode 100755 .github/workflows/scripts/e2e/gmc_xeon_test.sh delete mode 100755 .github/workflows/scripts/e2e/manifest_gaudi_test.sh delete mode 100755 .github/workflows/scripts/e2e/manifest_xeon_test.sh delete mode 100755 .github/workflows/scripts/e2e/utils.sh delete mode 100755 .github/workflows/scripts/go-coverage.sh delete mode 100644 .github/workflows/scripts/test_ut.sh create mode 100644 .pre-commit-config.yaml create mode 100644 asr-0.9.0.tgz create mode 100644 chatqna-0.9.0.tgz create mode 100644 chatqna-ui-0.9.0.tgz create mode 100644 codegen-0.9.0.tgz create mode 100644 codetrans-0.9.0.tgz create mode 100644 data-prep-0.9.0.tgz create mode 100644 docsum-0.9.0.tgz create mode 100644 embedding-usvc-0.9.0.tgz create mode 100644 index.old create mode 100644 llm-uservice-0.9.0.tgz create mode 100644 redis-vector-db-0.9.0.tgz create mode 100644 reranking-usvc-0.9.0.tgz create mode 100644 retriever-usvc-0.9.0.tgz create mode 100644 speecht5-0.9.0.tgz create mode 100644 tei-0.9.0.tgz create mode 100644 teirerank-0.9.0.tgz create mode 100644 tgi-0.9.0.tgz create mode 100644 tts-0.9.0.tgz create mode 100644 web-retriever-0.9.0.tgz create mode 100644 whisper-0.9.0.tgz diff --git a/.github/code_spell_ignore.txt b/.github/code_spell_ignore.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 7a1fa9e6..00000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,23 +0,0 @@ -## Description - -The summary of the proposed changes as long as the relevant motivation and context. - -## Issues - -List the issue or RFC link this PR is working on. If there is no such link, please mark it as `n/a`. - -## Type of change - -List the type of change like below. Please delete options that are not relevant. - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds new functionality) -- [ ] Breaking change (fix or feature that would break existing design and interface) - -## Dependencies - -List the newly introduced 3rd party dependency if exists. - -## Tests - -Describe the tests that you ran to verify your changes. diff --git a/.github/workflows/_gmc-e2e.yaml b/.github/workflows/_gmc-e2e.yaml deleted file mode 100644 index 5a2d16f6..00000000 --- a/.github/workflows/_gmc-e2e.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GMC E2e Test For Call -on: - workflow_call: - inputs: - tag: - default: "latest" - description: "Tag to apply to images, default is latest" - required: false - type: string - repo: - description: "Repo to apply to images, default is empty string" - required: false - type: string - -jobs: - go-e2e: - runs-on: kind-xeon - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set variables - env: - imagerepo: ${{ inputs.repo }} - imagetag: ${{ inputs.tag }} - run: | - if [[ -z "$imagerepo" ]]; then - echo "DOCKER_REGISTRY=${OPEA_IMAGE_REPO}" >> $GITHUB_ENV - else - echo "DOCKER_REGISTRY=${imagerepo}" >> $GITHUB_ENV - fi - echo "DOCKER_REGISTRY=$DOCKER_REGISTRY" >> $GITHUB_ENV - echo "VERSION=$imagetag" >> $GITHUB_ENV - echo "SYSTEM_NAMESPACE=opea-system" >> $GITHUB_ENV - echo "APP_NAMESPACE=opea-app" >> $GITHUB_ENV - echo "should_cleanup=false" >> $GITHUB_ENV - echo "skip_validate=false" >> $GITHUB_ENV - echo "KIND_MOUNT_DIR=/mnt/huggingface/hub" >> $GITHUB_ENV - - - name: Install GMC - run: | - echo "should_cleanup=true" >> $GITHUB_ENV - .github/workflows/scripts/e2e/gmc_install.sh install_gmc - exit_status=$$?$$ - if [ $$exit_status -ne 0 ]; then - echo "Failed to install modules" - echo "skip_validate=true" >> $GITHUB_ENV - fi - - - name: Run e2e tests - run: | - if $skip_validate; then - echo "Skip validate" - else - .github/workflows/scripts/e2e/gmc_xeon_test.sh validate_gmc - fi - - - name: Cleanup modules - if: always() - run: | - if $should_cleanup; then - .github/workflows/scripts/e2e/gmc_xeon_test.sh cleanup_apps - .github/workflows/scripts/e2e/gmc_install.sh cleanup_gmc - fi diff --git a/.github/workflows/_gmc-image-build.yaml b/.github/workflows/_gmc-image-build.yaml deleted file mode 100644 index 65daad83..00000000 --- a/.github/workflows/_gmc-image-build.yaml +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GMC Image Build -permissions: read-all -on: - workflow_call: - inputs: - image_repo: - required: false - type: string - image_tag: - required: true - type: string - runner_label: - required: false - type: string - default: 'docker-build-xeon' - outputs: - image_repo: - description: "The image repository used for the image build" - value: ${{ jobs.image-build.outputs.image_repo }} - image_tag: - description: "The image tag used for the image build" - value: ${{ jobs.image-build.outputs.version }} -env: - GOSRC_DIR: "microservices-connector" - -jobs: - image-build: - runs-on: ${{ inputs.runner_label }} - outputs: - image_repo: ${{ steps.set_variables.outputs.IMAGE_REPO }} - version: ${{ steps.set_variables.outputs.VERSION }} - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set variables - id: set_variables - env: - imagerepo: ${{ inputs.image_repo }} - run: | - if [[ -z "$imagerepo" ]]; then - echo "DOCKER_REGISTRY=${OPEA_IMAGE_REPO}opea" >> $GITHUB_ENV - echo "IMAGE_REPO=${OPEA_IMAGE_REPO}" >> $GITHUB_OUTPUT - else - echo "DOCKER_REGISTRY=${imagerepo}/opea" >> $GITHUB_ENV - echo "IMAGE_REPO=${imagerepo}/" >> $GITHUB_OUTPUT - fi - echo "VERSION=${{ inputs.image_tag }}" >> $GITHUB_ENV - echo "VERSION=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT - - - name: Build image and push - run: | - cd $GOSRC_DIR - make docker.build - make docker.push - - - name: Clean up images - if: always() - run: | - # clean up the images - docker rmi ${{ env.DOCKER_REGISTRY }}/gmcrouter:${{ env.VERSION }} - docker rmi ${{ env.DOCKER_REGISTRY }}/gmcmanager:${{ env.VERSION }} - echo y | docker image prune diff --git a/.github/workflows/_infra-workflow.yaml b/.github/workflows/_infra-workflow.yaml deleted file mode 100644 index 46f3d80a..00000000 --- a/.github/workflows/_infra-workflow.yaml +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GenAIInfra GMC Jobs -permissions: read-all -on: - workflow_call: - inputs: - tag: - default: "latest" - required: false - type: string - build: - default: true - required: false - type: boolean - scan: - default: true - required: false - type: boolean - test_gmc: - default: true - required: false - type: boolean - publish: - default: false - required: false - type: boolean - publish_tags: - default: "latest" - required: false - type: string -jobs: -#################################################################################################### -# Image Build -#################################################################################################### - image-build: - if: ${{ fromJSON(inputs.build) }} - uses: ./.github/workflows/_gmc-image-build.yaml - with: - image_tag: ${{ inputs.tag }} - runner_label: 'docker-build-xeon' - -#################################################################################################### -# Trivy Scan -#################################################################################################### - scan-images: - needs: [image-build] - if: ${{ fromJSON(inputs.scan) }} - strategy: - matrix: - image: ["gmcmanager", "gmcrouter"] - runs-on: 'docker-build-xeon' - steps: - - name: Harden Runner - uses: step-security/harden-runner@v2.8.1 - with: - egress-policy: audit - - - name: Pull Image - run: docker pull ${OPEA_IMAGE_REPO}opea/${{ matrix.image }}:${{ inputs.tag }} - - - name: Scan Container - uses: opea-project/validation/actions/trivy-scan@main - with: - image-ref: ${OPEA_IMAGE_REPO}opea/${{ matrix.image }}:${{ inputs.tag }} - output: ${{ matrix.image }}-scan.txt - - - name: Cleanup - if: always() - run: docker rmi -f ${OPEA_IMAGE_REPO}opea/${{ matrix.image }}:${{ inputs.tag }} - - uses: actions/upload-artifact@v4.3.4 - with: - name: gmc-scan - path: ${{ matrix.image }}-scan.txt - overwrite: true - -#################################################################################################### -# GMC Test -#################################################################################################### - test-gmc: - needs: [image-build] - if: ${{ fromJSON(inputs.test_gmc) }} - uses: ./.github/workflows/_gmc-e2e.yaml - with: - repo: ${{ needs.image-build.outputs.image_repo }} - tag: ${{ needs.image-build.outputs.image_tag }} - secrets: inherit - - -#################################################################################################### -# Publish -#################################################################################################### - publish: - needs: [image-build, scan-images, test-gmc] - if: ${{ fromJSON(inputs.publish) }} - strategy: - matrix: - image: ["gmcmanager", "gmcrouter"] - runs-on: "docker-build-xeon" - steps: - - name: Image Publish - uses: opea-project/validation/actions/image-publish@main - with: - local_image_ref: ${OPEA_IMAGE_REPO}opea/${{ matrix.image }}:${{ inputs.tag }} - image_name: opea/${{ matrix.image }} - publish_tags: ${{ inputs.publish_tags }} diff --git a/.github/workflows/docker/code-scan.dockerfile b/.github/workflows/docker/code-scan.dockerfile deleted file mode 100644 index 450f3f31..00000000 --- a/.github/workflows/docker/code-scan.dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -ARG UBUNTU_VER=22.04 -FROM ubuntu:${UBUNTU_VER} as devel - -ENV LANG C.UTF-8 - -RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ - aspell \ - aspell-en \ - build-essential \ - python3 \ - python3-pip \ - python3-dev \ - python3-distutils \ - wget - -RUN ln -sf $(which python3) /usr/bin/python - -RUN python -m pip install --no-cache-dir bandit - -WORKDIR / diff --git a/.github/workflows/docker/ut.dockerfile b/.github/workflows/docker/ut.dockerfile deleted file mode 100644 index 1ef50770..00000000 --- a/.github/workflows/docker/ut.dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -ARG UBUNTU_VER=22.04 -FROM ubuntu:${UBUNTU_VER} as devel - -ENV LANG C.UTF-8 - -RUN apt-get update && apt-get install -y --no-install-recommends --fix-missing \ - aspell \ - aspell-en \ - build-essential \ - python3 \ - python3-pip \ - python3-dev \ - python3-distutils \ - git \ - vim \ - wget - -RUN ln -sf $(which python3) /usr/bin/python -RUN python -m pip install --no-cache-dir pytest - -WORKDIR / diff --git a/.github/workflows/manual-cd-workflow.yaml b/.github/workflows/manual-cd-workflow.yaml deleted file mode 100644 index e839c7fc..00000000 --- a/.github/workflows/manual-cd-workflow.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GenAIInfra GMC CD workflow on manual event -on: - workflow_dispatch: - inputs: - tag: - default: "latest" - description: "Tag to apply to images" - required: true - type: string - build: - default: true - description: 'Build test required images for Examples' - required: false - type: boolean - scan: - default: true - description: 'Scan all images with Trivy' - required: false - type: boolean - test_gmc: - default: true - description: 'Test GMC on Xeon KIND' - required: false - type: boolean - publish: - default: false - description: 'Publish images to docker hub' - required: false - type: boolean - publish_tags: - default: "latest,v0.9" - description: 'Tag list apply to publish images' - required: false - type: string - -permissions: read-all -jobs: - gmc-release: - uses: ./.github/workflows/_infra-workflow.yaml - with: - tag: ${{ inputs.tag }} - build: ${{ fromJSON(inputs.build) }} - scan: ${{ fromJSON(inputs.scan) }} - test_gmc: ${{ fromJSON(inputs.test_gmc) }} - publish: ${{ fromJSON(inputs.publish) }} - publish_tags: ${{ fromJSON(inputs.publish_tags) }} - secrets: inherit diff --git a/.github/workflows/manual-image-build.yaml b/.github/workflows/manual-image-build.yaml deleted file mode 100644 index 853aaf1d..00000000 --- a/.github/workflows/manual-image-build.yaml +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Build GMC latest images on manual event - -on: - workflow_dispatch: - inputs: - registry: - default: "" - description: "Registry to store images, default is empty" - required: false - type: string - tag: - default: "latest" - description: "Tag to apply to images" - required: true - type: string - nodes: - default: "docker-build-xeon, docker-build-gaudi" - description: "List of nodes to run the build on" - required: true - type: string - -concurrency: - group: ${{ github.workflow }}-on-manual - cancel-in-progress: true - -jobs: - get-build-matrix: - runs-on: ubuntu-latest - outputs: - nodes: ${{ steps.get-services.outputs.nodes }} - steps: - - name: Get test Services - id: get-services - run: | - set -x - node_list=($(echo ${{ github.event.inputs.nodes }} | tr ',' ' ')) - nodes=$(printf '%s\n' "${node_list[@]}" | sort -u | jq -R '.' | jq -sc '.') - echo "nodes=$nodes" >> $GITHUB_OUTPUT - - image-build: - needs: get-build-matrix - strategy: - matrix: - node: ${{ fromJSON(needs.get-build-matrix.outputs.nodes) }} - uses: ./.github/workflows/_gmc-image-build.yaml - with: - image_repo: ${{ github.event.inputs.registry }} - image_tag: ${{ github.event.inputs.tag }} - runner_label: ${{ matrix.node }} diff --git a/.github/workflows/manual-trellix.yml b/.github/workflows/manual-trellix.yml deleted file mode 100644 index 8779f3b9..00000000 --- a/.github/workflows/manual-trellix.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Trellix Command Line Scanner - -on: - workflow_dispatch: - schedule: - - cron: "35 1 * * 6" - -jobs: - Trellix: - runs-on: trellix - steps: - - name: Clean Up Working Directory - run: sudo rm -rf ${{github.workspace}}/* - - - name: Checkout out Repo - uses: actions/checkout@v4 - - - name: Run Trellix Scanner - env: - workspace: ${{ github.workspace }} - run: bash .github/workflows/scripts/codeScan/trellix.sh - - - name: Publish pipeline artifact - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - path: ${{ github.workspace }}/.github/workflows/scripts/codeScan/report.html diff --git a/.github/workflows/pr-chart-e2e.yaml b/.github/workflows/pr-chart-e2e.yaml deleted file mode 100644 index 221ab012..00000000 --- a/.github/workflows/pr-chart-e2e.yaml +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: E2E test with helm charts - -on: - pull_request_target: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths: - - helm-charts/** - - .github/workflows/chart-e2e.yaml - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - CHARTS_DIR: "helm-charts" - -jobs: - job1: - name: Get-test-matrix - runs-on: ubuntu-latest - outputs: - run_matrix: ${{ steps.get-test-matrix.outputs.run_matrix }} - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - ref: "refs/pull/${{ github.event.number }}/merge" - fetch-depth: 0 - - - name: Get test matrix - id: get-test-matrix - run: | - base_commit=${{ github.event.pull_request.base.sha }} - merged_commit=$(git log -1 --format='%H') - e2e_charts=$(git diff --name-only ${base_commit} ${merged_commit} | \ - grep "^$CHARTS_DIR/" | \ - grep -vE 'README.md|common|*.sh' | \ - cut -d'/' -f2 | sort -u ) - common_charts=$(git diff --name-only ${base_commit} ${merged_commit} | \ - grep "^$CHARTS_DIR/common" | \ - grep -vE 'README.md|*.sh' | \ - cut -d'/' -f3 | sort -u ) - run_matrix="{\"include\":[" - for chart in ${e2e_charts}; do - if [ -f $CHARTS_DIR/$chart/gaudi-values.yaml ]; then - run_matrix="${run_matrix}{\"example\":\"${chart}\",\"hardware\":\"gaudi\"}," - fi - run_matrix="${run_matrix}{\"example\":\"${chart}\",\"hardware\":\"xeon\"}," - done - for chart in ${common_charts}; do - if [ -f $CHARTS_DIR/common/$chart/gaudi-values.yaml ]; then - run_matrix="${run_matrix}{\"example\":\"${chart}\",\"hardware\":\"gaudi\",\"directory\":\"common\"}," - fi - run_matrix="${run_matrix}{\"example\":\"${chart}\",\"hardware\":\"xeon\",\"directory\":\"common\"}," - done - run_matrix=$run_matrix"]}" - echo "run_matrix=${run_matrix}" - echo "run_matrix=${run_matrix}" >> $GITHUB_OUTPUT - - Chart-test: - needs: job1 - if: always() && ${{ needs.job1.outputs.run_matrix.example.length }} > 0 - strategy: - matrix: ${{ fromJSON(needs.job1.outputs.run_matrix) }} - runs-on: ${{ matrix.hardware }} - continue-on-error: true - outputs: - should_cleanup: ${{ steps.set_boolean.outputs.should_cleanup }} - steps: - - name: E2e test chart - run: | - echo "Matrix - chart: ${{ matrix.example }}" - - - name: Clean Up Working Directory - run: sudo rm -rf ${{github.workspace}}/* - - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - ref: "refs/pull/${{ github.event.number }}/merge" - - - name: Set variables - run: | - echo "RELEASE_NAME=${{ matrix.example }}$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV - echo "NAMESPACE=${{ matrix.example }}-$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV - echo "ROLLOUT_TIMEOUT_SECONDS=600s" >> $GITHUB_ENV - echo "KUBECTL_TIMEOUT_SECONDS=60s" >> $GITHUB_ENV - echo "should_cleanup=false" >> $GITHUB_ENV - echo "skip_validate=false" >> $GITHUB_ENV - echo "RELEASENAME=$RELEASE_NAME" - echo "NAMESPACE=$NAMESPACE" - if [ -n "${{ matrix.directory }}" ]; then - echo "CHART_FOLDER=$CHARTS_DIR/${{ matrix.directory }}/${{ matrix.example }}" >> $GITHUB_ENV - else - echo "CHART_FOLDER=$CHARTS_DIR/${{ matrix.example }}" >> $GITHUB_ENV - fi - - - name: Initialize chart testing - run: | - # Replace values for CI test environment - USER_ID=$(whoami) - CHART_MOUNT=/home/$USER_ID/.cache/huggingface/hub - HFTOKEN=$(cat /home/$USER_ID/.cache/huggingface/token) - pushd helm-charts - # insert a prefix before opea/.*, the prefix is OPEA_IMAGE_REPO - find . -name '*values.yaml' -type f -exec sed -i "s#repository: opea/*#repository: ${OPEA_IMAGE_REPO}opea/#g" {} \; - # set OPEA image tag to latest - find . -name '*values.yaml' -type f -exec sed -i 's#tag: ""#tag: latest#g' {} \; - # set huggingface token - find . -name '*values.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#${HFTOKEN}#g" {} \; - # replace the mount dir "Volume: *" with "Volume: $CHART_MOUNT" - find . -name '*values.yaml' -type f -exec sed -i "s#modelUseHostPath: .*#modelUseHostPath: $CHART_MOUNT#g" {} \; - # replace the pull policy "IfNotPresent" with "Always" - find . -name '*values.yaml' -type f -exec sed -i "s#pullPolicy: IfNotPresent#pullPolicy: Always#g" {} \; - popd - - - name: Helm install - id: install - env: - GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }} - GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} - run: | - set -xe - echo "should_cleanup=true" >> $GITHUB_ENV - helm-charts/update_dependency.sh && helm dependency update ${{ env.CHART_FOLDER}} - value_file="values.yaml" - if [ "${{ matrix.hardware }}" == "gaudi" ]; then - value_file="gaudi-values.yaml" - fi - if ! helm install --create-namespace --namespace $NAMESPACE --wait \ - --timeout "$ROLLOUT_TIMEOUT_SECONDS" \ - --set autodependency.enabled=true \ - --set GOOGLE_API_KEY=${{ env.GOOGLE_API_KEY}} \ - --set GOOGLE_CSE_ID=${{ env.GOOGLE_CSE_ID}} \ - --values ${{ env.CHART_FOLDER}}/${value_file} \ - $RELEASE_NAME ${{ env.CHART_FOLDER}} ; then - echo "Failed to install chart ${{ matrix.example }}" - echo "skip_validate=true" >> $GITHUB_ENV - .github/workflows/scripts/e2e/chart_test.sh dump_pods_status $NAMESPACE - exit 1 - fi - - - name: Validate e2e test - if: always() - run: | - set -xe - if $skip_validate; then - echo "Skip validate" - else - LOG_PATH=/home/$(whoami)/logs - chart=${{ matrix.example }} - helm test -n $NAMESPACE $RELEASE_NAME --logs |tee ${LOG_PATH}/charts-${chart}.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Chart ${chart} test failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - teststatus=false - if [[ -f $LOG_PATH/charts-${chart}.log ]] && \ - [[ $(grep -c "^Phase:.*Failed" $LOG_PATH/charts-${chart}.log) != 0 ]]; then - teststatus=false - .github/workflows/scripts/e2e/chart_test.sh dump_all_pod_logs $NAMESPACE - else - teststatus=true - fi - - if [ $teststatus == false ]; then - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - exit 0 - fi - fi - - - name: Helm uninstall - if: always() - run: | - if $should_cleanup; then - helm uninstall $RELEASE_NAME --namespace $NAMESPACE - if ! kubectl delete ns $NAMESPACE --timeout=$KUBECTL_TIMEOUT_SECONDS; then - kubectl delete pods --namespace $NAMESPACE --force --grace-period=0 --all - kubectl delete ns $NAMESPACE --force --grace-period=0 --timeout=$KUBECTL_TIMEOUT_SECONDS - fi - fi diff --git a/.github/workflows/pr-chart-validate.yaml b/.github/workflows/pr-chart-validate.yaml deleted file mode 100644 index c7ec580c..00000000 --- a/.github/workflows/pr-chart-validate.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Charts Validate - -on: - pull_request: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths: - - helm-charts/** - - .github/workflows/chart-validate.yaml - workflow_dispatch: - -# If there is a new commit, the previous jobs will be canceled -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - CHARTS_DIR: "helm-charts" - -jobs: - charts-validate: - runs-on: ubuntu-latest - permissions: - contents: write - actions: write - packages: write - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Helm - uses: azure/setup-helm@v4.2.0 - with: - version: v3.14.4 - - uses: actions/setup-python@v5 - with: - python-version: "3.x" - check-latest: true - - name: Set up chart-testing - uses: helm/chart-testing-action@v2.6.1 - - name: Run chart-testing (list-changed) - id: list-changed - run: | - changed=$(ct list-changed --chart-dirs ${{ env.CHARTS_DIR }} --target-branch ${{ github.event.repository.default_branch }}) - if [[ -n "$changed" ]]; then - echo "changed=true" >> "$GITHUB_OUTPUT" - fi - - name: Run chart-testing (lint) - if: steps.list-changed.outputs.changed == 'true' - run: | - helm-charts/update_dependency.sh - ct lint --check-version-increment=false --validate-maintainers=false --chart-dirs ${{ env.CHARTS_DIR }} --target-branch ${{ github.event.repository.default_branch }} diff --git a/.github/workflows/pr-code-scan.yml b/.github/workflows/pr-code-scan.yml deleted file mode 100644 index cd0fdcb7..00000000 --- a/.github/workflows/pr-code-scan.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Code Scan - -on: - pull_request: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths-ignore: - - "**.md" - workflow_dispatch: - -# If there is a new commit, the previous jobs will be canceled -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - DOCKER_CONFIG_NAME: "commonDockerConfig" - REPO_NAME: "code-scan" - REPO_TAG: "1.0" - DOCKER_FILE_NAME: "code-scan" - CONTAINER_NAME: "code-scan" - -jobs: - code-scan: - runs-on: ubuntu-latest - strategy: - matrix: - job_name: ["bandit", "hadolint"] - fail-fast: false - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - - - name: Docker Build - run: | - docker build -f ${{ github.workspace }}/.github/workflows/docker/${{ env.DOCKER_FILE_NAME }}.dockerfile -t ${{ env.REPO_NAME }}:${{ env.REPO_TAG }} . - - - name: Docker Run - run: | - if [[ $(docker ps -a | grep -i '${{ env.CONTAINER_NAME }}'$) ]]; then - docker stop ${{ env.CONTAINER_NAME }} - docker rm -vf ${{ env.CONTAINER_NAME }} || true - fi - docker run -dit --memory="4g" --memory-reservation="1g" --disable-content-trust --privileged --name=${{ env.CONTAINER_NAME }} --shm-size="1g" \ - -v ${{ github.workspace }}:/GenAIInfra \ - ${{ env.REPO_NAME }}:${{ env.REPO_TAG }} - - - name: Code scan check - run: | - docker exec ${{ env.CONTAINER_NAME }} \ - bash -c "bash /GenAIInfra/.github/workflows/scripts/codeScan/${{ matrix.job_name }}.sh" - - - name: Publish pipeline artifact - if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.job_name }} - path: ${{ github.workspace }}/.github/workflows/scripts/codeScan/${{ matrix.job_name }}.* diff --git a/.github/workflows/pr-gmc-helm.yaml b/.github/workflows/pr-gmc-helm.yaml deleted file mode 100644 index c924a6bb..00000000 --- a/.github/workflows/pr-gmc-helm.yaml +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GMC Helm Installation Test on Xeon - -on: - pull_request: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths: - - microservices-connector/config/crd/bases/gmc.opea.io_gmconnectors.yaml - - microservices-connector/config/gmcrouter/gmc-router.yaml - - microservices-connector/helm/** - - "!**.md" - - "!**.txt" - - "!**.png" - - .github/workflows/scripts/e2e/gmc_xeon_test.sh - workflow_dispatch: - -# If there is a new commit, the previous jobs will be canceled -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-xeon - cancel-in-progress: true - -env: - GOSRC_DIR: "microservices-connector" - -jobs: - image-build: - uses: ./.github/workflows/_gmc-image-build.yaml - with: - image_tag: ${{ github.event.pull_request.head.sha }} - runner_label: 'docker-build-xeon' - - go-e2e: - runs-on: kind-xeon - needs: image-build - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set variables - run: | - echo "RELEASE_NAME=gmc$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV - echo "SYSTEM_NAMESPACE=opea-system-helm" >> $GITHUB_ENV - echo "APP_NAMESPACE=opea-app" >> $GITHUB_ENV - echo "DOCKER_REGISTRY=${{ needs.image-build.outputs.image_repo }}" >> $GITHUB_ENV - echo "VERSION=${{ needs.image-build.outputs.image_tag }}" >> $GITHUB_ENV - echo "should_cleanup=false" >> $GITHUB_ENV - echo "skip_validate=false" >> $GITHUB_ENV - echo "KIND_MOUNT_DIR=/mnt/huggingface/hub" >> $GITHUB_ENV - echo "ROLLOUT_TIMEOUT_SECONDS=600s" >> $GITHUB_ENV - - - name: Install GMC - run: | - echo "should_cleanup=true" >> $GITHUB_ENV - USER_ID=$(whoami) - HFTOKEN=$(cat /home/$USER_ID/.cache/huggingface/token) - pushd microservices-connector/helm - # replace image for gmc-router - sed -i "s|image:.*|image: ${DOCKER_REGISTRY}opea/gmcrouter:$VERSION|" gmc-router.yaml - # replace the pull policy "IfNotPresent" with "Always" for gmc-router - sed -i "s|imagePullPolicy:.*|imagePullPolicy: Always|" gmc-router.yaml - # replace image for GenAI component manifests - find manifests_common/ -type f -name "*.yaml" -exec sed -i "s|image: \"opea|image: \"${DOCKER_REGISTRY}opea|" {} \; - # replace the pull policy "IfNotPresent" with "Always" for GenAI component manifests - find manifests_common/ -type f -name "*.yaml" -exec sed -i "s|imagePullPolicy: IfNotPresent|imagePullPolicy: Always|" {} \; - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find manifests_common/ -type f -name "*.yaml" -exec sed -i "s|path: /mnt/opea-models|path: $KIND_MOUNT_DIR|" {} \; - # replace huggingface token - find manifests_common/ -type f -name '*.yaml' -exec sed -i "s#insert-your-huggingface-token-here#${HFTOKEN}#g" {} \; - popd - # Install GMC - if ! helm install --create-namespace --namespace ${{ env.SYSTEM_NAMESPACE }} --wait \ - --timeout "$ROLLOUT_TIMEOUT_SECONDS" \ - $RELEASE_NAME microservices-connector/helm \ - --set image.tag=$VERSION --set image.repository=${DOCKER_REGISTRY}opea/gmcmanager; then - echo "Failed to install GMC by helm" - echo "skip_validate=true" >> $GITHUB_ENV - .github/workflows/scripts/e2e/chart_test.sh dump_pods_status ${{ env.SYSTEM_NAMESPACE }} - exit 1 - fi - - - name: Run e2e tests - run: | - if $skip_validate; then - echo "Skip validate" - else - .github/workflows/scripts/e2e/gmc_xeon_test.sh validate_gmc - fi - - - name: Cleanup modules - if: always() - run: | - if $should_cleanup; then - .github/workflows/scripts/e2e/gmc_xeon_test.sh cleanup_apps - helm uninstall $RELEASE_NAME --namespace ${{ env.SYSTEM_NAMESPACE }} - if kubectl get namespace ${{ env.SYSTEM_NAMESPACE }} > /dev/null 2>&1; then - echo "Deleting namespace: $SYSTEM_NAMESPACE" - kubectl delete namespace "$SYSTEM_NAMESPACE" - fi - kubectl delete crd gmconnectors.gmc.opea.io - kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io ${RELEASE_NAME}-controller --ignore-not-found - fi diff --git a/.github/workflows/pr-go-e2e.yaml b/.github/workflows/pr-go-e2e.yaml deleted file mode 100644 index f30774a9..00000000 --- a/.github/workflows/pr-go-e2e.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: GMC E2e Tests on Xeon - -on: - pull_request: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths: - - microservices-connector/** - - "!microservices-connector/helm/**" - - "!**.md" - - "!**.txt" - - "!**.png" - - .github/workflows/scripts/e2e/gmc_xeon_test.sh - workflow_dispatch: - -# If there is a new commit, the previous jobs will be canceled -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-xeon - cancel-in-progress: true - -env: - GOSRC_DIR: "microservices-connector" - -jobs: - image-build: - uses: ./.github/workflows/_gmc-image-build.yaml - with: - image_tag: ${{ github.event.pull_request.head.sha }} - runner_label: 'docker-build-xeon' - - go-e2e: - needs: [image-build] - uses: ./.github/workflows/_gmc-e2e.yaml - with: - repo: ${{ needs.image-build.outputs.image_repo }} - tag: ${{ needs.image-build.outputs.image_tag }} - secrets: inherit diff --git a/.github/workflows/pr-go-unittests.yaml b/.github/workflows/pr-go-unittests.yaml deleted file mode 100644 index 144b748d..00000000 --- a/.github/workflows/pr-go-unittests.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Golang Lint and Unit Tests - -on: - pull_request: - branches: [main] - types: [opened, reopened, ready_for_review, synchronize] # added `ready_for_review` since draft is skipped - paths: - - microservices-connector/** - - "!microservices-connector/helm/**" - - "!**.md" - - "!**.txt" - - "!**.png" - workflow_dispatch: - -# If there is a new commit, the previous jobs will be canceled -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -env: - GOSRC_DIR: "microservices-connector" - -jobs: - go-unittests: - runs-on: ubuntu-latest - permissions: - contents: write - actions: write - packages: write - steps: - - name: Set up Go 1.21 - uses: actions/setup-go@v5 - with: - go-version: "1.21" - id: go - - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Run golang lint - uses: golangci/golangci-lint-action@v6 - with: - args: --timeout=10m - version: v1.55.2 - working-directory: ${{ env.GOSRC_DIR }} - - - name: Run golangunit test - run: | - cd $GOSRC_DIR - make test - - - name: Run tests and generate coverage - run: | - cd $GOSRC_DIR - go test -coverprofile=coverage.out $(go list ./... | grep -v /e2e) - ../.github/workflows/scripts/go-coverage.sh diff --git a/.github/workflows/push-image-build.yaml b/.github/workflows/push-image-build.yaml deleted file mode 100644 index 46e6f895..00000000 --- a/.github/workflows/push-image-build.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -name: Upgrade GMC system on push event - -on: - push: - branches: ["main"] - paths: - - microservices-connector/** - - "!microservices-connector/helm/**" - - "!**.md" - - "!**.txt" - - "!**.png" - - "!.**" - - .github/workflows/gmc-on-push.yaml - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-on-push - cancel-in-progress: true - -env: - GOSRC_DIR: "microservices-connector" - -jobs: - image-build: - strategy: - matrix: - platform: [xeon, gaudi] - uses: ./.github/workflows/_gmc-image-build.yaml - with: - image_tag: 'latest' - runner_label: 'docker-build-${{ matrix.platform }}' - - gmc-install: - strategy: - matrix: - platform: [xeon, gaudi] - needs: image-build - runs-on: ${{ matrix.platform }} - steps: - - name: Checkout out Repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set variables - run: | - echo "SYSTEM_NAMESPACE=opea-system" >> $GITHUB_ENV - # don't set IMAGE_REPO to the outputs because outputs.image_repo may not be correct - # echo "IMAGE_REPO=${{ needs.image-build.outputs.image_repo }}" >> $GITHUB_ENV - echo "VERSION=${{ needs.image-build.outputs.image_tag }}" >> $GITHUB_ENV - - - name: Cleanup existing GMC - run: | - .github/workflows/scripts/e2e/gmc_install.sh cleanup_gmc - - - name: Install GMC - run: | - .github/workflows/scripts/e2e/gmc_install.sh install_gmc diff --git a/.github/workflows/scripts/change_color b/.github/workflows/scripts/change_color deleted file mode 100644 index b2ea724f..00000000 --- a/.github/workflows/scripts/change_color +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -# -------------- general approach start---------------- - -# 1. import this file: -# source path/change_color.sh -# 2. use COLOR/BG: -# $VARIABLE_NAME && out_put_content && $RESET -# 3. COLOR + BG: -# $COLOR/BG_VARIABLE_NAME && $BG/COLOR_VARIABLE_NAME && out_put_content && $RESET -# 4. custom -# abbreviation(change number) -# txt number range (30, 37) -# bg number range (40, 47) -# special effects number range (1, 7) -# echo -en \\E[number1 + ; + number2 + ; + number3 + m" -# e.g - BG_GRAY+LIGHT_RED = "echo -en \\E[47;31m" - -# -------------- general approach end----------------== - -# general setting -# ------------- light_color start---------------- -# black -LIGHT_BLACK="echo -en \\E[30m" -# red -LIGHT_RED="echo -en \\E[31m" -# green -LIGHT_GREEN="echo -en \\E[32m" -# yellow -LIGHT_YELLOW="echo -en \\E[33m" -# blue -LIGHT_BLUE="echo -en \\E[34m" -# purple -LIGHT_PURPLE="echo -en \\E[35m" -# cyan -LIGHT_CYAN="echo -en \\E[36m" -# gray -LIGHT_GRAY="echo -en \\E[37m" -# ------------- light_color end---------------- - -# ------------- bold_color start---------------- -# black -BOLD_BLACK="echo -en \\E[1;30m" -# red -BOLD_RED="echo -en \\E[1;31m" -# green -BOLD_GREEN="echo -en \\E[1;32m" -# yellow -BOLD_YELLOW="echo -en \\E[1;33m" -# blue -BOLD_BLUE="echo -en \\E[1;34m" -# purple -BOLD_PURPLE="echo -en \\E[1;35m" -# cyan -BOLD_CYAN="echo -en \\E[1;36m" -# gray -BOLD_GRAY="echo -en \\E[1;37m" -# ------------- bold_color end---------------- - -# ------------- background_color start---------------- -# black -BG_BLACK="echo -en \\E[40m" -# red -BG_RED="echo -en \\E[41m" -# green -BG_GREEN="echo -en \\E[42m" -# yellow -BG_YELLOW="echo -en \\E[43m" -# blue -BG_BLUE="echo -en \\E[44m" -# purple -BG_PURPLE="echo -en \\E[45m" -# cyan -BG_CYAN="echo -en \\E[46m" -# gray -BG_GRAY="echo -en \\E[47m" -# ------------- background_color end---------------- - -# close -RESET="echo -en \\E[0m" diff --git a/.github/workflows/scripts/codeScan/bandit.sh b/.github/workflows/scripts/codeScan/bandit.sh deleted file mode 100644 index c9d5d5b2..00000000 --- a/.github/workflows/scripts/codeScan/bandit.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -source /GenAIInfra/.github/workflows/scripts/change_color -pip install bandit==1.7.8 -log_dir=/GenAIInfra/.github/workflows/scripts/codeScan -python -m bandit -r -lll -iii /GenAIInfra 2>&1 | tee ${log_dir}/bandit.log -exit_code=$? - -$BOLD_YELLOW && echo " ----------------- Current log file output start --------------------------" -cat ${log_dir}/bandit.log -$BOLD_YELLOW && echo " ----------------- Current log file output end --------------------------" && $RESET - -if [ ${exit_code} -ne 0 ]; then - $BOLD_RED && echo "Error!! Please Click on the artifact button to download and view Bandit error details." && $RESET - exit 1 -fi - -$BOLD_PURPLE && echo "Congratulations, Bandit check passed!" && $LIGHT_PURPLE && echo " You can click on the artifact button to see the log details." && $RESET -exit 0 diff --git a/.github/workflows/scripts/codeScan/hadolint.sh b/.github/workflows/scripts/codeScan/hadolint.sh deleted file mode 100644 index 534e1d5b..00000000 --- a/.github/workflows/scripts/codeScan/hadolint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -source /GenAIInfra/.github/workflows/scripts/change_color -log_dir=/GenAIInfra/.github/workflows/scripts/codeScan - -find . -type f \( -name "Dockerfile*" \) -print -exec hadolint --ignore DL3006 --ignore DL3007 --ignore DL3008 {} \; 2>&1 | tee ${log_dir}/hadolint.log - -if [[ $(grep -c "error" ${log_dir}/hadolint.log) != 0 ]]; then - $BOLD_RED && echo "Error!! Please Click on the artifact button to download and check error details." && $RESET - exit 1 -fi - -$BOLD_PURPLE && echo "Congratulations, Hadolint check passed!" && $LIGHT_PURPLE && echo " You can click on the artifact button to see the log details." && $RESET -exit 0 diff --git a/.github/workflows/scripts/codeScan/trellix.sh b/.github/workflows/scripts/codeScan/trellix.sh deleted file mode 100644 index 909b2d13..00000000 --- a/.github/workflows/scripts/codeScan/trellix.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -source ${workspace}/.github/workflows/scripts/change_color -log_dir=${workspace}/.github/workflows/scripts/codeScan - - -echo "---Updating definition (DAT) files ---" -DEFS_URL=https://update.nai.com/products/commonupdater/current/vscandat1000/dat/0000 -echo "Finding latest defs at $DEFS_URL/avvdat.ini..." \ - && wget -q $DEFS_URL/avvdat.ini \ - && echo "SUCCESS" || fail - -inifile="avvdat.ini" -filename=`awk -F"=" '$2 ~ /avvdat.*zip/ { print $2 } ' $inifile` -filename2="$(echo -e "${filename}" | tr -d '[:space:]')" - -if [ -z "$filename2" ] -then - echo "Cannot get defs information from INI file:" - cat $inifile - fail -fi - -echo "Downloading latest defs from $DEFS_URL/$filename2..." \ - && wget -q $DEFS_URL/$filename2 \ - && echo "SUCCESS" || fail - -echo "Extracting latest defs..." \ - && unzip -o $filename2 -d /usr/local/uvscan \ - && echo "SUCCESS" || fail - -echo "--- Scanning ---" -ENV_SCAN_OPTS="--analyze --mime --program --recursive --unzip --threads 4 --summary --verbose --html=${workspace}/.github/workflows/scripts/codeScan/report.html" -echo "Scan Options: $ENV_SCAN_OPTS" - -rm -r ${workspace}/avvdat* -rm -r ${workspace}/.git -uvscan $ENV_SCAN_OPTS ${workspace} 2>&1 | tee ${log_dir}/trellix.log - -if [[ $(grep "Possibly Infected" ${log_dir}/trellix.log | sed 's/[^0-9]//g') != 0 ]]; then - $BOLD_RED && echo "Error!! Please Click on the artifact button to download and check error details." && $RESET - exit 1 -fi - -$BOLD_PURPLE && echo "Congratulations, Trellix Scan passed!" && $LIGHT_PURPLE && echo " You can click on the artifact button to see the log details." && $RESET -exit 0 diff --git a/.github/workflows/scripts/e2e/chart_test.sh b/.github/workflows/scripts/e2e/chart_test.sh deleted file mode 100755 index 9b9f0460..00000000 --- a/.github/workflows/scripts/e2e/chart_test.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -#set -xe - -function dump_pod_log() { - pod_name=$1 - namespace=$2 - echo "-----------Pod: $pod_name---------" - echo "#kubectl describe pod $pod_name -n $namespace" - kubectl describe pod $pod_name -n $namespace - echo "-----------------------------------" - echo "#kubectl logs $pod_name -n $namespace" - kubectl logs $pod_name -n $namespace - echo "-----------------------------------" -} - -function dump_pods_status() { - namespace=$1 - echo "-----DUMP POD STATUS in NS $namespace------" - kubectl get pods -n $namespace -o wide - echo "-----------------------------------" - - # Get all pods in the namespace and their statuses - pods=$(kubectl get pods -n $namespace --no-headers) - - # Loop through each pod - echo "$pods" | while read -r line; do - pod_name=$(echo $line | awk '{print $1}') - ready=$(echo $line | awk '{print $2}') - status=$(echo $line | awk '{print $3}') - - # Extract the READY count - ready_count=$(echo $ready | cut -d'/' -f1) - required_count=$(echo $ready | cut -d'/' -f2) - - # Check if the pod is not in "Running" status or READY count is less than required - if [[ "$status" != "Running" || "$ready_count" -lt "$required_count" ]]; then - dump_pod_log $pod_name $namespace - fi - done -} - -function dump_failed_pod_logs() { - namespace=$1 - logfile=$2 - - failed_test_suite=$(awk '/TEST SUITE:/{suite=$0} /Phase:/{if($2=="Failed"){print suite; exit}}' "$logfile") - failed_svc_name=$(echo "$failed_test_suite" | sed 's/^[ \t]*//;s/^TEST SUITE:[ \t]*//;s/-testpod$//') - - if [[ -n $failed_svc_name ]]; then - # Get the exact pod name - pods=$(kubectl get pods -n $namespace | grep -v 'testpod' | grep $failed_svc_name | awk '{print $1}') - for pod_name in $pods - do - dump_pod_log $pod_name $namespace - done - fi -} - -function dump_all_pod_logs() { - namespace=$1 - echo "-----DUMP POD STATUS AND LOG in NS $namespace------" - - pods=$(kubectl get pods -n $namespace -o jsonpath='{.items[*].metadata.name}') - for pod_name in $pods - do - dump_pod_log $pod_name $namespace - done -} - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -case "$1" in - dump_pods_status) - dump_pods_status $2 - ;; - dump_failed_pod_logs) - dump_failed_pod_logs $2 $3 - ;; - dump_all_pod_logs) - dump_all_pod_logs $2 - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/gmc_gaudi_test.sh b/.github/workflows/scripts/e2e/gmc_gaudi_test.sh deleted file mode 100755 index 5d10fb19..00000000 --- a/.github/workflows/scripts/e2e/gmc_gaudi_test.sh +++ /dev/null @@ -1,472 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -set -xe - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -source ${DIR}/utils.sh - -USER_ID=$(whoami) -LOG_PATH=/home/$(whoami)/logs -AUDIOQA_NAMESPACE="${APP_NAMESPACE}-audioqa" -CHATQNA_NAMESPACE="${APP_NAMESPACE}-chatqna" -CHATQNA_DATAPREP_NAMESPACE="${APP_NAMESPACE}-chatqna-dataprep" -CHATQNA_SWITCH_NAMESPACE="${APP_NAMESPACE}-chatqna-switch" -CODEGEN_NAMESPACE="${APP_NAMESPACE}-codegen" -CODETRANS_NAMESPACE="${APP_NAMESPACE}-codetrans" -DOCSUM_NAMESPACE="${APP_NAMESPACE}-docsum" - -function validate_gmc() { - echo "validate audio-qna" - validate_audioqa - - echo "validate chat-qna" - validate_chatqna - - echo "validate chat-qna with dataprep" - validate_chatqna_with_dataprep - - echo "validate chat-qna in switch mode" - validate_chatqna_in_switch - - # echo "validate codegen" - # validate_codegen - - # echo "validate codetrans" - # validate_codetrans - - # echo "validate docsum" - # validate_docsum - - get_gmc_controller_logs -} - -function cleanup_apps() { - echo "clean up microservice-connector" - # namespaces=("$CHATQNA_NAMESPACE" "$CHATQNA_DATAPREP_NAMESPACE" "$CHATQNA_SWITCH_NAMESPACE" "$CODEGEN_NAMESPACE" "$CODETRANS_NAMESPACE" "$DOCSUM_NAMESPACE") - namespaces=("$AUDIOQA_NAMESPACE" "$CHATQNA_NAMESPACE" "$CHATQNA_DATAPREP_NAMESPACE" "$CHATQNA_SWITCH_NAMESPACE") - for ns in "${namespaces[@]}"; do - if kubectl get namespace $ns > /dev/null 2>&1; then - echo "Deleting namespace: $ns" - kubectl delete namespace "$ns" - else - echo "Namespace $ns does not exist" - fi - done -} - -function validate_audioqa() { - kubectl create ns $AUDIOQA_NAMESPACE - sed -i "s|namespace: audioqa|namespace: $AUDIOQA_NAMESPACE|g" $(pwd)/config/samples/audioQnA_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/audioQnA_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the audioqa router service to be ready..." - wait_until_pod_ready "audioqa router" $AUDIOQA_NAMESPACE "router-service" - output=$(kubectl get pods -n $AUDIOQA_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $AUDIOQA_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $AUDIOQA_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $AUDIOQA_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $AUDIOQA_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $AUDIOQA_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='audioqa')].status.accessUrl}") - byte_str=$(kubectl exec "$CLIENT_POD" -n $AUDIOQA_NAMESPACE -- curl $accessUrl -s -X POST -d '{"byte_str": "UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA", "parameters":{"max_new_tokens":64, "do_sample": true, "streaming":false}}' -H 'Content-Type: application/json' | jq .byte_str) - if [ -z "$byte_str" ]; then - echo "audioqa failed, please check the the!" - exit 1 - fi - echo "Audioqa response check succeed!" -} - -function validate_chatqna() { - kubectl create ns $CHATQNA_NAMESPACE - sed -i "s|namespace: chatqa|namespace: $CHATQNA_NAMESPACE|g" $(pwd)/config/samples/chatQnA_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_NAMESPACE) - echo $output - - # Wait until the tgi pod is ready - TGI_POD_NAME=$(kubectl get pods --namespace=$CHATQNA_NAMESPACE | grep ^tgi-gaudi-svc | awk '{print $1}') - kubectl describe pod $TGI_POD_NAME -n $CHATQNA_NAMESPACE - kubectl wait --for=condition=ready pod/$TGI_POD_NAME --namespace=$CHATQNA_NAMESPACE --timeout=300s - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # wait for client pod ready - wait_until_pod_ready "client-test" $CHATQNA_NAMESPACE "client-test" - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_NAMESPACE - # send request to chatqna - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='chatqa')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CHATQNA_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?","parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna.log ]] && \ - [[ $(grep -c "billion" $LOG_PATH/curl_chatqna.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna.log ]]; then - cat $LOG_PATH/curl_chatqna.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_chatqna_with_dataprep() { - kubectl create ns $CHATQNA_DATAPREP_NAMESPACE - sed -i "s|namespace: chatqa|namespace: $CHATQNA_DATAPREP_NAMESPACE|g" $(pwd)/config/samples/chatQnA_dataprep_gaudi.yaml - # workaround for issue #268 - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/chatQnA_dataprep_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_dataprep_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_DATAPREP_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_DATAPREP_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_DATAPREP_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CHATQNA_DATAPREP_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_DATAPREP_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_DATAPREP_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_DATAPREP_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='chatqa')].status.accessUrl}") - - kubectl exec "$CLIENT_POD" -n $CHATQNA_DATAPREP_NAMESPACE -- curl "$accessUrl/dataprep" -X POST -F 'link_list=["https://raw.githubusercontent.com/opea-project/GenAIInfra/main/microservices-connector/test/data/gaudi.txt"]' -H "Content-Type: multipart/form-data" > $LOG_PATH/curl_dataprep.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "dataprep failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_dataprep.log ]] && \ - [[ $(grep -c "Data preparation succeeded" $LOG_PATH/curl_dataprep.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_dataprep.log ]]; then - cat $LOG_PATH/curl_dataprep.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - kubectl exec "$CLIENT_POD" -n $CHATQNA_DATAPREP_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What are the key features of Intel Gaudi?","parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_dataprep.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_dataprep.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_dataprep.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_dataprep.log ]]; then - cat $LOG_PATH/curl_chatqna_dataprep.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_chatqna_in_switch() { - kubectl create ns $CHATQNA_SWITCH_NAMESPACE - sed -i "s|namespace: switch|namespace: $CHATQNA_SWITCH_NAMESPACE|g" $(pwd)/config/samples/chatQnA_switch_gaudi.yaml - # workaround for issue #268 - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/chatQnA_switch_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_switch_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_SWITCH_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_SWITCH_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_SWITCH_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CHATQNA_SWITCH_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_SWITCH_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_SWITCH_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_SWITCH_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='switch')].status.accessUrl}") - - # test the chatqna with model condition: "model-id":"intel" and "embedding-model-id":"small" - kubectl exec "$CLIENT_POD" -n $CHATQNA_SWITCH_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?", "model-id":"intel", "embedding-model-id":"small", "parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_switch_intel.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_switch_intel.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_switch_intel.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_switch_intel.log ]]; then - cat $LOG_PATH/curl_chatqna_switch_intel.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - # test the chatqna with model condition: "model-id":"llama" and "embedding-model-id":"large" - kubectl exec "$CLIENT_POD" -n $CHATQNA_SWITCH_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?", "model-id":"llama", "embedding-model-id":"large", "parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_switch_llama.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_switch_llama.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_switch_llama.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_switch_llama.log ]]; then - cat $LOG_PATH/curl_chatqna_switch_llama.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_codegen() { - kubectl create ns $CODEGEN_NAMESPACE - sed -i "s|namespace: codegen|namespace: $CODEGEN_NAMESPACE|g" $(pwd)/config/samples/codegen_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/codegen_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the codegen router service to be ready..." - wait_until_pod_ready "codegen router" $CODEGEN_NAMESPACE "router-service" - output=$(kubectl get pods -n $CODEGEN_NAMESPACE) - echo $output - - - # deploy client pod for testing - kubectl create deployment client-test -n $CODEGEN_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # wait for client pod ready - wait_until_pod_ready "client-test" $CODEGEN_NAMESPACE "client-test" - # giving time to populating data - sleep 60 - - kubectl get pods -n $CODEGEN_NAMESPACE - # send request to codegen - export CLIENT_POD=$(kubectl get pod -n $CODEGEN_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CODEGEN_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='codegen')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CODEGEN_NAMESPACE -- curl $accessUrl -X POST -d '{"query": "def print_hello_world():"}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_codegen.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "codegen failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_codegen.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_codegen.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_codegen.log ]]; then - cat $LOG_PATH/gmc_codegen.log - fi - echo "Response check failed, please check the logs in artifacts!" - cat $LOG_PATH/gmc_codegen.log - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_codetrans() { - kubectl create ns $CODETRANS_NAMESPACE - sed -i "s|namespace: codetrans|namespace: $CODETRANS_NAMESPACE|g" $(pwd)/config/samples/codetrans_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/codetrans_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the codetrans router service to be ready..." - wait_until_pod_ready "codetrans router" $CODETRANS_NAMESPACE "router-service" - output=$(kubectl get pods -n $CODETRANS_NAMESPACE) - echo $output - - - # deploy client pod for testing - kubectl create deployment client-test -n $CODETRANS_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # wait for client pod ready - wait_until_pod_ready "client-test" $CODETRANS_NAMESPACE "client-test" - # giving time to populating data - sleep 60 - - kubectl get pods -n $CODETRANS_NAMESPACE - # send request to codetrans - export CLIENT_POD=$(kubectl get pod -n $CODETRANS_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CODETRANS_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='codetrans')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CODETRANS_NAMESPACE -- curl $accessUrl -X POST -d '{"query":" ### System: Please translate the following Golang codes into Python codes. ### Original codes: '\'''\'''\''Golang \npackage main\n\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"Hello, World!\");\n '\'''\'''\'' ### Translated codes:"}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_codetrans.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "codetrans failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_codetrans.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_codetrans.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_codetrans.log ]]; then - cat $LOG_PATH/gmc_codetrans.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_docsum() { - kubectl create ns $DOCSUM_NAMESPACE - sed -i "s|namespace: docsum|namespace: $DOCSUM_NAMESPACE|g" $(pwd)/config/samples/docsum_gaudi.yaml - kubectl apply -f $(pwd)/config/samples/docsum_gaudi.yaml - - # Wait until the router service is ready - echo "Waiting for the docsum router service to be ready..." - wait_until_pod_ready "docsum router" $DOCSUM_NAMESPACE "router-service" - output=$(kubectl get pods -n $DOCSUM_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $DOCSUM_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # wait for client pod ready - wait_until_pod_ready "client-test" $DOCSUM_NAMESPACE "client-test" - # giving time to populating data - sleep 60 - - kubectl get pods -n $DOCSUM_NAMESPACE - # send request to docsum - export CLIENT_POD=$(kubectl get pod -n $DOCSUM_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $DOCSUM_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='docsum')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $DOCSUM_NAMESPACE -- curl $accessUrl -X POST -d '{"query":"Text Embeddings Inference (TEI) is a toolkit for deploying and serving open source text embeddings and sequence classification models. TEI enables high-performance extraction for the most popular models, including FlagEmbedding, Ember, GTE and E5."}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_docsum.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "docsum failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_docsum.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_docsum.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_docsum.log ]]; then - cat $LOG_PATH/gmc_docsum.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -case "$1" in - validate_gmc) - pushd microservices-connector - validate_gmc - popd - ;; - cleanup_apps) - cleanup_apps - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/gmc_install.sh b/.github/workflows/scripts/e2e/gmc_install.sh deleted file mode 100755 index 9427e420..00000000 --- a/.github/workflows/scripts/e2e/gmc_install.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -set -xe - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -source ${DIR}/utils.sh - -USER_ID=$(whoami) -LOG_PATH=/home/$(whoami)/logs -MOUNT_DIR=${KIND_MOUNT_DIR:-"/home/$USER_ID/.cache/huggingface/hub"} -TOKEN_DIR=${KIND_TOKEN_DIR:-"/home/$USER_ID/.cache/huggingface/token"} -IMAGE_REPO=${OPEA_IMAGE_REPO:-""} -VERSION=${VERSION:-"latest"} - -function install_gmc() { - PLATFORM=${1:-"xeon"} - # Make sure you have to use image tag $VERSION for microservice-connector installation - echo "install microservice-connector on $PLATFORM, using repo $IMAGE_REPO and tag $VERSION" - echo "using namespace $SYSTEM_NAMESPACE" - - init_gmc - - kubectl apply -f $(pwd)/config/crd/bases/gmc.opea.io_gmconnectors.yaml - kubectl apply -f $(pwd)/config/rbac/gmc-manager-rbac.yaml - kubectl create configmap gmcyaml -n $SYSTEM_NAMESPACE --from-file $(pwd)/config/manifests - kubectl apply -f $(pwd)/config/manager/gmc-manager.yaml - - # Wait until the gmc controller pod is ready - wait_until_pod_ready "gmc-controller" $SYSTEM_NAMESPACE "gmc-controller" - kubectl get pods -n $SYSTEM_NAMESPACE -} - -function init_gmc() { - # replace tag with for the gmc-router and gmc-manager image - sed -i "s|opea/\(.*\):latest|opea/\1:$VERSION|g" $(pwd)/config/gmcrouter/gmc-router.yaml - sed -i "s|opea/\(.*\):latest|opea/\1:$VERSION|g" $(pwd)/config/manager/gmc-manager.yaml - # replace the pull policy "IfNotPresent" with "Always" - sed -i "s#imagePullPolicy: IfNotPresent#imagePullPolicy: Always#g" $(pwd)/config/gmcrouter/gmc-router.yaml - sed -i "s#imagePullPolicy: IfNotPresent#imagePullPolicy: Always#g" $(pwd)/config/manager/gmc-manager.yaml - - cp $(pwd)/config/gmcrouter/gmc-router.yaml -p $(pwd)/config/manifests/ - - # replace namespace for gmc-router and gmc-manager - sed -i "s|namespace: system|namespace: $SYSTEM_NAMESPACE|g" $(pwd)/config/manager/gmc-manager.yaml - sed -i "s|namespace: system|namespace: $SYSTEM_NAMESPACE|g" $(pwd)/config/rbac/gmc-manager-rbac.yaml - sed -i "s|name: system|name: $SYSTEM_NAMESPACE|g" $(pwd)/config/rbac/gmc-manager-rbac.yaml - - # if SET_VERSION=true then replace latest with VERSION - if [ -n "$SET_VERSION" ]; then - # replace the repository "image: opea/*:latest" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: opea/\(.*\):latest#image: opea/\1:$VERSION#g" {} \; - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/\(.*\):latest#image: \"opea/\1:$VERSION#g" {} \; - fi - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - # find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt/models#path: $MOUNT_DIR#g" {} \; - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt/opea-models#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: opea/*#image: ${IMAGE_REPO}opea/#g" {} \; - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat $TOKEN_DIR)#g" {} \; - # replace the pull policy "IfNotPresent" with "Always" - find . -name '*.yaml' -type f -exec sed -i "s#imagePullPolicy: IfNotPresent#imagePullPolicy: Always#g" {} \; -} - -function cleanup_gmc() { - echo "clean up microservice-connector" - if kubectl get namespace $SYSTEM_NAMESPACE > /dev/null 2>&1; then - echo "Deleting namespace: $SYSTEM_NAMESPACE" - kubectl delete namespace "$SYSTEM_NAMESPACE" - kubectl delete crd gmconnectors.gmc.opea.io || true - kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io validating-webhook-configuration --ignore-not-found - else - echo "Namespace $SYSTEM_NAMESPACE does not exist" - fi -} - -#------MAIN----------- -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi -case "$1" in - install_gmc) - pushd microservices-connector - install_gmc - popd - ;; - cleanup_gmc) - cleanup_gmc - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/gmc_xeon_test.sh b/.github/workflows/scripts/e2e/gmc_xeon_test.sh deleted file mode 100755 index 703b1965..00000000 --- a/.github/workflows/scripts/e2e/gmc_xeon_test.sh +++ /dev/null @@ -1,697 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -set -xe - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" -source ${DIR}/utils.sh - -USER_ID=$(whoami) -LOG_PATH=/home/$(whoami)/logs -AUDIOQA_NAMESPACE="${APP_NAMESPACE}-audioqa" -CHATQNA_NAMESPACE="${APP_NAMESPACE}-chatqna" -CHATQNA_DATAPREP_NAMESPACE="${APP_NAMESPACE}-chatqna-dataprep" -CHATQNA_SWITCH_NAMESPACE="${APP_NAMESPACE}-chatqna-switch" -CODEGEN_NAMESPACE="${APP_NAMESPACE}-codegen" -CODETRANS_NAMESPACE="${APP_NAMESPACE}-codetrans" -DOCSUM_NAMESPACE="${APP_NAMESPACE}-docsum" -DELETE_STEP_NAMESPACE="${APP_NAMESPACE}-delstep" -MODIFY_STEP_NAMESPACE="${APP_NAMESPACE}-modstep" -WEBHOOK_NAMESPACE="${APP_NAMESPACE}-webhook" - -function validate_gmc() { - echo "validate audio-qna" - validate_audioqa - - echo "validate chat-qna" - validate_chatqna - - echo "validate chat-qna with dataprep" - validate_chatqna_with_dataprep - - echo "validate chat-qna in switch mode" - validate_chatqna_in_switch - - echo "validate change graph" - validate_modify_config - validate_remove_step - - # echo "validate codegen" - # validate_codegen - - # echo "validate codetrans" - # validate_codetrans - - # echo "validate docsum" - # validate_docsum - - echo "validate webhook" - validate_webhook - - get_gmc_controller_logs -} - -function validate_webhook() { - kubectl create ns $WEBHOOK_NAMESPACE || echo "namespace $WEBHOOK_NAMESPACE is created." - # validate root node existence - yq ".spec.nodes.node123 = .spec.nodes.root | del(.spec.nodes.root)" config/samples/chatQnA_xeon.yaml > /tmp/webhook-case1.yaml - sed -i "s|namespace: chatqa|namespace: $WEBHOOK_NAMESPACE|g" /tmp/webhook-case1.yaml - output=$(! kubectl apply -f /tmp/webhook-case1.yaml 2>&1) - if ! (echo $output | grep -q "a root node is required"); then - echo "Root node existence validation error message is not found!" - echo $output - exit 1 - fi - - # StepName validation - yq '(.spec.nodes.root.steps[] | select ( .name == "Llm")).name = "xyz"' config/samples/chatQnA_gaudi.yaml > /tmp/webhook-case2.yaml - sed -i "s|namespace: chatqa|namespace: $WEBHOOK_NAMESPACE|g" /tmp/webhook-case2.yaml - output=$(! kubectl apply -f /tmp/webhook-case2.yaml 2>&1) - if ! (echo $output | grep -q "invalid step name"); then - echo "Step name validation error message is not found!" - echo $output - exit 1 - fi - - # nodeName existence - yq '(.spec.nodes.root.steps[] | select ( .name == "Embedding")).nodeName = "node123"' config/samples/chatQnA_switch_xeon.yaml > /tmp/webhook-case3.yaml - sed -i "s|namespace: switch|namespace: $WEBHOOK_NAMESPACE|g" /tmp/webhook-case3.yaml - output=$(! kubectl apply -f /tmp/webhook-case3.yaml 2>&1) - if ! (echo $output | grep -q "node name: node123 in step Embedding does not exist"); then - echo "nodeName existence validation error message is not found!" - echo $output - exit 1 - fi - - # serviceName uniqueness - yq '(.spec.nodes.node1.steps[] | select ( .name == "Embedding")).internalService.serviceName = "tei-embedding-svc-bge15"' config/samples/chatQnA_switch_xeon.yaml > /tmp/webhook-case4.yaml - sed -i "s|namespace: switch|namespace: $WEBHOOK_NAMESPACE|g" /tmp/webhook-case4.yaml - output=$(! kubectl apply -f /tmp/webhook-case4.yaml 2>&1) - if ! (echo $output | grep -q "service name: tei-embedding-svc-bge15 in node node1 already exists"); then - echo "serviceName uniqueness validation error message is not found!" - echo $output - exit 1 - fi - - # clean up cases - rm -f /tmp/webhook-case*.yaml -} - -function cleanup_apps() { - echo "clean up microservice-connector" - # namespaces=("$CHATQNA_NAMESPACE" "$CHATQNA_DATAPREP_NAMESPACE" "$CHATQNA_SWITCH_NAMESPACE" "$CODEGEN_NAMESPACE" "$CODETRANS_NAMESPACE" "$DOCSUM_NAMESPACE") - namespaces=("$AUDIOQA_NAMESPACE" "$CHATQNA_NAMESPACE" "$CHATQNA_DATAPREP_NAMESPACE" "$CHATQNA_SWITCH_NAMESPACE" "$WEBHOOK_NAMESPACE" "$MODIFY_STEP_NAMESPACE" "$DELETE_STEP_NAMESPACE") - for ns in "${namespaces[@]}"; do - if kubectl get namespace $ns > /dev/null 2>&1; then - echo "Deleting namespace: $ns" - kubectl delete namespace "$ns" - else - echo "Namespace $ns does not exist" - fi - done -} - -function validate_audioqa() { - kubectl create ns $AUDIOQA_NAMESPACE - sed -i "s|namespace: audioqa|namespace: $AUDIOQA_NAMESPACE|g" $(pwd)/config/samples/audioQnA_xeon.yaml - kubectl apply -f $(pwd)/config/samples/audioQnA_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the audioqa router service to be ready..." - wait_until_pod_ready "audioqa router" $AUDIOQA_NAMESPACE "router-service" - output=$(kubectl get pods -n $AUDIOQA_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $AUDIOQA_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $AUDIOQA_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $AUDIOQA_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - # expected_ready_pods, expected_external_pods, expected_total_pods - # pods_count-1 is to exclude the client pod in this namespace - check_gmc_status $AUDIOQA_NAMESPACE 'audioqa' $((pods_count-1)) 0 7 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $AUDIOQA_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $AUDIOQA_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $AUDIOQA_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='audioqa')].status.accessUrl}") - byte_str=$(kubectl exec "$CLIENT_POD" -n $AUDIOQA_NAMESPACE -- curl $accessUrl -s -X POST -d '{"byte_str": "UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA", "parameters":{"max_new_tokens":64, "do_sample": true, "streaming":false}}' -H 'Content-Type: application/json' | jq .byte_str) - if [ -z "$byte_str" ]; then - echo "audioqa failed, please check the the!" - exit 1 - fi - echo "Audioqa response check succeed!" - - kubectl delete gmc -n $AUDIOQA_NAMESPACE 'audioqa' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $AUDIOQA_NAMESPACE -} - -function validate_chatqna() { - kubectl create ns $CHATQNA_NAMESPACE - sed -i "s|namespace: chatqa|namespace: $CHATQNA_NAMESPACE|g" $(pwd)/config/samples/chatQnA_xeon.yaml - # workaround for issue #268 - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/chatQnA_xeon.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CHATQNA_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $CHATQNA_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - - # expected_ready_pods, expected_external_pods, expected_total_pods - # pods_count-1 is to exclude the client pod in this namespace - check_gmc_status $CHATQNA_NAMESPACE 'chatqa' $((pods_count-1)) 0 9 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='chatqa')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CHATQNA_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?","parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna.log ]]; then - cat $LOG_PATH/curl_chatqna.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - kubectl delete deployment client-test -n $CHATQNA_NAMESPACE - kubectl delete gmc -n $CHATQNA_NAMESPACE 'chatqa' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $CHATQNA_NAMESPACE -} - -function validate_chatqna_with_dataprep() { - kubectl create ns $CHATQNA_DATAPREP_NAMESPACE - sed -i "s|namespace: chatqa|namespace: $CHATQNA_DATAPREP_NAMESPACE|g" $(pwd)/config/samples/chatQnA_dataprep_xeon.yaml - # workaround for issue #268 - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/chatQnA_dataprep_xeon.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_dataprep_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_DATAPREP_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_DATAPREP_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_DATAPREP_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CHATQNA_DATAPREP_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $CHATQNA_DATAPREP_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - # expected_ready_pods, expected_external_pods, expected_total_pods - # pods_count-1 is to exclude the client pod in this namespace - check_gmc_status $CHATQNA_DATAPREP_NAMESPACE 'chatqa' $((pods_count-1)) 0 10 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_DATAPREP_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_DATAPREP_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_DATAPREP_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='chatqa')].status.accessUrl}") - - kubectl exec "$CLIENT_POD" -n $CHATQNA_DATAPREP_NAMESPACE -- curl "$accessUrl/dataprep" -X POST -F 'link_list=["https://raw.githubusercontent.com/opea-project/GenAIInfra/main/microservices-connector/test/data/gaudi.txt"]' -H "Content-Type: multipart/form-data" > $LOG_PATH/curl_dataprep.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "dataprep failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_dataprep.log ]] && \ - [[ $(grep -c "Data preparation succeeded" $LOG_PATH/curl_dataprep.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_dataprep.log ]]; then - cat $LOG_PATH/curl_dataprep.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - kubectl exec "$CLIENT_POD" -n $CHATQNA_DATAPREP_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What are the key features of Intel Gaudi?","parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_dataprep.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_dataprep.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_dataprep.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_dataprep.log ]]; then - cat $LOG_PATH/curl_chatqna_dataprep.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - kubectl delete deployment client-test -n $CHATQNA_DATAPREP_NAMESPACE - kubectl delete gmc -n $CHATQNA_DATAPREP_NAMESPACE 'chatqa' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $CHATQNA_DATAPREP_NAMESPACE -} - -function validate_chatqna_in_switch() { - kubectl create ns $CHATQNA_SWITCH_NAMESPACE - sed -i "s|namespace: switch|namespace: $CHATQNA_SWITCH_NAMESPACE|g" $(pwd)/config/samples/chatQnA_switch_xeon.yaml - # workaround for issue #268 - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/chatQnA_switch_xeon.yaml - kubectl apply -f $(pwd)/config/samples/chatQnA_switch_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the chatqa router service to be ready..." - wait_until_pod_ready "chatqna router" $CHATQNA_SWITCH_NAMESPACE "router-service" - output=$(kubectl get pods -n $CHATQNA_SWITCH_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CHATQNA_SWITCH_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CHATQNA_SWITCH_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $CHATQNA_SWITCH_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - # expected_ready_pods, expected_external_pods, expected_total_pods - # pods_count-1 is to exclude the client pod in this namespace - check_gmc_status $CHATQNA_SWITCH_NAMESPACE 'switch' $((pods_count-1)) 0 15 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - - # giving time to populating data - sleep 90 - - kubectl get pods -n $CHATQNA_SWITCH_NAMESPACE - # send request to chatqnA - export CLIENT_POD=$(kubectl get pod -n $CHATQNA_SWITCH_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CHATQNA_SWITCH_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='switch')].status.accessUrl}") - - # test the chatqna with model condition: "model-id":"intel" and "embedding-model-id":"small" - kubectl exec "$CLIENT_POD" -n $CHATQNA_SWITCH_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?", "model-id":"intel", "embedding-model-id":"small", "parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_switch_intel.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_switch_intel.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_switch_intel.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_switch_intel.log ]]; then - cat $LOG_PATH/curl_chatqna_switch_intel.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - # test the chatqna with model condition: "model-id":"llama" and "embedding-model-id":"large" - kubectl exec "$CLIENT_POD" -n $CHATQNA_SWITCH_NAMESPACE -- curl $accessUrl -X POST -d '{"text":"What is the revenue of Nike in 2023?", "model-id":"llama", "embedding-model-id":"large", "parameters":{"max_new_tokens":17, "do_sample": true}}' -H 'Content-Type: application/json' > $LOG_PATH/curl_chatqna_switch_llama.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna_switch_llama.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/curl_chatqna_switch_llama.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/curl_chatqna_switch_llama.log ]]; then - cat $LOG_PATH/curl_chatqna_switch_llama.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi - - kubectl delete deployment client-test -n $CHATQNA_SWITCH_NAMESPACE - kubectl delete gmc -n $CHATQNA_SWITCH_NAMESPACE 'switch' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $CHATQNA_SWITCH_NAMESPACE -} - - -function validate_modify_config() { - kubectl create ns $MODIFY_STEP_NAMESPACE - cp $(pwd)/config/samples/codegen_xeon.yaml $(pwd)/config/samples/codegen_xeon_mod.yaml - sed -i "s|namespace: codegen|namespace: $MODIFY_STEP_NAMESPACE|g" $(pwd)/config/samples/codegen_xeon_mod.yaml - kubectl apply -f $(pwd)/config/samples/codegen_xeon_mod.yaml - - # Wait until the router service is ready - echo "Waiting for the router service to be ready..." - wait_until_pod_ready "router" $MODIFY_STEP_NAMESPACE "router-service" - output=$(kubectl get pods -n $MODIFY_STEP_NAMESPACE) - echo $output - - # Wait until all pods are ready - wait_until_all_pod_ready $MODIFY_STEP_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $MODIFY_STEP_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - check_gmc_status $MODIFY_STEP_NAMESPACE 'codegen' $pods_count 0 3 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - #change the model id of the step named "Tgi" in the codegen_xeon_mod.yaml - yq -i '(.spec.nodes.root.steps[] | select ( .name == "Tgi")).internalService.config.MODEL_ID = "bigscience/bloom-560m"' $(pwd)/config/samples/codegen_xeon_mod.yaml - kubectl apply -f $(pwd)/config/samples/codegen_xeon_mod.yaml - - pods_count=$(kubectl get pods -n $MODIFY_STEP_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - check_gmc_status $MODIFY_STEP_NAMESPACE 'codegen' $pods_count 0 3 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - #revert the codegen yaml - sed -i "s|namespace: $MODIFY_STEP_NAMESPACE|namespace: codegen|g" $(pwd)/config/samples/codegen_xeon_mod.yaml - kubectl delete gmc -n $MODIFY_STEP_NAMESPACE 'codegen' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $MODIFY_STEP_NAMESPACE -} - -function validate_remove_step() { - kubectl create ns $DELETE_STEP_NAMESPACE - cp $(pwd)/config/samples/codegen_xeon.yaml $(pwd)/config/samples/codegen_xeon_del.yaml - sed -i "s|namespace: codegen|namespace: $DELETE_STEP_NAMESPACE|g" $(pwd)/config/samples/codegen_xeon_del.yaml - kubectl apply -f $(pwd)/config/samples/codegen_xeon_del.yaml - - # Wait until the router service is ready - echo "Waiting for the router service to be ready..." - wait_until_pod_ready "router" $DELETE_STEP_NAMESPACE "router-service" - output=$(kubectl get pods -n $DELETE_STEP_NAMESPACE) - echo $output - - # Wait until all pods are ready - wait_until_all_pod_ready $DELETE_STEP_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - pods_count=$(kubectl get pods -n $DELETE_STEP_NAMESPACE -o jsonpath='{.items[*].metadata.name}' | wc -w) - check_gmc_status $DELETE_STEP_NAMESPACE 'codegen' $pods_count 0 3 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - # remove the step named "llm" in the codegen_xeon.yaml - yq -i 'del(.spec.nodes.root.steps[] | select ( .name == "Llm"))' $(pwd)/config/samples/codegen_xeon_del.yaml - kubectl apply -f $(pwd)/config/samples/codegen_xeon_del.yaml - - sleep 10 - check_pod_terminated $DELETE_STEP_NAMESPACE - - check_gmc_status $DELETE_STEP_NAMESPACE 'codegen' 2 0 2 - if [ $? -ne 0 ]; then - echo "GMC status is not as expected" - exit 1 - fi - - #revert the codegen yaml - sed -i "s|namespace: $MODIFY_STEP_NAMESPACE|namespace: codegen|g" $(pwd)/config/samples/codegen_xeon_del.yaml - kubectl delete gmc -n $DELETE_STEP_NAMESPACE 'codegen' - echo "sleep 10s for cleaning up" - sleep 10 - check_resource_cleared $DELETE_STEP_NAMESPACE -} - -function validate_codegen() { - kubectl create ns $CODEGEN_NAMESPACE - sed -i "s|namespace: codegen|namespace: $CODEGEN_NAMESPACE|g" $(pwd)/config/samples/codegen_xeon.yaml - kubectl apply -f $(pwd)/config/samples/codegen_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the codegen router service to be ready..." - wait_until_pod_ready "codegen router" $CODEGEN_NAMESPACE "router-service" - output=$(kubectl get pods -n $CODEGEN_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CODEGEN_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CODEGEN_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 60 - - kubectl get pods -n $CODEGEN_NAMESPACE - # send request to codegen - export CLIENT_POD=$(kubectl get pod -n $CODEGEN_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CODEGEN_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='codegen')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CODEGEN_NAMESPACE -- curl $accessUrl -X POST -d '{"query": "def print_hello_world():"}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_codegen.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "codegen failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_codegen.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_codegen.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_codegen.log ]]; then - cat $LOG_PATH/gmc_codegen.log - fi - echo "Response check failed, please check the logs in artifacts!" - cat $LOG_PATH/gmc_codegen.log - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_codetrans() { - kubectl create ns $CODETRANS_NAMESPACE - sed -i "s|namespace: codetrans|namespace: $CODETRANS_NAMESPACE|g" $(pwd)/config/samples/codetrans_xeon.yaml - kubectl apply -f $(pwd)/config/samples/codetrans_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the codetrans router service to be ready..." - wait_until_pod_ready "codetrans router" $CODETRANS_NAMESPACE "router-service" - output=$(kubectl get pods -n $CODETRANS_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $CODETRANS_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $CODETRANS_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 60 - - kubectl get pods -n $CODETRANS_NAMESPACE - # send request to codetrans - export CLIENT_POD=$(kubectl get pod -n $CODETRANS_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $CODETRANS_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='codetrans')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $CODETRANS_NAMESPACE -- curl $accessUrl -X POST -d '{"query":" ### System: Please translate the following Golang codes into Python codes. ### Original codes: '\'''\'''\''Golang \npackage main\n\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"Hello, World!\");\n '\'''\'''\'' ### Translated codes:"}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_codetrans.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "codetrans failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_codetrans.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_codetrans.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_codetrans.log ]]; then - cat $LOG_PATH/gmc_codetrans.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -function validate_docsum() { - kubectl create ns $DOCSUM_NAMESPACE - sed -i "s|namespace: docsum|namespace: $DOCSUM_NAMESPACE|g" $(pwd)/config/samples/docsum_xeon.yaml - kubectl apply -f $(pwd)/config/samples/docsum_xeon.yaml - - # Wait until the router service is ready - echo "Waiting for the docsum router service to be ready..." - wait_until_pod_ready "docsum router" $DOCSUM_NAMESPACE "router-service" - output=$(kubectl get pods -n $DOCSUM_NAMESPACE) - echo $output - - # deploy client pod for testing - kubectl create deployment client-test -n $DOCSUM_NAMESPACE --image=python:3.8.13 -- sleep infinity - - # Wait until all pods are ready - wait_until_all_pod_ready $DOCSUM_NAMESPACE 300s - if [ $? -ne 0 ]; then - echo "Error Some pods are not ready!" - exit 1 - fi - - # giving time to populating data - sleep 60 - - kubectl get pods -n $DOCSUM_NAMESPACE - # send request to docsum - export CLIENT_POD=$(kubectl get pod -n $DOCSUM_NAMESPACE -l app=client-test -o jsonpath={.items..metadata.name}) - echo "$CLIENT_POD" - accessUrl=$(kubectl get gmc -n $DOCSUM_NAMESPACE -o jsonpath="{.items[?(@.metadata.name=='docsum')].status.accessUrl}") - kubectl exec "$CLIENT_POD" -n $DOCSUM_NAMESPACE -- curl $accessUrl -X POST -d '{"query":"Text Embeddings Inference (TEI) is a toolkit for deploying and serving open source text embeddings and sequence classification models. TEI enables high-performance extraction for the most popular models, including FlagEmbedding, Ember, GTE and E5."}' -H 'Content-Type: application/json' > $LOG_PATH/gmc_docsum.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "docsum failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/gmc_docsum.log ]] && \ - [[ $(grep -c "[DONE]" $LOG_PATH/gmc_docsum.log) != 0 ]]; then - status=true - fi - if [ $status == false ]; then - if [[ -f $LOG_PATH/gmc_docsum.log ]]; then - cat $LOG_PATH/gmc_docsum.log - fi - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -case "$1" in - validate_gmc) - pushd microservices-connector - validate_gmc - popd - ;; - cleanup_apps) - cleanup_apps - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/manifest_gaudi_test.sh b/.github/workflows/scripts/e2e/manifest_gaudi_test.sh deleted file mode 100755 index 40cadb88..00000000 --- a/.github/workflows/scripts/e2e/manifest_gaudi_test.sh +++ /dev/null @@ -1,278 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -set -xe -USER_ID=$(whoami) -LOG_PATH=/home/$(whoami)/logs -MOUNT_DIR=/home/$USER_ID/.cache/huggingface/hub -# IMAGE_REPO is $OPEA_IMAGE_REPO, or else "" -IMAGE_REPO=${OPEA_IMAGE_REPO:-""} - -function init_docsum() { - # executed under path manifest/docsum/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function init_codetrans() { - # executed under path manifest/codetrans/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function init_codegen() { - # executed under path manifest/codegen/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function install_docsum { - echo "namespace is $NAMESPACE" - find . -name 'qna_configmap_gaudi.yaml' -type f -exec sed -i "s#default#${NAMESPACE}#g" {} \; - kubectl apply -f qna_configmap_gaudi.yaml -n $NAMESPACE - kubectl apply -f docsum_gaudi_llm.yaml -n $NAMESPACE - kubectl apply -f tgi_gaudi_service.yaml -n $NAMESPACE -} - -function install_codetrans { - echo "namespace is $NAMESPACE" - kubectl apply -f . -n $NAMESPACE -} - -function install_codegen { - echo "namespace is $NAMESPACE" - kubectl apply -f . -n $NAMESPACE -} - -function init_chatqna() { - # executed under path manifest/chatqna/xeon - # replace the mount dir "path: /mnt" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt/models#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: opea/*#image: ${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function install_chatqna { - # replace namespace "default" with real namespace - find . -name '*.yaml' -type f -exec sed -i "s#default.svc#$NAMESPACE.svc#g" {} \; - # for very yaml file in yaml_files, apply it to the k8s cluster - yaml_files=("qna_configmap_gaudi" "redis-vector-db" "tei_embedding_gaudi_service" "tei_reranking_service" "tgi_gaudi_service" "retriever" "embedding" "reranking" "llm") - for yaml_file in ${yaml_files[@]}; do - kubectl apply -f $yaml_file.yaml -n $NAMESPACE - done - sleep 60 - kubectl apply -f chaqna-xeon-backend-server.yaml -n $NAMESPACE -} - -function validate_docsum() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/chat/docsum..." - # Curl the DocSum LLM Service - curl http://${ip_address}:${port}/v1/chat/docsum \ - -X POST \ - -d '{"query":"Text Embeddings Inference (TEI) is a toolkit for deploying and serving open source text embeddings and sequence classification models. TEI enables high-performance extraction for the most popular models, including FlagEmbedding, Ember, GTE and E5."}' \ - -H 'Content-Type: application/json' > $LOG_PATH/curl_docsum.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "LLM for docsum failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_docsum.log ]] && \ - [[ $(grep -c "TEI" $LOG_PATH/curl_docsum.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_codetrans() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/chat/completions..." - # Curl the CodeTrans LLM Service - curl http://${ip_address}:${port}/v1/chat/completions \ - -X POST \ - -d '{"query":" ### System: Please translate the following Golang codes into Python codes. ### Original codes: '\'''\'''\''Golang \npackage main\n\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"Hello, World!\");\n '\'''\'''\'' ### Translated codes:"}' \ - -H 'Content-Type: application/json' > $LOG_PATH/curl_codetrans.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "LLM for codetrans failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_codetrans.log ]] && \ - [[ $(grep -c "Hello" $LOG_PATH/curl_codetrans.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_codegen() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/codegen..." - # Curl the Mega Service - curl http://${ip_address}:${port}/v1/codegen \ - -H "Content-Type: application/json" \ - -d '{"messages": "Implement a high-level API for a TODO list application. The API takes as input an operation request and updates the TODO list in place. If the request is invalid, raise an exception."}' > $LOG_PATH/curl_codegen.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Megaservice codegen failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_codegen.log ]] && \ - [[ $(grep -c "print" $LOG_PATH/curl_codegen.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_chatqna() { - # make sure microservice retriever is ready - test_embedding=$(python3 -c "import random; embedding = [random.uniform(-1, 1) for _ in range(768)]; print(embedding)") - until curl http://retriever-svc.$NAMESPACE:7000/v1/retrieval -X POST \ - -d '{"text":"What is the revenue of Nike in 2023?","embedding":"'"${test_embedding}"'"}' \ - -H 'Content-Type: application/json'; do sleep 10; done - - # make sure microservice tgi-svc is ready - until curl http://tgi-gaudi-svc.$NAMESPACE:9009/generate -X POST \ - -d '{"inputs":"What is Deep Learning?","parameters":{"max_new_tokens":17, "do_sample": true}}' \ - -H 'Content-Type: application/json'; do sleep 10; done - - # Curl the Mega Service - curl http://chaqna-xeon-backend-server-svc.$NAMESPACE:8888/v1/chatqna -H "Content-Type: application/json" \ - -d '{ "messages": "What is the revenue of Nike in 2023?" }' > $LOG_PATH/curl_chatqna.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Megaservice chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna.log ]] && \ - [[ $(grep -c "billion" $LOG_PATH/curl_chatqna.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -case "$1" in - init_docsum) - cp manifests/ChatQnA/qna_configmap_gaudi.yaml manifests/DocSum/gaudi/ - pushd manifests/DocSum/gaudi - init_docsum - popd - ;; - init_codetrans) - pushd manifests/CodeTrans/gaudi - init_codetrans - popd - ;; - init_codegen) - pushd manifests/CodeGen/gaudi - init_codegen - popd - ;; - init_chatqna) - pushd manifests/ChatQnA - init_chatqna - popd - ;; - install_docsum) - pushd manifests/DocSum/gaudi - NAMESPACE=$2 - install_docsum - popd - ;; - install_codetrans) - pushd manifests/CodeTrans/gaudi - NAMESPACE=$2 - install_codetrans - popd - ;; - install_codegen) - pushd manifests/CodeGen/gaudi - NAMESPACE=$2 - install_codegen - popd - ;; - install_chatqna) - pushd manifests/ChatQnA - NAMESPACE=$2 - install_chatqna - popd - ;; - validate_docsum) - NAMESPACE=$2 - SERVICE_NAME=docsum-llm-uservice - # validate_docsum - ;; - validate_codetrans) - NAMESPACE=$2 - SERVICE_NAME=codetrans-llm-uservice - validate_codetrans - ;; - validate_codegen) - NAMESPACE=$2 - SERVICE_NAME=codegen - validate_codegen - ;; - validate_chatqna) - NAMESPACE=$2 - SERVICE_NAME=chaqna-xeon-backend-server-svc - validate_chatqna - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/manifest_xeon_test.sh b/.github/workflows/scripts/e2e/manifest_xeon_test.sh deleted file mode 100755 index 78d9f3ce..00000000 --- a/.github/workflows/scripts/e2e/manifest_xeon_test.sh +++ /dev/null @@ -1,278 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -set -xe -USER_ID=$(whoami) -LOG_PATH=/home/$(whoami)/logs -MOUNT_DIR=/home/$USER_ID/.cache/huggingface/hub -# IMAGE_REPO is $OPEA_IMAGE_REPO, or else "" -IMAGE_REPO=${OPEA_IMAGE_REPO:-""} - -function init_docsum() { - # executed under path manifest/docsum/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function init_codetrans() { - # executed under path manifest/codetrans/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function init_codegen() { - # executed under path manifest/codegen/xeon - # replace the mount dir "path: /mnt/model" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: \"opea/*#image: \"${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function install_docsum { - echo "namespace is $NAMESPACE" - find . -name 'qna_configmap_xeon.yaml' -type f -exec sed -i "s#default#${NAMESPACE}#g" {} \; - kubectl apply -f qna_configmap_xeon.yaml -n $NAMESPACE - kubectl apply -f docsum_llm.yaml -n $NAMESPACE - kubectl apply -f tgi_service.yaml -n $NAMESPACE -} - -function install_codetrans { - echo "namespace is $NAMESPACE" - kubectl apply -f . -n $NAMESPACE -} - -function install_codegen { - echo "namespace is $NAMESPACE" - kubectl apply -f . -n $NAMESPACE -} - -function init_chatqna() { - # executed under path manifest/chatqna/xeon - # replace the mount dir "path: /mnt" with "path: $CHART_MOUNT" - find . -name '*.yaml' -type f -exec sed -i "s#path: /mnt/models#path: $MOUNT_DIR#g" {} \; - # replace the repository "image: opea/*" with "image: ${IMAGE_REPO}opea/" - find . -name '*.yaml' -type f -exec sed -i "s#image: opea/*#image: ${IMAGE_REPO}opea/#g" {} \; - # set huggingface token - find . -name '*.yaml' -type f -exec sed -i "s#insert-your-huggingface-token-here#$(cat /home/$USER_ID/.cache/huggingface/token)#g" {} \; -} - -function install_chatqna { - # replace namespace "default" with real namespace - find . -name '*.yaml' -type f -exec sed -i "s#default.svc#$NAMESPACE.svc#g" {} \; - # for very yaml file in yaml_files, apply it to the k8s cluster - yaml_files=("qna_configmap_xeon" "redis-vector-db" "tei_embedding_service" "tei_reranking_service" "tgi_service" "retriever" "embedding" "reranking" "llm") - for yaml_file in ${yaml_files[@]}; do - kubectl apply -f $yaml_file.yaml -n $NAMESPACE - done - sleep 60 - kubectl apply -f chaqna-xeon-backend-server.yaml -n $NAMESPACE -} - -function validate_docsum() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/chat/docsum..." - # Curl the DocSum LLM Service - curl http://${ip_address}:${port}/v1/chat/docsum \ - -X POST \ - -d '{"query":"Text Embeddings Inference (TEI) is a toolkit for deploying and serving open source text embeddings and sequence classification models. TEI enables high-performance extraction for the most popular models, including FlagEmbedding, Ember, GTE and E5."}' \ - -H 'Content-Type: application/json' > $LOG_PATH/curl_docsum.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "LLM for docsum failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_docsum.log ]] && \ - [[ $(grep -c "TEI" $LOG_PATH/curl_docsum.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_codetrans() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/chat/completions..." - # Curl the CodeTrans LLM Service - curl http://${ip_address}:${port}/v1/chat/completions \ - -X POST \ - -d '{"query":" ### System: Please translate the following Golang codes into Python codes. ### Original codes: '\'''\'''\''Golang \npackage main\n\nimport \"fmt\"\nfunc main() {\n fmt.Println(\"Hello, World!\");\n '\'''\'''\'' ### Translated codes:"}' \ - -H 'Content-Type: application/json' > $LOG_PATH/curl_codetrans.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "LLM for codetrans failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_codetrans.log ]] && \ - [[ $(grep -c "Hello" $LOG_PATH/curl_codetrans.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_codegen() { - ip_address=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.clusterIP}') - port=$(kubectl get svc $SERVICE_NAME -n $NAMESPACE -o jsonpath='{.spec.ports[0].port}') - echo "try to curl http://${ip_address}:${port}/v1/codegen..." - # Curl the Mega Service - curl http://${ip_address}:${port}/v1/codegen \ - -H "Content-Type: application/json" \ - -d '{"messages": "Implement a high-level API for a TODO list application. The API takes as input an operation request and updates the TODO list in place. If the request is invalid, raise an exception."}' > $LOG_PATH/curl_codegen.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Megaservice codegen failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_codegen.log ]] && \ - [[ $(grep -c "print" $LOG_PATH/curl_codegen.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - else - echo "Response check succeed!" - fi -} - -function validate_chatqna() { - # make sure microservice retriever is ready - test_embedding=$(python3 -c "import random; embedding = [random.uniform(-1, 1) for _ in range(768)]; print(embedding)") - until curl http://retriever-svc.$NAMESPACE:7000/v1/retrieval -X POST \ - -d '{"text":"What is the revenue of Nike in 2023?","embedding":"'"${test_embedding}"'"}' \ - -H 'Content-Type: application/json'; do sleep 10; done - - # make sure microservice tgi-svc is ready - until curl http://tgi-svc.$NAMESPACE:9009/generate -X POST \ - -d '{"inputs":"What is Deep Learning?","parameters":{"max_new_tokens":17, "do_sample": true}}' \ - -H 'Content-Type: application/json'; do sleep 10; done - - # Curl the Mega Service - curl http://chaqna-xeon-backend-server-svc.$NAMESPACE:8888/v1/chatqna -H "Content-Type: application/json" \ - -d '{ "messages": "What is the revenue of Nike in 2023?" }' > $LOG_PATH/curl_chatqna.log - exit_code=$? - if [ $exit_code -ne 0 ]; then - echo "Megaservice chatqna failed, please check the logs in ${LOG_PATH}!" - exit 1 - fi - - echo "Checking response results, make sure the output is reasonable. " - local status=false - if [[ -f $LOG_PATH/curl_chatqna.log ]] && \ - [[ $(grep -c "billion" $LOG_PATH/curl_chatqna.log) != 0 ]]; then - status=true - fi - - if [ $status == false ]; then - echo "Response check failed, please check the logs in artifacts!" - exit 1 - else - echo "Response check succeed!" - fi -} - -if [ $# -eq 0 ]; then - echo "Usage: $0 " - exit 1 -fi - -case "$1" in - init_docsum) - cp manifests/ChatQnA/qna_configmap_xeon.yaml manifests/DocSum/xeon/ - pushd manifests/DocSum/xeon - init_docsum - popd - ;; - init_codetrans) - pushd manifests/CodeTrans/xeon - init_codetrans - popd - ;; - init_codegen) - pushd manifests/CodeGen/xeon - init_codegen - popd - ;; - init_chatqna) - pushd manifests/ChatQnA - init_chatqna - popd - ;; - install_docsum) - pushd manifests/DocSum/xeon - NAMESPACE=$2 - install_docsum - popd - ;; - install_codetrans) - pushd manifests/CodeTrans/xeon - NAMESPACE=$2 - install_codetrans - popd - ;; - install_codegen) - pushd manifests/CodeGen/xeon - NAMESPACE=$2 - install_codegen - popd - ;; - install_chatqna) - pushd manifests/ChatQnA - NAMESPACE=$2 - install_chatqna - popd - ;; - validate_docsum) - NAMESPACE=$2 - SERVICE_NAME=docsum-llm-uservice - # validate_docsum - ;; - validate_codetrans) - NAMESPACE=$2 - SERVICE_NAME=codetrans-llm-uservice - validate_codetrans - ;; - validate_codegen) - NAMESPACE=$2 - SERVICE_NAME=codegen - validate_codegen - ;; - validate_chatqna) - NAMESPACE=$2 - SERVICE_NAME=chaqna-xeon-backend-server-svc - validate_chatqna - ;; - *) - echo "Unknown function: $1" - ;; -esac diff --git a/.github/workflows/scripts/e2e/utils.sh b/.github/workflows/scripts/e2e/utils.sh deleted file mode 100755 index f78834eb..00000000 --- a/.github/workflows/scripts/e2e/utils.sh +++ /dev/null @@ -1,137 +0,0 @@ -#!/bin/bash -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -function wait_until_pod_ready() { - echo "Waiting for the $1 to be ready..." - max_retries=60 - retry_count=0 - while ! is_pod_ready $2 $3; do - if [ $retry_count -ge $max_retries ]; then - echo "$1 is not ready after waiting for a significant amount of time" - get_gmc_controller_logs - exit 1 - fi - echo "$1 is not ready yet. Retrying in 30 seconds..." - sleep 15 - output=$(kubectl get pods -n $2) - echo $output - retry_count=$((retry_count + 1)) - done -} - -function is_pod_ready() { - if [ "$2" == "gmc-controller" ]; then - pod_status=$(kubectl get pods -n $1 -o jsonpath='{.items[].status.conditions[?(@.type=="Ready")].status}') - else - pod_status=$(kubectl get pods -n $1 -l app=$2 -o jsonpath='{.items[].status.conditions[?(@.type=="Ready")].status}') - fi - if [ "$pod_status" == "True" ]; then - return 0 - else - return 1 - fi -} - -function get_gmc_controller_logs() { - # Fetch the name of the pod with the app-name gmc-controller in the specified namespace - pod_name=$(kubectl get pods -n $SYSTEM_NAMESPACE -l control-plane=gmc-controller -o jsonpath='{.items[0].metadata.name}') - - # Check if the pod name was found - if [ -z "$pod_name" ]; then - echo "No pod found with app-name gmc-controller in namespace $SYSTEM_NAMESPACE" - return 1 - fi - - # Get the logs of the found pod - echo "Fetching logs for pod $pod_name in namespace $SYSTEM_NAMESPACE..." - kubectl logs $pod_name -n $SYSTEM_NAMESPACE -} - - -function wait_until_all_pod_ready() { - namespace=$1 - timeout=$2 - - echo "Wait for all pods in NS $namespace to be ready..." - pods=$(kubectl get pods -n $namespace --no-headers -o custom-columns=":metadata.name") - # Loop through each pod - echo "$pods" | while read -r line; do - pod_name=$line - kubectl wait --for=condition=Ready pod/${pod_name} -n $namespace --timeout=${timeout} - if [ $? -ne 0 ]; then - echo "Pod $pod_name is not ready after waiting for ${timeout}" - echo "Pod $pod_name status:" - kubectl describe pod $pod_name -n $namespace - echo "Pod $pod_name logs:" - kubectl logs $pod_name -n $namespace - exit 1 - fi - done -} - -function check_gmc_status() { - namespace=$1 - gmc_name=$2 - expected_ready_pods=$3 - expected_external_pods=$4 - expected_total_pods=$5 - - # pods*3 is because 1 pod has 1 configmap + 1 deployment + 1 service - # minus 1 is because router and redis don't have the configmap - expected_total_records=$((3* $3 - 2)) - - if [ $((expected_ready_pods + expected_external_pods)) -ne $expected_total_pods ]; then - return 1 - fi - - gmc_status=$(kubectl get gmc -n $namespace -o jsonpath="{.items[?(@.metadata.name=='$gmc_name')].status.status}") - echo $gmc_status - if [[ "$gmc_status" == "$expected_ready_pods/$expected_external_pods/$expected_total_pods" ]]; then - return 0 - else - return 1 - fi - annotation=$(kubectl get gmc -n $namespace -o json | jq ".items[] | select(.metadata.name==\"$gmc_name\") | .status.annotations | length") - echo $annotation - if [ $annotation -eq $expected_total_records ]; then - return 0 - else - return 1 - fi -} - -function check_resource_cleared() { - namespace=$1 - - actual_count=$(kubectl get all -n $namespace --no-headers | wc -l) - if [ $actual_count -eq 0 ]; then - return 0 - else - #check every line of kubectl get all status is Terminating - remaining=$(kubectl get pods -n $namespace --no-headers) - echo $remaining - status=$(echo $remaining | awk '{print $3}') - for i in $status; do - if [[ "$i" != "Terminating" ]]; then - return 1 - fi - done - return 0 - fi -} - -function check_pod_terminated() { - namespace=$1 - - #check every line of kubectl get all status is Terminating - remaining=$(kubectl get pods -n $namespace --no-headers) - echo $remaining - status=$(echo $remaining | awk '{print $3}') - for i in $status; do - if [[ "$i" == "Terminating" ]]; then - return 0 - fi - done - return 1 -} diff --git a/.github/workflows/scripts/go-coverage.sh b/.github/workflows/scripts/go-coverage.sh deleted file mode 100755 index 758cf7de..00000000 --- a/.github/workflows/scripts/go-coverage.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/sh -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -cat > coverge-test-ignore << EndOfMessage -zz_generated.deepcopy.go -openapi_generated.go -testing -tests -test -EndOfMessage - -PASSPERCENT=20 - -while read p || [ -n "$p" ] -do - sed -i "/${p}/d" ./coverage.out -done < coverge-test-ignore - -# get the total coverage percentage number -COVPERCENT=$(go tool cover -func ./coverage.out | grep total | awk '{print $3}') -# remove the % sign -COVPERCENT=${COVPERCENT%\%} -echo "Coverage: $COVPERCENT%" - -# if coverage is less than $PASSPERCENT then exit with error -if [ $(echo "$COVPERCENT < $PASSPERCENT" | bc) -eq 1 ]; then - echo "Coverage is less than $PASSPERCENT%. Failed!" - exit 1 -fi - -echo "Coverage is bigger than $PASSPERCENT%. Pass!" diff --git a/.github/workflows/scripts/test_ut.sh b/.github/workflows/scripts/test_ut.sh deleted file mode 100644 index 169a1a89..00000000 --- a/.github/workflows/scripts/test_ut.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -# Copyright (C) 2024 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -test_name=$1 -# run test -ut_log_name=/GenAIComps/.github/workflows/scripts/${test_name}_ut.log -cd /GenAIComps/tests -if [ $test_name = 'mega' ]; then - echo "run mega test" - cd mega - find . -name "test*.py" | sed 's,\.\/,python -m pytest -vs --disable-warnings ,g' > run.sh - bash run.sh 2>&1 | tee ${ut_log_name} -else - echo "run other test" - python -m pytest -vs --disable-warnings ./test_${test_name}*.py 2>&1 | tee ${ut_log_name} -fi - -# clean the pytest cache -rm -rf /GenAIComps/.pytest_cache - -# check test result -if [ $(grep -c '== FAILURES ==' ${ut_log_name}) != 0 ] || [ $(grep -c '== ERRORS ==' ${ut_log_name}) != 0 ] || [ $(grep -c ' passed' ${ut_log_name}) == 0 ]; then - echo "Find errors in pytest case, please check the output..." - echo "Please search for '== FAILURES ==' or '== ERRORS =='" - exit 1 -fi -echo "UT finished successfully! " diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..c7cea578 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,108 @@ +ci: + autofix_prs: true + autoupdate_schedule: quarterly + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + files: (.*\.(py|md|rst|yaml|yml|json|ts|js|html|svelte|sh))$ + - id: check-json + - id: debug-statements + - id: requirements-txt-fixer + - id: trailing-whitespace + files: (.*\.(py|rst|cmake|yaml|yml|json|ts|js|html|svelte|sh))$ + + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: insert-license + files: (.*\.(py|yaml|yml|sh))$ + args: + [ + --license-filepath=.github/license_template.txt, + --use-current-year, + --detect-license-in-X-top-lines=40, + --skip-license-insertion-comment=Copyright, + ] + - id: insert-license + files: (.*\.(ts|js))$ + args: + [ + --license-filepath=.github/license_template.txt, + --use-current-year, + --detect-license-in-X-top-lines=40, + --skip-license-insertion-comment=Copyright, + --comment-style=//, + ] + - id: insert-license + files: (.*\.(html|svelte))$ + args: + [ + --license-filepath=.github/license_template.txt, + --use-current-year, + --detect-license-in-X-top-lines=40, + --skip-license-insertion-comment=Copyright, + --comment-style=, + ] + + - repo: https://github.com/asottile/yesqa + rev: v1.5.0 + hooks: + - id: yesqa + name: Unused noqa + + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.5 + hooks: + - id: docformatter + args: [ + --in-place, + --wrap-summaries=0, # 0 means disable wrap + --wrap-descriptions=0, # 0 means disable wrap + --black, + --style=google, + ] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v4.0.0-alpha.8" # Use the sha / tag you want to point at + hooks: + - id: prettier + args: [--print-width=120] + types_or: [markdown, html, css, scss, javascript, json, ts, shell, sh] + additional_dependencies: + - prettier@3.2.5 + + - repo: https://github.com/psf/black.git + rev: 24.3.0 + hooks: + - id: black + files: (.*\.py)$ + + - repo: https://github.com/asottile/blacken-docs + rev: 1.16.0 + hooks: + - id: blacken-docs + args: [--line-length=120, --skip-errors] + additional_dependencies: + - black==24.3.0 + + - repo: https://github.com/codespell-project/codespell + rev: v2.2.6 + hooks: + - id: codespell + args: [-w] + additional_dependencies: + - tomli + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix, --no-cache] diff --git a/README.md b/README.md index d0f5415b..c7c8060a 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,8 @@ helm repo add opea https://opea-project.github.io/GenAIInfra You can then run `helm search repo opea` to see the charts. - If you had already added this repo earlier, run `helm repo update` to retrieve -the latest versions of the packages. You can then run `helm search repo +the latest versions of the packages. You can then run `helm search repo opea` to see the charts. To install the chart: diff --git a/asr-0.9.0.tgz b/asr-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b4e799a1383baf161834e7403d9eefc343c5d927 GIT binary patch literal 5132 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 DtTM}K literal 0 HcmV?d00001 diff --git a/codegen-0.9.0.tgz b/codegen-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..53958928c1ef2296c0e1665504ddf9e541fafdbd GIT binary patch literal 8428 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
    fKk%x13;RU(|rG>58|*)ObNp7TgH z0Z)hkyW<)x_PDI`l(KtgjO@ihhgiO``8Fa&N9WECnJ>QX0VBCgdFw2{!*pc|Ry3(x z&@k9a0u$zQMeac6bNY%LO>!5z%P-tdR?_fHAJ5sfbNP^sTHT(O>}eVgfrh2&7fNy`O={(E@u*xw#GKjYL&B?R8sr0e zl`n7I2w?NvGn9c>Cr}98>4^=WpjcS3F6vH^qDX0ed8( z(gYvT@pq7k`ky`zsGho4EF^%v~hY624t-a0WMq@N4GFJj`h`UAZdzv zn5Av)&9<9C0>r<;%AskOZ6H1tYdzy}UAo_L5`)5}$uSUa2x@n#n zPPjzAXuW)VcEbc1PLp-~ctc~P*89&56!5)QBM&DE56d+wCAl8uu)8?*r_x&#yK(Sz zc>saLDYMTBa3dYUy#!Qat^p2>$Iq1FUuv$k-fh9S_Q)qLvCH!*2pTKqAzo8sxK8OT zTPGa0h{=5a)(-Dw8@ew37ral^9dtqgdBYP#=Ke=9W!gWAOh4WlcF?YpgzBH;aNJdc zd(VM?=k6LB0?gO7g&52E0PdA z9pB7Kbx@pvk}aA--e0Z6jw_kI8Zo`6HFP)H{d<_x6r}aG_I3xcpz8UiqUWjem?-eu%5RLbbTGwumt!j)l@;)@8G;ntRDOJ!1UM5nw|m zeE3VIQnZIxH+ZGM83ysEd=>3w5T0Yva`Xt6kjU^+POF*twP`OjnhgyXfinnJ{ zuN~fH%jEOXlv_yL-RpM8Ae5Je8x}HUoZV5o>u5N1Zf1ZViuft8J^#ALOB|WFSy)iK z=uX?-&h5xeVh*J~F3g)u={Lh$ zGH!qmiz^OKK3o^Amw>LqBes%B{U)L3cmriEq4oCss5je4iG5?^g*3x!?9|t^a-zr+ z@s7kX0UEg3wneOZN5J3Rwj5{*{B-*Un(bYC0DMTuD+YL^1J$lNzgB=;q7*)FzQ7@h zonZYF;_}3QO4kGj%zyi2a2^6E@W$y?M?Ls63yA@qJXDkbDBfWU=$W(9155kDYV|-1 z@!e`a5CfQ;w{yUKdVz}DH6oy86RP)k`oBAG4z&1zfd9lsfS5;@;w49)vb#0WZ~yP4 z|B;l+aO^2I3t#j9|0dpyvtjlF0ab0o*3UsC0pKbRb$B$3)cHT# z{m)8Fs$Vyu#s{0ch3FfRq59i}oDvY3KJ( zP^$kVZ(g&%{l7Mm%6Z=IL8%vLt9*V5Q2uY^FeL^EQhx@zR+{_+5RZDsp)v=(d~~s` zH3$OyB>p*_9e&gO?HeHV-{1o@_t`R{<^KW*Bo}V>jIi`uqVm)Q@cgG10Ltm44-j+_ G5&jo!~d}*y&wc2PI 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 literal 0 HcmV?d00001 diff --git a/redis-vector-db-0.9.0.tgz b/redis-vector-db-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ad9a900217a71973f1ece71a91549d4b24c1ea83 GIT binary patch literal 3083 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 Z@>m|rWBGqo{x<*s|NnQ1ywCtX0075Z5vu?I literal 0 HcmV?d00001 diff --git a/reranking-usvc-0.9.0.tgz b/reranking-usvc-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..b7a10a3ae432a3691b423ba8c5c53787c435a8a7 GIT binary patch literal 5768 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 L^$(Lf0NMZm0_O`G literal 0 HcmV?d00001 diff --git a/speecht5-0.9.0.tgz b/speecht5-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..381a4188534e7c6c5ddf156f3b2aa4e485a695a0 GIT binary patch literal 3799 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 literal 0 HcmV?d00001 diff --git a/update_chart_repo.sh b/update_chart_repo.sh index 1392bf16..0eab7ef6 100755 --- a/update_chart_repo.sh +++ b/update_chart_repo.sh @@ -3,6 +3,15 @@ # Copyright (C) 2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 +# Steps for new helm charts release v0.9rc as example. +# This should be implemented in CICD workflow. +# 0. Charts tag updated for this branch +# 1. git clone -b https://github.com/opea-project/GenAIInfra.git +# 2. cd GenAIInfra; git switch v0.9rc; cp -rf helm-charts ../ # Copy the helm-charts directory +# 3. git switch charts-release; mv ../helm-charts . # Switch to charts-release and copy back the helm-charts directory +# 4. ./update_chart_repo.sh; rm -rf helm-charts +# 5. git add .; git commit -s -m "v0.9 charts release"; git push + UPD_DIR=$(cd $(dirname "$0") && pwd) CHART_DIR=./helm-charts @@ -32,4 +41,5 @@ do done # Update Index +mv index.yaml index.old helm repo index . diff --git a/web-retriever-0.9.0.tgz b/web-retriever-0.9.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0e47e849fcdca35d7c5b16500a1b45bd0b5e7f84 GIT binary patch literal 5877 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 literal 0 HcmV?d00001