Skip to content

Commit

Permalink
Add Jenkins Webhook and RFS E2E job on PR (#938)
Browse files Browse the repository at this point in the history
This change is hopefully the final chapter in the Jenkins setup saga and a huge leap in the integration testing story. It adds support for triggering a Jenkins job through a configured Github Action and monitoring its completion to pass/fail the action. This design is one of many that can be taken, but a benefit of this approach is that it keeps the Github action in control of triggering Jenkins jobs and status updates, as well as doesn't require Jenkins to update the PR (though this may be a useful addition for logs or other data in the future). As a part of this pattern, a general python webhook was created to trigger Jenkins jobs and monitor for a terminal state, this could also potentially become its own GHA plugin separate from this repo.

Additionally this adds our first GHA which triggers a Jenkins job for our RFS E2E integration test. This GHA is planned to be monitored as any bugs/issues are worked out and should not currently be restrictive for PRs to be merged.

Jenkins pipeline files have also been updated to attain a lock on deployment stages to prevent different pipeline executions from using the same stage. The pipeline files have also been largely moved to the shared library, to allow any changes to the context options or other settings for the pipeline to be tested in PR actions.

---------

Signed-off-by: Tanner Lewis <[email protected]>
Co-authored-by: Peter Nied <[email protected]>
  • Loading branch information
lewijacn and peternied authored Sep 19, 2024
1 parent 7a72c10 commit d2dce08
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 108 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/rfs_pr_e2e_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Jenkins

on:
push:
branches-ignore:
- 'backport/**'
- 'dependabot/**'
pull_request_target:
types: [opened, synchronize, reopened]

env:
python-version: '3.11'

permissions:
contents: read # to fetch code (actions/checkout)

jobs:
rfs-e2e-aws-test:
runs-on: ubuntu-latest
steps:
- name: Determine Github repository and branch
id: determine-repo-vars
run: |
if [[ "${GITHUB_EVENT_NAME}" == "pull_request_target" ]]; then
branch_name="${{ github.event.pull_request.head.ref }}"
pr_repo_url="https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git"
else
branch_name="${{ github.ref_name }}"
pr_repo_url="https://github.com/${{ github.repository }}.git"
fi
echo "Running jenkins test on repo: $pr_repo_url and branch: $branch_name"
echo "branch_name=$branch_name" >> $GITHUB_OUTPUT
echo "pr_repo_url=$pr_repo_url" >> $GITHUB_OUTPUT
- name: Jenkins Job Trigger and Monitor
uses: lewijacn/[email protected]
with:
jenkins_url: "https://migrations.ci.opensearch.org"
job_name: "rfs-default-e2e-test"
api_token: "${{ secrets.JENKINS_MIGRATIONS_GENERIC_WEBHOOK_TOKEN }}"
job_params: "GIT_REPO_URL=${{ steps.determine-repo-vars.outputs.pr_repo_url }},GIT_BRANCH=${{ steps.determine-repo-vars.outputs.branch_name }}"
78 changes: 0 additions & 78 deletions jenkins/migrationIntegPipelines/rfsBackfillE2EPipeline.groovy

This file was deleted.

9 changes: 9 additions & 0 deletions jenkins/migrationIntegPipelines/rfsDefaultE2ETestCover.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def gitBranch = params.GIT_BRANCH ?: 'main'
def gitUrl = params.GIT_REPO_URL ?: 'https://github.com/opensearch-project/opensearch-migrations.git'

library identifier: "migrations-lib@${gitBranch}", retriever: modernSCM(
[$class: 'GitSCMSource',
remote: "${gitUrl}"])

// Shared library function (location from root: vars/rfsDefaultE2ETest.groovy)
rfsDefaultE2ETest()
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def gitBranch = params.GIT_BRANCH ?: 'main'
def gitUrl = params.GIT_REPO_URL ?: 'https://github.com/opensearch-project/opensearch-migrations.git'

library identifier: "migrations-lib@${gitBranch}", retriever: modernSCM(
[$class: 'GitSCMSource',
remote: "${gitUrl}"])

// Shared library function (location from root: vars/trafficReplayDefaultE2ETest.groovy)
trafficReplayDefaultE2ETest()
39 changes: 33 additions & 6 deletions vars/defaultIntegPipeline.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ def call(Map config = [:]) {
def sourceContext = config.sourceContext
def migrationContext = config.migrationContext
def defaultStageId = config.defaultStageId
def jobName = config.jobName
if(sourceContext == null || sourceContext.isEmpty()){
throw new RuntimeException("The sourceContext argument must be provided");
}
Expand All @@ -11,11 +12,16 @@ def call(Map config = [:]) {
if(defaultStageId == null || defaultStageId.isEmpty()){
throw new RuntimeException("The defaultStageId argument must be provided");
}
if(jobName == null || jobName.isEmpty()){
throw new RuntimeException("The jobName argument must be provided");
}
def source_context_id = config.sourceContextId ?: 'source-single-node-ec2'
def migration_context_id = config.migrationContextId ?: 'migration-default'
def source_context_file_name = 'sourceJenkinsContext.json'
def migration_context_file_name = 'migrationJenkinsContext.json'
def skipCaptureProxyOnNodeSetup = config.skipCaptureProxyOnNodeSetup ?: false
def testDir = "/root/lib/integ_test/integ_test"
def integTestCommand = config.integTestCommand ?: "${testDir}/replayer_tests.py"
pipeline {
agent { label config.workerAgent ?: 'Jenkins-Default-Agent-X64-C5xlarge-Single-Host' }

Expand All @@ -25,6 +31,27 @@ def call(Map config = [:]) {
string(name: 'STAGE', defaultValue: "${defaultStageId}", description: 'Stage name for deployment environment')
}

options {
// Acquire lock on a given deployment stage
lock(label: params.STAGE, quantity: 1, variable: 'stage')
timeout(time: 3, unit: 'HOURS')
buildDiscarder(logRotator(daysToKeepStr: '30'))
}

triggers {
GenericTrigger(
genericVariables: [
[key: 'GIT_REPO_URL', value: '$.GIT_REPO_URL'],
[key: 'GIT_BRANCH', value: '$.GIT_BRANCH'],
[key: 'job_name', value: '$.job_name']
],
tokenCredentialId: 'jenkins-migrations-generic-webhook-token',
causeString: 'Triggered by PR on opensearch-migrations repository',
regexpFilterExpression: "^$jobName\$",
regexpFilterText: "\$job_name",
)
}

stages {
stage('Checkout') {
steps {
Expand Down Expand Up @@ -92,13 +119,14 @@ def call(Map config = [:]) {
if (config.deployStep) {
config.deployStep()
} else {
echo "Acquired deployment stage: ${stage}"
sh 'sudo usermod -aG docker $USER'
sh 'sudo newgrp docker'
def baseCommand = "sudo --preserve-env ./awsE2ESolutionSetup.sh --source-context-file './$source_context_file_name' " +
"--migration-context-file './$migration_context_file_name' " +
"--source-context-id $source_context_id " +
"--migration-context-id $migration_context_id " +
"--stage ${params.STAGE} " +
"--stage ${stage} " +
"--migrations-git-url ${params.GIT_REPO_URL} " +
"--migrations-git-branch ${params.GIT_BRANCH}"
if (skipCaptureProxyOnNodeSetup) {
Expand Down Expand Up @@ -127,17 +155,16 @@ def call(Map config = [:]) {
} else {
def time = new Date().getTime()
def uniqueId = "integ_min_${time}_${currentBuild.number}"
def test_dir = "/root/lib/integ_test/integ_test"
def test_result_file = "${test_dir}/reports/${uniqueId}/report.xml"
def command = "pipenv run pytest --log-file=${test_dir}/reports/${uniqueId}/pytest.log " +
"--junitxml=${test_result_file} ${test_dir}/replayer_tests.py " +
def test_result_file = "${testDir}/reports/${uniqueId}/report.xml"
def command = "pipenv run pytest --log-file=${testDir}/reports/${uniqueId}/pytest.log " +
"--junitxml=${test_result_file} ${integTestCommand} " +
"--unique_id ${uniqueId} " +
"-s"
withCredentials([string(credentialsId: 'migrations-test-account-id', variable: 'MIGRATIONS_TEST_ACCOUNT_ID')]) {
withAWS(role: 'JenkinsDeploymentRole', roleAccount: "${MIGRATIONS_TEST_ACCOUNT_ID}", duration: 3600, roleSessionName: 'jenkins-session') {
sh "sudo --preserve-env ./awsRunIntegTests.sh --command '${command}' " +
"--test-result-file ${test_result_file} " +
"--stage ${params.STAGE}"
"--stage ${stage}"
}
}
}
Expand Down
57 changes: 57 additions & 0 deletions vars/rfsDefaultE2ETest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Note:
// 1. There is a still a manual step needed on the EC2 source load balancer to replace its security group rule which allows all traffic (0.0.0.0/0) to
// allow traffic for the relevant service security group. This needs a better story around accepting user security groups in our Migration CDK.

def call(Map config = [:]) {
def sourceContextId = 'source-single-node-ec2'
def migrationContextId = 'migration-rfs'
def source_cdk_context = """
{
"source-single-node-ec2": {
"suffix": "ec2-source-<STAGE>",
"networkStackSuffix": "ec2-source-<STAGE>",
"distVersion": "7.10.2",
"distributionUrl": "https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-oss-7.10.2-linux-x86_64.tar.gz",
"captureProxyEnabled": false,
"securityDisabled": true,
"minDistribution": false,
"cpuArch": "x64",
"isInternal": true,
"singleNodeCluster": true,
"networkAvailabilityZones": 2,
"dataNodeCount": 1,
"managerNodeCount": 0,
"serverAccessType": "ipv4",
"restrictServerAccessTo": "0.0.0.0/0"
}
}
"""
def migration_cdk_context = """
{
"migration-rfs": {
"stage": "<STAGE>",
"vpcId": "<VPC_ID>",
"engineVersion": "OS_2.11",
"domainName": "os-cluster-<STAGE>",
"dataNodeCount": 2,
"openAccessPolicyEnabled": true,
"domainRemovalPolicy": "DESTROY",
"artifactBucketRemovalPolicy": "DESTROY",
"trafficReplayerServiceEnabled": false,
"reindexFromSnapshotServiceEnabled": true,
"sourceClusterEndpoint": "<SOURCE_CLUSTER_ENDPOINT>"
}
}
"""

defaultIntegPipeline(
sourceContext: source_cdk_context,
migrationContext: migration_cdk_context,
sourceContextId: sourceContextId,
migrationContextId: migrationContextId,
defaultStageId: 'rfs-integ',
skipCaptureProxyOnNodeSetup: true,
jobName: 'rfs-default-e2e-test',
integTestCommand: '/root/lib/integ_test/integ_test/backfill_tests.py'
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
// 1. There is a still a manual step needed on the EC2 source load balancer to replace its security group rule which allows all traffic (0.0.0.0/0) to
// allow traffic for the relevant service security group. This needs a better story around accepting user security groups in our Migration CDK.

def sourceContextId = 'source-single-node-ec2'
def migrationContextId = 'migration-default'
// These default values should only be used on the initial Jenkins run in order to load parameter options into the UI,
// all future runs should use the specified parameters
def gitBranch = params.GIT_BRANCH ?: 'main'
def gitUrl = params.GIT_REPO_URL ?: 'https://github.com/opensearch-project/opensearch-migrations.git'
def source_cdk_context = """
def call(Map config = [:]) {
def sourceContextId = 'source-single-node-ec2'
def migrationContextId = 'migration-default'
def source_cdk_context = """
{
"source-single-node-ec2": {
"suffix": "ec2-source-<STAGE>",
Expand All @@ -29,8 +26,8 @@ def source_cdk_context = """
"restrictServerAccessTo": "0.0.0.0/0"
}
}
"""
def migration_cdk_context = """
"""
def migration_cdk_context = """
{
"migration-default": {
"stage": "<STAGE>",
Expand All @@ -50,19 +47,17 @@ def migration_cdk_context = """
"migrationAPIEnabled": true
}
}
"""
"""

library identifier: "migrations-lib@${gitBranch}", retriever: modernSCM(
[$class: 'GitSCMSource',
remote: "${gitUrl}"])

defaultIntegPipeline(
sourceContext: source_cdk_context,
migrationContext: migration_cdk_context,
sourceContextId: sourceContextId,
migrationContextId: migrationContextId,
defaultStageId: 'aws-integ',
//deployStep: {
// echo 'Custom Test Step'
//}
)
defaultIntegPipeline(
sourceContext: source_cdk_context,
migrationContext: migration_cdk_context,
sourceContextId: sourceContextId,
migrationContextId: migrationContextId,
defaultStageId: 'aws-integ',
jobName: 'traffic-replay-default-e2e-test',
//deployStep: {
// echo 'Custom Test Step'
//}
)
}

0 comments on commit d2dce08

Please sign in to comment.