You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by rh...@apache.org on 2020/06/19 21:15:27 UTC

[geode] branch develop updated: Add Mass Test Run pipeline. (#5271)

This is an automated email from the ASF dual-hosted git repository.

rhoughton pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 3c52713  Add Mass Test Run pipeline. (#5271)
3c52713 is described below

commit 3c527139713d243800711ceedddcb578ab5be636
Author: Robert Houghton <rh...@pivotal.io>
AuthorDate: Fri Jun 19 14:14:42 2020 -0700

    Add Mass Test Run pipeline. (#5271)
    
    * Add Mass Test Run pipeline.
    
    This commit includes:
    * Creating a new test run pipeline that runs the distributed test
      multiple number of times.
    * Update the meta pipeline to add mass test run pipeline.
    * Logs into concourse using Vault creds to get fly token.
    * Poll for test run completion, to trigger report-generation.
    * Use 'try' around test job, so we can trigger on it's completion state
---
 ci/bin/concourse_job_performance.py               |  10 +-
 ci/bin/concourse_job_performance_requirements.txt |   5 +
 ci/images/meta-mini/Dockerfile                    |   2 +-
 ci/pipelines/mass-test-run/deploy_pipeline.sh     |  90 +++++
 ci/pipelines/mass-test-run/jinja.template.yml     | 434 ++++++++++++++++++++++
 ci/pipelines/meta/deploy_meta.sh                  |   2 +
 ci/pipelines/meta/destroy_pipelines.sh            |   2 +-
 ci/pipelines/meta/jinja.template.yml              |  54 +++
 ci/pipelines/shared/jinja.variables.yml           |   1 +
 9 files changed, 594 insertions(+), 6 deletions(-)

diff --git a/ci/bin/concourse_job_performance.py b/ci/bin/concourse_job_performance.py
index 9b939f8..8e68f03 100755
--- a/ci/bin/concourse_job_performance.py
+++ b/ci/bin/concourse_job_performance.py
@@ -29,7 +29,9 @@ from typing import List
 from urllib.parse import urlparse
 
 import requests
+# from sseclient-py
 import sseclient
+# from ansicolors
 from colors import color
 from tqdm import tqdm
 import yaml
@@ -58,15 +60,15 @@ def main(url, team, pipeline, job, number_of_builds, authorization_cookie, threa
     builds = get_builds_summary_sheet(url, team, pipeline, job, number_of_builds+10, authorization_cookie)
 
     build_to_examine = get_builds_to_examine(builds, number_of_builds)
-    expected_failed_builds = [int(b['name']) for b in build_to_examine if b['status'] == 'failed']
-    expected_failed_builds_count = len(expected_failed_builds)
-    logging.info(f"Expecting {expected_failed_builds_count} runs to have failure strings: {expected_failed_builds}")
+    completed_builds = [int(b['name']) for b in build_to_examine if b['status'] in ['failed', 'succeeded']]
+    completed_builds_count = len(completed_builds)
+    logging.info(f"Expecting {completed_builds_count} runs to have failure strings: {completed_builds}")
 
     long_list_of_failures = aggregate_failure_information(authorization_cookie, build_to_examine, threaded, url)
 
     failure_url_base = f"{url}/teams/{team}/pipelines/{pipeline}/jobs/{job}/builds/"
 
-    print_results(len(build_to_examine), expected_failed_builds, long_list_of_failures, url, failure_url_base)
+    print_results(len(build_to_examine), completed_builds, long_list_of_failures, url, failure_url_base)
 
 
 def get_cookie(url):
diff --git a/ci/bin/concourse_job_performance_requirements.txt b/ci/bin/concourse_job_performance_requirements.txt
new file mode 100644
index 0000000..90cad74
--- /dev/null
+++ b/ci/bin/concourse_job_performance_requirements.txt
@@ -0,0 +1,5 @@
+ansicolors
+requests
+sseclient-py
+tqdm
+urllib3
diff --git a/ci/images/meta-mini/Dockerfile b/ci/images/meta-mini/Dockerfile
index 97886eb..3598990 100644
--- a/ci/images/meta-mini/Dockerfile
+++ b/ci/images/meta-mini/Dockerfile
@@ -21,7 +21,7 @@ RUN apk --no-cache add \
       bash \
       curl \
       jq \
-      python2 \
       python3 \
+      py3-pip \
   && pip3 install --upgrade pip \
   && pip3 install Jinja2 pyYAML
diff --git a/ci/pipelines/mass-test-run/deploy_pipeline.sh b/ci/pipelines/mass-test-run/deploy_pipeline.sh
new file mode 100755
index 0000000..0a48277
--- /dev/null
+++ b/ci/pipelines/mass-test-run/deploy_pipeline.sh
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink
+  SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+  SOURCE="$(readlink "$SOURCE")"
+  [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located
+done
+SCRIPTDIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+GEODEBUILDDIR="${SCRIPTDIR}/../geode-build"
+
+set -e
+
+if [ -z "${GEODE_BRANCH}" ]; then
+  GEODE_BRANCH=$(git rev-parse --abbrev-ref HEAD)
+fi
+
+if [ "${GEODE_BRANCH}" = "HEAD" ]; then
+  echo "Unable to determine branch for deployment. Quitting..."
+  exit 1
+fi
+
+
+echo "Sanitized Geode Fork = ${SANITIZED_GEODE_FORK}"
+echo "Sanitized Geode Branch = ${SANITIZED_GEODE_BRANCH}"
+
+MY_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/name" -H "Metadata-Flavor: Google")
+MY_ZONE=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/zone" -H "Metadata-Flavor: Google")
+MY_ZONE=${MY_ZONE##*/}
+NETWORK_INTERFACE_INFO="$(gcloud compute instances describe ${MY_NAME} --zone ${MY_ZONE} --format="json(networkInterfaces)")"
+GCP_NETWORK=$(echo ${NETWORK_INTERFACE_INFO} | jq -r '.networkInterfaces[0].network')
+GCP_NETWORK=${GCP_NETWORK##*/}
+GCP_SUBNETWORK=$(echo ${NETWORK_INTERFACE_INFO} | jq -r '.networkInterfaces[0].subnetwork')
+GCP_SUBNETWORK=${GCP_SUBNETWORK##*/}
+ENV_ID=$(echo ${GCP_NETWORK} | awk -F- '{ print $1}')
+
+#echo "DEBUG INFO *****************************"
+#echo "Pipeline prefix = ${PIPELINE_PREFIX}"
+#echo "Docker image prefix = ${DOCKER_IMAGE_PREFIX}"
+pushd ${SCRIPTDIR} 2>&1 > /dev/null
+
+  cat > repository.yml <<YML
+repository:
+  project: 'geode'
+  fork: ${GEODE_FORK}
+  branch: ${GEODE_BRANCH}
+  upstream_fork: ${UPSTREAM_FORK}
+  public: ${REPOSITORY_PUBLIC}
+  sanitized_fork: ${SANITIZED_GEODE_FORK}
+YML
+
+  python3 ../render.py jinja.template.yml --variable-file ../shared/jinja.variables.yml repository.yml --environment ../shared/ --output ${SCRIPTDIR}/generated-pipeline.yml || exit 1
+
+popd 2>&1 > /dev/null
+cp ${SCRIPTDIR}/generated-pipeline.yml ${OUTPUT_DIRECTORY}/generated-pipeline.yml
+
+grep -n . ${OUTPUT_DIRECTORY}/generated-pipeline.yml
+
+cat > ${OUTPUT_DIRECTORY}/pipeline-vars.yml <<YML
+geode-build-branch: ${GEODE_BRANCH}
+geode-fork: ${GEODE_FORK}
+geode-repo-name: ${GEODE_REPO_NAME}
+upstream-fork: ${UPSTREAM_FORK}
+pipeline-prefix: "${PIPELINE_PREFIX}"
+public-pipelines: ${PUBLIC_PIPELINES}
+gcp-project: ${GCP_PROJECT}
+artifact-bucket: ${ARTIFACT_BUCKET}
+gradle-global-args: ${GRADLE_GLOBAL_ARGS}
+semver-prerelease-token: ${SEMVER_PRERELEASE_TOKEN}
+concourse-url: ${CONCOURSE_URL}
+concourse-team: ${CONCOURSE_TEAM}
+YML
+
+
diff --git a/ci/pipelines/mass-test-run/jinja.template.yml b/ci/pipelines/mass-test-run/jinja.template.yml
new file mode 100644
index 0000000..31285ec
--- /dev/null
+++ b/ci/pipelines/mass-test-run/jinja.template.yml
@@ -0,0 +1,434 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+{% from 'shared_jinja.yml' import pipeline_prefix with context %}
+{% from 'shared_jinja.yml' import github_access with context %}
+{% from 'shared_jinja.yml' import init_retry with context %}
+
+---
+
+{% macro plan_resource_gets(test) %}
+- in_parallel:
+  - get: geode-ci
+    passed: [PinGeode]
+    params:
+      depth: 1
+  - get: geode
+    passed: [PinGeode]
+    params:
+      depth: 1
+  - get: geode-build-version
+    passed: [PinGeode]
+{% endmacro %}
+
+{%- macro deep_merge(a, b): %}
+  {%- if b is defined %}
+    {%- for k,v in b.items(): %}
+      {%- if v is not defined: %}
+        {%- do a.pop(k) %}
+      {%- else: %}
+        {%- if v is mapping: %}
+          {%- if a[k] is not mapping: %}
+            {%- do a.update({ k: { } }) %}
+          {%- endif %}
+          {%- do deep_merge(a[k], v) %}
+        {%- else: %}
+          {%- do a.update({ k: v }) %}
+        {%- endif %}
+      {% endif %}
+    {%- endfor %}
+  {%- endif %}
+{%- endmacro %}
+
+{%- do deep_merge(a, b) %}
+
+{% macro common_instance_params(test) -%}
+GCP_PROJECT: ((gcp-project))
+CPUS: {{ test.CPUS }}
+RAM: {{ test.RAM }}
+{%- endmacro %}
+
+{% macro common_test_params(test) -%}
+  {%- if test.CALL_STACK_TIMEOUT -%}
+CALL_STACK_TIMEOUT: {{ test.CALL_STACK_TIMEOUT }}
+  {%- endif %}
+  {%- if test.GRADLE_TASK_OPTIONS -%}
+GRADLE_TASK_OPTIONS: {{ test.GRADLE_TASK_OPTIONS }}
+  {%- endif %}
+DUNIT_PARALLEL_FORKS: {{ test.DUNIT_PARALLEL_FORKS }}
+MAINTENANCE_VERSION: ((geode-build-branch ))
+BUILD_BRANCH: ((geode-build-branch))
+PARALLEL_DUNIT: {{ test.PARALLEL_DUNIT }}
+{% if test.PARALLEL_GRADLE is defined %}
+PARALLEL_GRADLE: {{ test.PARALLEL_GRADLE }}
+{% else %}
+PARALLEL_GRADLE: true
+{% endif %}
+ARTIFACT_BUCKET: ((artifact-bucket))
+SERVICE_ACCOUNT: ((concourse-gcp-account))
+GRADLE_GLOBAL_ARGS: ((gradle-global-args))
+{%- endmacro %}
+
+
+resources:
+- name: weekly
+  type: time
+  source:
+    interval: 168h
+- name: test-finish-time
+  type: time
+  source:
+    interval: 168h
+
+- name: concourse-metadata-resource
+  type: concourse-metadata-resource
+  source: {}
+- name: geode
+  type: git
+  source:
+    branch: ((geode-build-branch))
+    ignore_paths:
+      - ci/*
+      - dev-tools/release/*
+      - "*.md"
+    {{ github_access() | indent(4) }}
+- name: geode-ci
+  type: git
+  source:
+    branch: ((geode-build-branch))
+    paths:
+    - ci/*
+    {{ github_access() | indent(4) }}
+- name: geode-build-version
+  type: semver
+  source:
+    bucket: ((artifact-bucket))
+    driver: gcs
+    initial_version: {{ metadata.initial_version }}
+    json_key: ((concourse-gcp-key))
+    key: semvers/((pipeline-prefix))((geode-build-branch))/version
+- name: alpine-tools-image
+  type: docker-image
+  source:
+    username: ((docker-username))
+    password: ((docker-password))
+    repository: gcr.io/((gcp-project))/((pipeline-prefix))alpine-tools
+    tag: latest
+- name: linux-builder-image-family
+  type: gci
+  source:
+    key: ((concourse-gcp-key))
+    family_project: ((gcp-project))
+    family: ((pipeline-prefix))linux-geode-builder
+
+resource_types:
+- name: gci
+  type: registry-image
+  source:
+    repository: smgoller/gci-resource
+- name: concourse-metadata-resource
+  type: docker-image
+  source:
+    password: ((docker-password))
+    repository: gcr.io/((gcp-project))/((pipeline-prefix))concourse-metadata-resource
+    tag: latest
+    username: ((docker-username))
+- name: gcs-resource
+  type: docker-image
+  source:
+    repository: frodenas/gcs-resource
+
+jobs:
+- name: PinGeode
+  serial: true
+  plan:
+  - in_parallel:
+    - get: weekly
+      trigger: true
+    - get: geode
+      params:
+        depth: 1
+    - get: geode-ci
+      params:
+        depth: 1
+    - get: geode-build-version
+
+- name: TriggerMassRun
+  serial: true
+  plan:
+  - in_parallel:
+    - get: weekly
+      trigger: true
+      passed: [PinGeode]
+    - get: alpine-tools-image
+  - task: run-distributed-test-N-times
+    image: alpine-tools-image
+    config:
+      platform: linux
+      params:
+        CONCOURSE_URL: ((concourse-url))
+        CONCOURSE_TEAM: ((concourse-team))
+        PIPELINE_PREFIX: ((pipeline-prefix))
+        MASS_RUN_NUMBER: {{metadata.mass_test_run_iterations}}
+      {%- if repository.upstream_fork!="apache" %}
+        CONCOURSE_USERNAME: ((concourse-username))
+        CONCOURSE_PASSWORD: ((concourse-password))
+      {%- endif %}
+      run:
+        path: bash
+        args:
+        - -exc
+        - |
+          BASE_DIR=$(pwd)
+          curl "${CONCOURSE_URL}/api/v1/cli?arch=amd64&platform=linux" --output fly
+          chmod +x fly
+          TARGET_NAME=concourse
+          ./fly -t ${TARGET_NAME} login -c "${CONCOURSE_URL}" -n "${CONCOURSE_TEAM}" -u "${CONCOURSE_USERNAME}" -p "${CONCOURSE_PASSWORD}"
+          for t in $(seq ${MASS_RUN_NUMBER}); do
+            ./fly -t ${TARGET_NAME} trigger-job -j ${PIPELINE_PREFIX}mass-test-run/DistributedTestOpenJDK8
+          done
+
+{%- for test in tests if test.name=="Distributed" %}
+  {%- set parameters = {} %}
+  {%- do deep_merge(parameters, test) %}
+  {%- for java_test_version in (java_test_versions) if java_test_version.name=="OpenJDK8" -%}
+    {%- if java_test_version.override is defined and java_test_version.override[test.name] is defined %}
+      {%- do deep_merge(parameters, java_test_version.override[test.name]) %}
+    {%- endif %}
+- name: {{test.name}}Test{{java_test_version.name}}
+  max_in_flight: 5
+  plan:
+  - do:
+    {{- plan_resource_gets(test) |indent(4) }}
+      - put: concourse-metadata-resource
+      - get: alpine-tools-image
+        passed: [TriggerMassRun]
+      - get: {{ test.PLATFORM}}-builder-image-family
+    - do:
+      {{ init_retry()|indent(6) }}
+      - task: create_instance-{{java_test_version.name}}
+        image: alpine-tools-image
+        config:
+          platform: linux
+          params:
+            {{ common_instance_params(parameters) | indent(12) }}
+            GEODE_BRANCH: {{repository.branch}}
+            GEODE_FORK: {{repository.fork}}
+            JAVA_BUILD_VERSION: {{ java_build_version.version }}
+            JAVA_TEST_VERSION: {{ java_test_version.version }}
+            IMAGE_FAMILY_NAME: ((pipeline-prefix)){{ test.PLATFORM }}-geode-builder
+          run:
+            path: geode-ci/ci/scripts/create_instance.sh
+          inputs:
+          - name: concourse-metadata-resource
+          - name: geode-ci
+          - name: geode
+          - name: attempts-log
+            path: old
+          outputs:
+          - name: instance-data-{{java_test_version.name}}
+            path: instance-data
+          - name: attempts-log
+            path: new
+        timeout: 20m
+        attempts: 5
+      - do:
+        - task: rsync_code_up-{{java_test_version.name}}
+          image: alpine-tools-image
+          config:
+            platform: linux
+            run:
+              path: geode-ci/ci/scripts/rsync_code_up.sh
+            inputs:
+            - name: geode-ci
+            - name: geode
+            - name: instance-data-{{java_test_version.name}}
+              path: instance-data
+          timeout: 15m
+          attempts: 5
+        - try:
+            task: execute_tests-{{java_test_version.name}}
+            image: alpine-tools-image
+            config:
+              platform: linux
+              params:
+                ARTIFACT_SLUG: {{test.ARTIFACT_SLUG}}-{{java_test_version.name}}
+                JAVA_BUILD_VERSION: {{ java_build_version.version }}
+                JAVA_TEST_VERSION: {{ java_test_version.version }}
+                GRADLE_TASK: {{test.GRADLE_TASK}}
+                {{ common_test_params(parameters) | indent(16) }}
+              run:
+                path: geode-ci/ci/scripts/execute_tests.sh
+              inputs:
+              - name: geode-ci
+              - name: geode
+              - name: instance-data-{{java_test_version.name}}
+                path: instance-data
+            timeout: {{parameters.EXECUTE_TEST_TIMEOUT}}
+            ensure:
+              do:
+              - task: rsync_code_down-{{java_test_version.name}}
+                image: alpine-tools-image
+                config:
+                  platform: linux
+                  params:
+                    JAVA_BUILD_VERSION: {{ java_build_version.version }}
+                    ARTIFACT_SLUG: {{test.ARTIFACT_SLUG}}-{{java_test_version.name}}
+                  run:
+                    path: geode-ci/ci/scripts/rsync_code_down.sh
+                  inputs:
+                  - name: geode-ci
+                  - name: instance-data-{{java_test_version.name}}
+                    path: instance-data
+                  outputs:
+                  - name: geode-results-{{java_test_version.name}}
+                    path: geode-results
+                timeout: 15m
+                attempts: 5
+              ensure:
+                do:
+                - aggregate:
+                  - task: archive_results-{{java_test_version.name}}
+                    image: alpine-tools-image
+                    config:
+                      platform: linux
+                      params:
+                        ARTIFACT_SLUG: {{test.ARTIFACT_SLUG}}-{{java_test_version.name}}
+                        GRADLE_TASK: {{test.GRADLE_TASK}}
+                        MAINTENANCE_VERSION: ((geode-build-branch))
+                        ARTIFACT_BUCKET: ((artifact-bucket))
+                        SERVICE_ACCOUNT: ((concourse-gcp-account))
+                      run:
+                        path: geode-ci/ci/scripts/archive_results.sh
+                      inputs:
+                        - name: concourse-metadata-resource
+                        - name: geode-ci
+                        - name: geode-build-version
+                        - name: geode-results-{{java_test_version.name}}
+                          path: geode-results
+                    timeout: 1h
+                  - task: delete_instance-{{java_test_version.name}}
+                    image: alpine-tools-image
+                    config:
+                      platform: linux
+                      run:
+                        path: geode-ci/ci/scripts/delete_instance.sh
+                      inputs:
+                      - name: geode-ci
+                      - name: instance-data-{{java_test_version.name}}
+                        path: instance-data
+                    timeout: 1h
+  ensure:
+    put: test-finish-time
+
+- name: poll-for-test-completion
+  public: ((public-pipelines))
+  serial: true
+  plan:
+  - in_parallel:
+    - get: alpine-tools-image
+      passed: [TriggerMassRun]
+    - get: test-finish-time
+      passed: [{{test.name}}Test{{java_test_version.name}}]
+      trigger: true
+    - put: concourse-metadata-resource
+  - task: poll-for-test-run-completion
+    image: alpine-tools-image
+    config:
+      platform: linux
+      inputs:
+      - name: concourse-metadata-resource
+      run:
+        path: bash
+        args:
+        - -ec
+        - |
+          . concourse-metadata-resource/concourse_metadata
+          curl -L -k -o fly "((concourse-url))/api/v1/cli?arch=amd64&platform=linux"
+          chmod +x fly
+          job="{{test.name}}Test{{java_test_version.name}}"
+          ./fly -t cc login \
+            --concourse-url=((concourse-url)) \
+            --team-name=((concourse-team)) \
+            --username=((concourse-username)) \
+            --password=((concourse-password))
+          latest_job=$(./fly -t cc builds --job="${BUILD_PIPELINE_NAME}/${job}" --json | jq -r 'max_by(.name | tonumber)')
+          count=$(echo "${latest_job}" | jq -r .name)
+          if [ $((${count}%{{metadata.mass_test_run_iterations}})) -ne 0 ]; then
+            echo "Only generating report every [{{metadata.mass_test_run_iterations}}] jobs. Exiting."
+            exit 1
+          fi
+
+          status=$(echo "${latest_job}" | jq -r .status)
+          case ${status} in
+            started | pending)
+              echo "Waiting on job ["${count}"] to finish"
+              exit 1
+              ;;
+            succeeded | errored | failed | aborted | *)
+              echo "Job [${count}] status is: [${status}]. Processing"
+              ;;
+          esac
+          exit 0
+
+- name: create-mass-test-run-report
+  public: ((public-pipelines))
+  serial: true
+  plan:
+  - in_parallel:
+    - get: geode-ci
+      params:
+        depth: 1
+    - get: alpine-tools-image
+      passed: [poll-for-test-completion]
+    - put: concourse-metadata-resource
+    - get: test-finish-time
+      passed: [poll-for-test-completion]
+      trigger: true
+  - task: create-job-performance-report
+    image: alpine-tools-image
+    config:
+      platform: linux
+      inputs:
+      - name: geode-ci
+      - name: concourse-metadata-resource
+      run:
+        path: bash
+        args:
+        - -ec
+        - |
+          . concourse-metadata-resource/concourse_metadata
+          curl -L -k -o fly "((concourse-url))/api/v1/cli?arch=amd64&platform=linux"
+          chmod +x fly
+          job="{{test.name}}Test{{java_test_version.name}}"
+          ./fly -t cc login \
+            --concourse-url=((concourse-url)) \
+            --team-name=((concourse-team)) \
+            --username=((concourse-username)) \
+            --password=((concourse-password))
+
+          set -x
+          pip3 install -r geode-ci/ci/bin/concourse_job_performance_requirements.txt
+          geode-ci/ci/bin/concourse_job_performance.py \
+            --threaded \
+            --team ((concourse-team)) \
+            --number-of-builds {{metadata.mass_test_run_iterations}} \
+            ((concourse-url)) \
+            ${BUILD_PIPELINE_NAME} \
+            {{test.name}}Test{{java_test_version.name}}
+{% endfor -%}
+{% endfor -%}
diff --git a/ci/pipelines/meta/deploy_meta.sh b/ci/pipelines/meta/deploy_meta.sh
index 7ae0fe6..5443936 100755
--- a/ci/pipelines/meta/deploy_meta.sh
+++ b/ci/pipelines/meta/deploy_meta.sh
@@ -256,6 +256,7 @@ set +x
 if [[ "${GEODE_FORK}" != "${UPSTREAM_FORK}" ]]; then
   echo "Disabling unnecessary jobs for forks."
   pauseJobs ${META_PIPELINE} set-reaper-pipeline
+  pauseJobs ${META_PIPELINE} set-mass-test-run-pipeline
   pauseNewJobs ${META_PIPELINE} set-metrics-pipeline
 elif [[ "$GEODE_FORK" == "${UPSTREAM_FORK}" ]] && [[ "$GEODE_BRANCH" == "develop" ]]; then
   echo "Disabling optional jobs for develop"
@@ -283,6 +284,7 @@ if [[ "$GEODE_FORK" == "${UPSTREAM_FORK}" ]]; then
   fi
   if [[ "$GEODE_BRANCH" == "develop" ]]; then
     enableFeature pr
+    enableFeature mass-test-run
   fi
 fi
 
diff --git a/ci/pipelines/meta/destroy_pipelines.sh b/ci/pipelines/meta/destroy_pipelines.sh
index 7203a85..36bc8ba 100755
--- a/ci/pipelines/meta/destroy_pipelines.sh
+++ b/ci/pipelines/meta/destroy_pipelines.sh
@@ -80,5 +80,5 @@ function destroyPipelines {
   done
 }
 
-destroyPipelines ${PIPELINE_PREFIX}main ${PIPELINE_PREFIX}pr ${PIPELINE_PREFIX}images ${PIPELINE_PREFIX}reaper ${PIPELINE_PREFIX}metrics ${PIPELINE_PREFIX}examples ${PIPELINE_PREFIX}meta ${PIPELINE_PREFIX}rc
+destroyPipelines ${PIPELINE_PREFIX}main ${PIPELINE_PREFIX}pr ${PIPELINE_PREFIX}images ${PIPELINE_PREFIX}reaper ${PIPELINE_PREFIX}metrics ${PIPELINE_PREFIX}examples ${PIPELINE_PREFIX}meta ${PIPELINE_PREFIX}rc ${PIPELINE_PREFIX}mass-test-run
 echo "Destroyed ${CONCOURSE_URL}/teams/${CONCOURSE_TEAM}/pipelines/${PIPELINE_PREFIX}main and all related pipelines"
diff --git a/ci/pipelines/meta/jinja.template.yml b/ci/pipelines/meta/jinja.template.yml
index 5fd8ee3..d4fad8a 100644
--- a/ci/pipelines/meta/jinja.template.yml
+++ b/ci/pipelines/meta/jinja.template.yml
@@ -59,6 +59,14 @@ resources:
     branch: ((!geode-build-branch))
     paths:
     - ci/pipelines/reaper/*
+- name: geode-mass-test-run-pipeline
+  type: git
+  source:
+    {{ github_access() | indent(4) }}
+    branch: ((geode-build-branch))
+    paths:
+      - ci/pipelines/mass-test-run/*
+      - ci/pipelines/shared/*
 - name: meta-mini-dockerfile
   type: git
   source:
@@ -358,6 +366,52 @@ jobs:
             vars_files:
               - results/pipeline-vars.yml
 
+- name: set-mass-test-run-pipeline
+  serial: true
+  public: ((public-pipelines))
+  plan:
+  - in_parallel:
+    - get: geode-mass-test-run-pipeline
+      trigger: true
+    - get: meta-mini-image
+      trigger: true
+      passed: [build-meta-mini-docker-image]
+  - task: create-image-yml
+    image: meta-mini-image
+    config:
+      platform: linux
+      inputs:
+      - name: geode-mass-test-run-pipeline
+      outputs:
+      - name: results
+      params:
+        OUTPUT_DIRECTORY: results
+        GEODE_BRANCH: ((geode-build-branch))
+        GEODE_FORK: ((geode-fork))
+        GEODE_REPO_NAME: ((geode-repo-name))
+        REPOSITORY_PUBLIC: {{ repository.public }}
+        UPSTREAM_FORK: {{ repository.upstream_fork }}
+        PIPELINE_PREFIX: ((pipeline-prefix))
+        SANITIZED_GEODE_BRANCH: ((sanitized-geode-build-branch))
+        SANITIZED_GEODE_FORK: ((sanitized-geode-fork))
+        GCP_PROJECT: ((gcp-project))
+        PUBLIC_PIPELINES: ((public-pipelines))
+        CONCOURSE_URL: ((concourse-url))
+        CONCOURSE_HOST: ((concourse-host))
+        CONCOURSE_TEAM: ((concourse-team))
+        ARTIFACT_BUCKET: ((artifact-bucket))
+        SEMVER_PRERELEASE_TOKEN: ((semver-prerelease-token))
+      run:
+        path: geode-mass-test-run-pipeline/ci/pipelines/mass-test-run/deploy_pipeline.sh
+  - put: concourse
+    params:
+      pipelines:
+      - name: ((pipeline-prefix))mass-test-run
+        team: ((concourse-team))
+        config_file: results/generated-pipeline.yml
+        vars_files:
+        - results/pipeline-vars.yml
+
 - name: build-meta-mini-docker-image
   public: ((!public-pipelines))
   serial: true
diff --git a/ci/pipelines/shared/jinja.variables.yml b/ci/pipelines/shared/jinja.variables.yml
index a23324c..001f1e7 100644
--- a/ci/pipelines/shared/jinja.variables.yml
+++ b/ci/pipelines/shared/jinja.variables.yml
@@ -67,6 +67,7 @@ java_test_versions:
 
 metadata:
   initial_version: 1.14.0-((semver-prerelease-token)).0
+  mass_test_run_iterations: 200
 
 publish_artifacts:
   CPUS: '8'