From 6c0203a9b0bc6f2b520331984d7eb37933daa473 Mon Sep 17 00:00:00 2001 From: rameshthoomu Date: Wed, 13 Mar 2019 17:12:14 -0400 Subject: [PATCH] FABCI-284 Update CI Pipeline script This patch updates CI pipeline scripts which utilizes global shared library reusable functions. It also creates ci.properties file with all the parameters required to test the fabric-samples patch. Change-Id: Ib2fd948eae9f2e37535144489279773836400358 Signed-off-by: rameshthoomu --- Jenkinsfile | 294 +++++++++--------- README.md | 4 + basic-network/start.sh | 1 + ci.properties | 21 ++ docs/fabric-samples-ci.md | 109 +++++++ docs/pipeline_flow.png | Bin 0 -> 32711 bytes fabcar/startFabric.sh | 1 + first-network/byfn.sh | 6 + scripts/ci_scripts/byfn_eyfn.sh | 75 +++++ scripts/ci_scripts/ciScript.sh | 54 ++++ .../{Jenkins_Scripts => ci_scripts}/fabcar.sh | 3 +- 11 files changed, 422 insertions(+), 146 deletions(-) create mode 100644 ci.properties create mode 100644 docs/fabric-samples-ci.md create mode 100644 docs/pipeline_flow.png create mode 100755 scripts/ci_scripts/byfn_eyfn.sh create mode 100755 scripts/ci_scripts/ciScript.sh rename scripts/{Jenkins_Scripts => ci_scripts}/fabcar.sh (95%) diff --git a/Jenkinsfile b/Jenkinsfile index 72f0cea1..21965e54 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,153 +1,157 @@ +#!groovy // Copyright IBM Corp All Rights Reserved // // SPDX-License-Identifier: Apache-2.0 // -// Pipeline script for fabric-samples +// Jenkinfile will get triggered on verify and merge jobs and run byfn, eyfn and fabcar +// tests. -node ('hyp-x') { // trigger build on x86_64 node - timestamps { - try { - def ROOTDIR = pwd() // workspace dir (/w/workspace/ - def nodeHome = tool 'nodejs-8.11.3' // NodeJs version - env.ARCH = "amd64" - env.VERSION = sh(returnStdout: true, script: 'curl -O https://raw.githubusercontent.com/hyperledger/fabric/release-1.4/Makefile && cat Makefile | grep "PREV_VERSION =" | cut -d "=" -f2').trim() - env.VERSION = "$VERSION" // PREV_VERSION from fabric Makefile - env.BASE_IMAGE_VER = sh(returnStdout: true, script: 'cat Makefile | grep "BASEIMAGE_RELEASE=" | cut -d "=" -f2').trim() // BASEIMAGE Version from fabric Makefile - env.BASE_IMAGE_TAG = "${ARCH}-${BASE_IMAGE_VER}" //fabric baseimage version - env.PROJECT_DIR = "gopath/src/github.com/hyperledger" - env.GOPATH = "$WORKSPACE/gopath" - env.PATH = "$GOPATH/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:${nodeHome}/bin:$PATH" - def jobname = sh(returnStdout: true, script: 'echo ${JOB_NAME} | grep -q "verify" && echo patchset || echo merge').trim() - def failure_stage = "none" - // delete working directory - deleteDir() - stage("Fetch Patchset") { // fetch gerrit refspec on latest commit - try { - if (jobname == "patchset") { - println "$GERRIT_REFSPEC" - println "$GERRIT_BRANCH" - checkout([ - $class: 'GitSCM', - branches: [[name: '$GERRIT_REFSPEC']], - extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'gopath/src/github.com/hyperledger/$PROJECT'], [$class: 'CheckoutOption', timeout: 10]], - userRemoteConfigs: [[credentialsId: 'hyperledger-jobbuilder', name: 'origin', refspec: '$GERRIT_REFSPEC:$GERRIT_REFSPEC', url: '$GIT_BASE']]]) - } else { - // Clone fabric-samples on merge - println "Clone $PROJECT repository" - checkout([ - $class: 'GitSCM', - branches: [[name: 'refs/heads/$GERRIT_BRANCH']], - extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'gopath/src/github.com/hyperledger/$PROJECT']], - userRemoteConfigs: [[credentialsId: 'hyperledger-jobbuilder', name: 'origin', refspec: '+refs/heads/$GERRIT_BRANCH:refs/remotes/origin/$GERRIT_BRANCH', url: '$GIT_BASE']]]) - } - dir("${ROOTDIR}/$PROJECT_DIR/$PROJECT") { - sh ''' - # Print last two commit details - echo - git log -n2 --pretty=oneline --abbrev-commit - echo - ''' - } +// global shared library from ci-management repository +// https://github.com/hyperledger/ci-management/tree/master/vars (Global Shared scripts) +@Library("fabric-ci-lib") _ + pipeline { + agent { + // Execute tests on x86_64 build nodes + // Set this value from Jenkins Job Configuration + label env.NODE_ARCH + } + options { + // Using the Timestamper plugin we can add timestamps to the console log + timestamps() + // Set build timeout for 60 mins + timeout(time: 60, unit: 'MINUTES') + } + environment { + ROOTDIR = pwd() + // Applicable only on x86_64 nodes + // LF team has to install the newer version in Jenkins global config + // Send an email to helpdesk@hyperledger.org to add newer version + nodeHome = tool 'nodejs-8.11.3' + MARCH = sh(returnStdout: true, script: "uname -m | sed 's/x86_64/amd64/g'").trim() + OS_NAME = sh(returnStdout: true, script: "uname -s|tr '[:upper:]' '[:lower:]'").trim() + props = "null" + } + stages { + stage('Clean Environment') { + steps { + script { + // delete working directory + deleteDir() + // Clean build env before start the build + fabBuildLibrary.cleanupEnv() + // Display jenkins environment details + fabBuildLibrary.envOutput() + } } - catch (err) { - failure_stage = "Fetch patchset" - currentBuild.result = 'FAILURE' - throw err - } -} - // clean environment and get env data - stage("Clean Environment - Get Env Info") { - try { - dir("${ROOTDIR}/$PROJECT_DIR/fabric-samples/scripts/Jenkins_Scripts") { - sh './CI_Script.sh --clean_Environment --env_Info' - } - } - catch (err) { - failure_stage = "Clean Environment - Get Env Info" - currentBuild.result = 'FAILURE' - throw err - } - } - - // Pull Third_party Images - stage("Pull third_party Images") { - // making the output color coded - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { - try { - dir("${ROOTDIR}/$PROJECT_DIR/fabric-samples/scripts/Jenkins_Scripts") { - sh './CI_Script.sh --pull_Thirdparty_Images' - } - } - catch (err) { - failure_stage = "Pull third_party docker images" - currentBuild.result = 'FAILURE' - throw err - } - } - } - - // Pull Fabric, fabric-ca Images - stage("Pull Docker Images") { - // making the output color coded - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { - try { - dir("${ROOTDIR}/$PROJECT_DIR/fabric-samples/scripts/Jenkins_Scripts") { - sh './CI_Script.sh --pull_Docker_Images' - } - } - catch (err) { - failure_stage = "Pull fabric, fabric-ca docker images" - currentBuild.result = 'FAILURE' - throw err - } - } - } - - // Run byfn, eyfn tests (default, custom channel, couchdb, nodejs chaincode) - stage("Run byfn_eyfn Tests") { - // making the output color coded - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { - try { - dir("${ROOTDIR}/$PROJECT_DIR/fabric-samples/scripts/Jenkins_Scripts") { - sh './CI_Script.sh --byfn_eyfn_Tests' - } - } - catch (err) { - failure_stage = "byfn_eyfn_Tests" - currentBuild.result = 'FAILURE' - throw err - } - } - } - - // Run fabcar tests - stage("Run FabCar Tests") { - // making the output color coded - wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { - try { - dir("${ROOTDIR}/$PROJECT_DIR/fabric-samples/scripts/Jenkins_Scripts") { - sh './CI_Script.sh --fabcar_Tests' - } - } - catch (err) { - failure_stage = "fabcar_Tests" - currentBuild.result = 'FAILURE' - throw err - } - } - } - } finally { - // Archive the artifacts - archiveArtifacts allowEmptyArchive: true, artifacts: '**/*.log' - // Sends notification to Rocket.Chat jenkins-robot channel - if (env.JOB_NAME == "fabric-samples-merge-job") { - if (currentBuild.result == 'FAILURE') { // Other values: SUCCESS, UNSTABLE - rocketSend message: "Build Notification - STATUS: *${currentBuild.result}* - BRANCH: *${env.GERRIT_BRANCH}* - PROJECT: *${env.PROJECT}* - (<${env.BUILD_URL}|Open>)" - } - } } -// End Timestamps block - } -// End Node block -} + stage('Checkout SCM') { + steps { + script { + // Get changes from gerrit + fabBuildLibrary.cloneRefSpec('fabric-samples') + // Load properties from ci.properties file + props = fabBuildLibrary.loadProperties() + } + } + } + // Pull build artifacts + stage('Pull Build Artifacts') { + steps { + script { + if(props["IMAGE_SOURCE"] == "build") { + println "BUILD ARTIFACTS" + // Set PATH + env.GOPATH = "$WORKSPACE/gopath" + env.GOROOT = "/opt/go/go" + props["GO_VER"] + ".linux." + "$MARCH" + env.PATH = "$GOPATH/bin:$GOROOT/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:${nodeHome}/bin:$PATH" + // Clone fabric repo + fabBuildLibrary.cloneScm('fabric', '$GERRIT_BRANCH') + // Build fabric docker images and binaries + fabBuildLibrary.fabBuildImages('fabric', 'docker dist') + // Clone fabric-ca repo + fabBuildLibrary.cloneScm('fabric-ca', '$GERRIT_BRANCH') + // Build fabric docker images and binaries + fabBuildLibrary.fabBuildImages('fabric-ca', 'docker dist') + // Copy binaries to fabric-samples dir + sh 'cp -r $ROOTDIR/gopath/src/github.com/hyperledger/fabric/release/$OS_NAME-$MARCH/bin $ROOTDIR/$BASE_DIR/' + // Pull Thirdparty Docker Images from hyperledger DockerHub + fabBuildLibrary.pullThirdPartyImages(props["FAB_BASEIMAGE_VERSION"], props["FAB_THIRDPARTY_IMAGES_LIST"]) + } else { + dir("$ROOTDIR/$BASE_DIR") { + // Set PATH + env.GOPATH = "$WORKSPACE/gopath" + env.GOROOT = "/opt/go/go" + props["GO_VER"] + ".linux." + "$MARCH" + env.PATH = "$GOPATH/bin:$GOROOT/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:${nodeHome}/bin:$PATH" + // Pull Binaries with latest version from nexus2 + fabBuildLibrary.pullBinaries(props["FAB_BINARY_VER"], props["FAB_BINARY_REPO"]) + // Pull Docker Images from nexus3 + fabBuildLibrary.pullDockerImages(props["FAB_BASE_VERSION"], props["FAB_IMAGES_LIST"]) + // Pull Thirdparty Docker Images from hyperledger DockerHub + fabBuildLibrary.pullThirdPartyImages(props["FAB_BASEIMAGE_VERSION"], props["FAB_THIRDPARTY_IMAGES_LIST"]) + } + } + } + } + } + // Run byfn, eyfn tests (default, custom channel, couchdb, nodejs, java chaincode) + stage('Run byfn_eyfn Tests') { + steps { + script { + // making the output color coded + wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { + try { + dir("$ROOTDIR/$BASE_DIR/scripts/ci_scripts") { + // Run BYFN, EYFN tests + sh './ciScript.sh --byfn_eyfn_Tests' + } + } + catch (err) { + failure_stage = "byfn_eyfn_Tests" + currentBuild.result = 'FAILURE' + throw err + } + } + } + } + } + // Run fabcar tests + stage('Run Fab Car Tests') { + steps { + script { + // making the output color coded + wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) { + try { + dir("$ROOTDIR/$BASE_DIR/scripts/ci_scripts") { + // Run fabcar tests + sh './ciScript.sh --fabcar_Tests' + } + } + catch (err) { + failure_stage = "fabcar_Tests" + currentBuild.result = 'FAILURE' + throw err + } + } + } + } + } + } // stages + post { + always { + // Archiving the .log files and ignore if empty + archiveArtifacts artifacts: '**/*.log', allowEmptyArchive: true + } + failure { + script { + if (env.JOB_TYPE == 'merge') { + // Send rocketChat notification to channel + // Send merge build failure email notifications to the submitter + sendNotifications(currentBuild.result, props["CHANNEL_NAME"]) + // Delete workspace when build is done + cleanWs notFailBuild: true + } + } + } + } // post + } // pipeline diff --git a/README.md b/README.md index 3946e594..71164f7e 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,10 @@ are 1.4.0, 1.4.0 and 0.4.14 respectively. ./scripts/bootstrap.sh [version] [ca version] [thirdparty_version] ``` +### Continuous Integration + +Please have a look at [Continuous Integration Process](docs/fabric-samples-ci.md) + ## License Hyperledger Project source code files are made available under the Apache diff --git a/basic-network/start.sh b/basic-network/start.sh index 246605bd..43e08a9a 100755 --- a/basic-network/start.sh +++ b/basic-network/start.sh @@ -13,6 +13,7 @@ export MSYS_NO_PATHCONV=1 docker-compose -f docker-compose.yml down docker-compose -f docker-compose.yml up -d ca.example.com orderer.example.com peer0.org1.example.com couchdb +docker ps -a # wait for Hyperledger Fabric to start # incase of errors when running later commands, issue export FABRIC_START_TIMEOUT= diff --git a/ci.properties b/ci.properties new file mode 100644 index 00000000..a1fbb1c2 --- /dev/null +++ b/ci.properties @@ -0,0 +1,21 @@ +# Set "nexus" if you would like to pull images from nexus3 +# Set "build" if you would like build fabric, fabric-ca on latest commits +IMAGE_SORUCE=nexus +# set only "javaenv" image if you set IMAGE_SOURCE to "build" +# Pull below list of images from nexus3 if IMAGE_SOURCE is set to "nexus" or "build" +FAB_IMAGES_LIST=ca peer orderer ccenv tools javaenv +# Set fabric if you would like pull only fabric binaries +FAB_BINARY_REPO=fabric fabric-ca +# Pull below list of images from Hyperledger DockerHub +FAB_THIRDPARTY_IMAGES_LIST=kafka zookeeper couchdb +# Pull latest binaries of latest commit of release-1.4 from nexus snapshots +# Applicable only when set IMAGE_SOURCE to "nexus" +FAB_BINARY_VER=1.4.1-stable +# Set base version from fabric branch +FAB_BASE_VERSION=1.4.1 +# Set base image version from fabric branch +FAB_BASEIMAGE_VERSION=0.4.15 +# Set related rocketChat channel name. Default: jenkins-robot +CHANNEL_NAME=jenkins-robot +# Set compaitable go version +GO_VER=1.11.5 diff --git a/docs/fabric-samples-ci.md b/docs/fabric-samples-ci.md new file mode 100644 index 00000000..cfaf3516 --- /dev/null +++ b/docs/fabric-samples-ci.md @@ -0,0 +1,109 @@ +# Continuous Integration Process + +This document explains the fabric-samples Jenkins pipeline flow and FAQ's on the build +process to help developer to get more familiarize with the process flow. + +We use Jenkins as a CI tool and to manage jobs, we use [JJB](https://docs.openstack.org/infra/jenkins-job-builder). +Please see the pipeline job configuration template here https://ci-docs.readthedocs.io/en/latest/source/pipeline_jobs.html#job-templates. + +## CI Pipeline flow + +- Every Gerrit patchset triggers a verify job with the Gerrit Refspec on the parent commit + of the patchset and runs the below tests from the `Jenkinsfile`. Note: when you are ready + to merge the patchset, it's always a best practice to rebase the patchset on the latest commit. + +All the below tests runs on the Hyperledger infarstructure x86_64 build nodes. All these nodes +uses the packer with pre-configured software packages. This helps us to run the tests in much +faster than installing required packages everytime. + +Below steps shows what each stage does in the Jenkins pipeline verify and merge flow. +Before execute the below tests, it clean the environment (Deletes the left over build artifiacts) +and clone the repository with the Gerrit Refspec. + +![](pipeline_flow.png) + +Based on the value provided to **IMAGE_SOURCE** in ci.properties file, Jenkinsfile execute the +steps. If the IMAGE_SOURCE is set to "build", Jenkins clones the latest commits of fabric +and fabric-ca and builds the docker images and binaries. If you specify IMAGE_SOURCE as "nexus", +it always pulls the latest fabric and fabric-ca images published from nightly job which triggers +everyday at 8:00 PM EST. + +Also, it pulls the "javaenv" and "nodeenv" images from the latest published images from nexus3. +NOTE: nodeenv image is available only in master branch. + +Once the artifacts stage is ready, Jenkins executes the below tests + +- byfn & eyfn tests + - on default channel + - Custom channel with couchdb + - on node chanincode + +- fabcar tests + - go + - javascript + - typescript + +Above pipeline flow works the same on both `fabric-samples-verify-x86_64` and `fabric-samples-merge-x86_64` pipeline jobs. + +See below **FAQ's** for more information on the pipeline process. + +## FAQ's + +#### How to re-trigger failed tests? + +You can post comments `reverify` or `reverify-x` on the gerrit patchset to trigger the `fabric-samples-verify-x86_64` +job which triggers the pipeline flow as mentioned above. Also, we provided `remerge` or `remerge-x` +comment phrases to re-trigger the failed merge job. + +#### Where should I see the output of the stages? + +Piepline supports two views (stages and blueocean). **Staged views** shows on the Jenkins job +main page and it shows each stage in order and the status. For better view, we suggest you +to access BlueOcean plugin. Click on the build number and click on the **Open Blue Ocean** +link that shows the build stages in pipeline view. + +#### How to add more stages to this pipeline flow? + +We use scripted pipeline syntax with groovy and shell scripts. Also, we use global shared +library scripts which are placed in https://github.com/hyperledger/ci-management/tree/master/vars. +Try to leverage these common functions in your code. All you have to do is, undestand the pipeline +flow of the tests and conditions, add more stages as mentioned in the existing Jenkinsfile. + +#### How will I get build failure notifications? + +On every merge failure, we send build failure email notications to the submitter of the +patchset and sends the build details to the Rocket Chat **jenkins-robot** channel. Check the +result here https://chat.hyperledger.org/channel/jenkins-robot + +#### What steps I have to modify when I create a new branch from master? + +As the Jenkinsfile is completely parametrzed, you no need to modify anything in the +Jenkinsfile but you may endup modifying **ci.properties** file with the appropirate +Base Versions, Baseimage versions etc... in the new branch. We suggest you to modify this +file immediately after you create a new branch to avoid running tests on older versions. + +#### On what platforms these tests triggers? + +- x86_64 (Run the above mentioned tests on verify and merge jobs) +- s390x (Run the above mentioned tests in daily jobs) + +#### Where can I see the Build Scripts. + +We use global shared library scripts and Jenkinsfile along with the build file. + +Global Shared Library - https://github.com/hyperledger/ci-management/tree/master/vars + +Jenkinsfile - https://github.com/hyperledger/fabric-samples/tree/release-1.4/Jenkinsfile + +ci.properties - https://github.com/hyperledger/fabric-samples/tree/release-1.4/ci.properties +(ci.properties is the only file you have to modify with the values requried for the specific branch.) + +Packer Scripts - https://github.com/hyperledger/ci-management/blob/master/packer/provision/docker.sh +(Packer is a tool for automatically creating VM and container images, configuring them and +post-processing them into standard output formats. We build Hyperledger's CI images via Packer +and attach them to x86_64 build nodes. On s390x, we install manually. See the packages we +install as a pre-requisite in the CI x86 build nodes.) + +#### How to reach out to CI team? + +Post your questions or feedback in https://chat.hyperledger.org/channel/ci-pipeline or https://chat.hyperledger.org/channel/fabric-ci Rocket Chat channels. Also, you can create JIRA tasks or bugs in FABCI project. https://jira.hyperledger.org/projects/FABCI \ No newline at end of file diff --git a/docs/pipeline_flow.png b/docs/pipeline_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..b1d98011fd2dd06f784663d1a5fcb676603f8bca GIT binary patch literal 32711 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 Tg*`pIfPZ8ql_V-Y83p_wZQ&9x literal 0 HcmV?d00001 diff --git a/fabcar/startFabric.sh b/fabcar/startFabric.sh index 7a2fcaee..e57ec129 100755 --- a/fabcar/startFabric.sh +++ b/fabcar/startFabric.sh @@ -44,6 +44,7 @@ cd ../basic-network # Now launch the CLI container in order to install, instantiate chaincode # and prime the ledger with our 10 cars docker-compose -f ./docker-compose.yml up -d cli +docker ps -a docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fabcar -v 1.0 -p "$CC_SRC_PATH" -l "$CC_RUNTIME_LANGUAGE" docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -l "$CC_RUNTIME_LANGUAGE" -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.member','Org2MSP.member')" diff --git a/first-network/byfn.sh b/first-network/byfn.sh index 965e4db0..cb893bb4 100755 --- a/first-network/byfn.sh +++ b/first-network/byfn.sh @@ -159,18 +159,24 @@ function networkUp() { if [ "${IF_COUCHDB}" == "couchdb" ]; then if [ "$CONSENSUS_TYPE" == "kafka" ]; then IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_KAFKA -f $COMPOSE_FILE_COUCH up -d 2>&1 + docker ps -a elif [ "$CONSENSUS_TYPE" == "etcdraft" ]; then IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_RAFT2 -f $COMPOSE_FILE_COUCH up -d 2>&1 + docker ps -a else IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1 + docker ps -a fi else if [ "$CONSENSUS_TYPE" == "kafka" ]; then IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_KAFKA up -d 2>&1 + docker ps -a elif [ "$CONSENSUS_TYPE" == "etcdraft" ]; then IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_RAFT2 up -d 2>&1 + docker ps -a else IMAGE_TAG=$IMAGETAG docker-compose -f $COMPOSE_FILE up -d 2>&1 + docker ps -a fi fi if [ $? -ne 0 ]; then diff --git a/scripts/ci_scripts/byfn_eyfn.sh b/scripts/ci_scripts/byfn_eyfn.sh new file mode 100755 index 00000000..635b07bc --- /dev/null +++ b/scripts/ci_scripts/byfn_eyfn.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# + +# docker container list +CONTAINER_LIST=(peer0.org1 peer1.org1 peer0.org2 peer1.org2 peer0.org3 peer1.org3 orderer) +COUCHDB_CONTAINER_LIST=(couchdb0 couchdb1 couchdb2 couchdb3 couchdb4 couchdb5) + +cd $WORKSPACE/$BASE_DIR/first-network +# export path +export PATH=$WORKSPACE/$BASE_DIR/bin:$PATH + +logs() { + # Create Logs directory + mkdir -p $WORKSPACE/Docker_Container_Logs + for CONTAINER in ${CONTAINER_LIST[*]}; do + docker logs $CONTAINER.example.com >& $WORKSPACE/Docker_Container_Logs/$CONTAINER-$1.log + echo + done +} + +if [ ! -z $2 ]; then + for CONTAINER in ${COUCHDB_CONTAINER_LIST[*]}; do + docker logs $CONTAINER >& $WORKSPACE/Docker_Container_Logs/$CONTAINER-$1.log + echo + done +fi + +copy_logs() { + # Call logs function + logs $2 $3 + if [ $1 != 0 ]; then + echo -e "\033[31m $2 test case is FAILED" "\033[0m" + exit 1 + fi +} + +echo " ################ " +echo -e "\033[1m DEFAULT CHANNEL\033[0m" +echo " # ############## " +set -x +echo y | ./byfn.sh -m down +echo y | ./byfn.sh -m up -t 60 +copy_logs $? default-channel +echo y | ./eyfn.sh -m up -t 60 +copy_logs $? default-channel +echo y | ./eyfn.sh -m down +set +x +echo + +echo " ############################ " +echo -e "\033[1mCUSTOM CHANNEL - COUCHDB\033[0m" +echo " # ########################## " +set -x +echo y | ./byfn.sh -m up -c custom-channel-couchdb -s couchdb -t 75 -d 15 +copy_logs $? custom-channel-couch couchdb +echo y | ./eyfn.sh -m up -c custom-channel-couchdb -s couchdb -t 75 -d 15 +copy_logs $? custom-channel-couch +echo y | ./eyfn.sh -m down +set +x +echo + +echo " #################################### " +echo -e "\033[1m NODE CHAINCODE\033[0m" +echo " # ################################## " +set -x +echo y | ./byfn.sh -m up -l node -t 60 +copy_logs $? default-channel-node +echo y | ./eyfn.sh -m up -l node -t 60 +copy_logs $? default-channel-node +echo y | ./eyfn.sh -m down +set +x diff --git a/scripts/ci_scripts/ciScript.sh b/scripts/ci_scripts/ciScript.sh new file mode 100755 index 00000000..f2edebf1 --- /dev/null +++ b/scripts/ci_scripts/ciScript.sh @@ -0,0 +1,54 @@ +#!/bin/bash -e +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# + +# exit on first error + +Parse_Arguments() { + while [ $# -gt 0 ]; do + case $1 in + --byfn_eyfn_Tests) + byfn_eyfn_Tests + ;; + --fabcar_Tests) + fabcar_Tests + ;; + esac + shift + done +} + +# run byfn,eyfn tests +byfn_eyfn_Tests() { + + echo + echo " ____ __ __ _____ _ _ _____ __ __ _____ _ _ " + echo " | __ ) \ \ / / | ___| | \ | | | ____| \ \ / / | ___| | \ | | " + echo " | _ \ \ V / | |_ | \| | _____ | _| \ V / | |_ | \| | " + echo " | |_) | | | | _| | |\ | |_____| | |___ | | | _| | |\ | " + echo " |____/ |_| |_| |_| \_| |_____| |_| |_| |_| \_| " + + ./byfn_eyfn.sh +} +# run fabcar tests +fabcar_Tests() { + + echo " #############################" + echo "npm version ------> $(npm -v)" + echo "node version ------> $(node -v)" + echo " #############################" + + echo + echo " _____ _ ____ ____ _ ____ " + echo " | ___| / \ | __ ) / ___| / \ | _ \ " + echo " | |_ / _ \ | _ \ | | / _ \ | |_) | " + echo " | _| / ___ \ | |_) | | |___ / ___ \ | _ < " + echo " |_| /_/ \_\ |____/ \____| /_/ \_\ |_| \_\ " + + ./fabcar.sh +} + +Parse_Arguments $@ diff --git a/scripts/Jenkins_Scripts/fabcar.sh b/scripts/ci_scripts/fabcar.sh similarity index 95% rename from scripts/Jenkins_Scripts/fabcar.sh rename to scripts/ci_scripts/fabcar.sh index d47d4682..7a70f74d 100755 --- a/scripts/Jenkins_Scripts/fabcar.sh +++ b/scripts/ci_scripts/fabcar.sh @@ -28,11 +28,12 @@ if [ $1 != 0 ]; then fi } -cd $BASE_FOLDER/fabric-samples/fabcar || exit +cd $WORKSPACE/$BASE_DIR/fabcar || exit export PATH=gopath/src/github.com/hyperledger/fabric-samples/bin:$PATH LANGUAGES="go javascript typescript" for LANGUAGE in ${LANGUAGES}; do + echo -e "\033[1m ${LANGUAGE} Test\033[0m" echo -e "\033[32m starting fabcar test (${LANGUAGE})" "\033[0m" # Start Fabric, and deploy the smart contract ./startFabric.sh ${LANGUAGE}