From fee6a44fcd3610ba612a6c0455e98642183eff24 Mon Sep 17 00:00:00 2001 From: jkneubuh <86427252+jkneubuh@users.noreply.github.com> Date: Tue, 8 Feb 2022 10:51:02 -0500 Subject: [PATCH] Run a basic-asset-transfer CI test on Kubernetes (#637) Signed-off-by: Josh Kneubuhl --- .../application-gateway-typescript/src/app.ts | 51 +++++-- ci/azure-pipelines.yml | 14 ++ ci/scripts/run-k8s-test-network-basic.sh | 144 ++++++++++++++++++ ci/templates/install-k8s-deps.yml | 9 ++ test-network-k8s/scripts/test_network.sh | 8 +- 5 files changed, 212 insertions(+), 14 deletions(-) create mode 100755 ci/scripts/run-k8s-test-network-basic.sh create mode 100644 ci/templates/install-k8s-deps.yml diff --git a/asset-transfer-basic/application-gateway-typescript/src/app.ts b/asset-transfer-basic/application-gateway-typescript/src/app.ts index bd4dff49..3dda3fff 100644 --- a/asset-transfer-basic/application-gateway-typescript/src/app.ts +++ b/asset-transfer-basic/application-gateway-typescript/src/app.ts @@ -11,29 +11,35 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import { TextDecoder } from 'util'; -const channelName = 'mychannel'; -const chaincodeName = 'basic'; -const mspId = 'Org1MSP'; +const channelName = envOrDefault('CHANNEL_NAME', 'mychannel'); +const chaincodeName = envOrDefault('CHAINCODE_NAME', 'basic'); +const mspId = envOrDefault('MSP_ID', 'Org1MSP'); // Path to crypto materials. -const cryptoPath = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'); +const cryptoPath = envOrDefault('CRYPTO_PATH', path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com')); // Path to user private key directory. -const keyDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'); +const keyDirectoryPath = envOrDefault('KEY_DIRECTORY_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore')); // Path to user certificate. -const certPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem'); +const certPath = envOrDefault('CERT_PATH', path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem')); // Path to peer tls certificate. -const tlsCertPath = path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); +const tlsCertPath = envOrDefault('TLS_CERT_PATH', path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt')); // Gateway peer endpoint. -const peerEndpoint = 'localhost:7051'; +const peerEndpoint = envOrDefault('PEER_ENDPOINT', 'localhost:7051'); + +// Gateway peer SSL host name override. +const peerHostAlias = envOrDefault('PEER_HOST_ALIAS', 'peer0.org1.example.com'); const utf8Decoder = new TextDecoder(); const assetId = `asset${Date.now()}`; async function main(): Promise { + + await displayInputParameters(); + // The gRPC client connection should be shared by all Gateway connections to this endpoint. const client = await newGrpcConnection(); @@ -86,13 +92,16 @@ async function main(): Promise { } } -main().catch(error => console.error('******** FAILED to run the application:', error)); +main().catch(error => { + console.error('******** FAILED to run the application:', error); + process.exitCode = 1; +}); async function newGrpcConnection(): Promise { const tlsRootCert = await fs.readFile(tlsCertPath); const tlsCredentials = grpc.credentials.createSsl(tlsRootCert); return new grpc.Client(peerEndpoint, tlsCredentials, { - 'grpc.ssl_target_name_override': 'peer0.org1.example.com', + 'grpc.ssl_target_name_override': peerHostAlias, }); } @@ -205,3 +214,25 @@ async function updateNonExistentAsset(contract: Contract): Promise{ console.log('*** Successfully caught the error: \n', error); } } + +/** + * envOrDefault() will return the value of an environment variable, or a default value if the variable is undefined. + */ +function envOrDefault(key: string, defaultValue: string): string { + return process.env[key] || defaultValue; +} + +/** + * displayInputParameters() will print the global scope parameters used by the main driver routine. + */ +async function displayInputParameters(): Promise { + console.log(`channelName: ${channelName}`); + console.log(`chaincodeName: ${chaincodeName}`); + console.log(`mspId: ${mspId}`); + console.log(`cryptoPath: ${cryptoPath}`); + console.log(`keyDirectoryPath: ${keyDirectoryPath}`); + console.log(`certPath: ${certPath}`); + console.log(`tlsCertPath: ${tlsCertPath}`); + console.log(`peerEndpoint: ${peerEndpoint}`); + console.log(`peerHostAlias: ${peerHostAlias}`); +} \ No newline at end of file diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 039bd53f..f3ce962f 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -127,6 +127,20 @@ jobs: workingDirectory: test-network displayName: Run Test Network Basic Chaincode + - job: KubeTestNetworkBasic + displayName: Kube Test Network Basic + pool: + vmImage: ubuntu-20.04 + strategy: + matrix: + Docker-Typescript: + CLIENT_LANGUAGE: typescript + steps: + - template: templates/install-k8s-deps.yml + - script: ../ci/scripts/run-k8s-test-network-basic.sh + workingDirectory: test-network-k8s + displayName: Run Kubernetes Test Network Basic Asset Transfer + - job: TestNetworkLedger displayName: Test Network pool: diff --git a/ci/scripts/run-k8s-test-network-basic.sh b/ci/scripts/run-k8s-test-network-basic.sh new file mode 100755 index 00000000..6ce21f47 --- /dev/null +++ b/ci/scripts/run-k8s-test-network-basic.sh @@ -0,0 +1,144 @@ +#!/bin/bash -e +# +# Copyright IBM Corp All Rights Reserved +# +# SPDX-License-Identifier: Apache-2.0 +# +set -euo pipefail + +# Test matrix parameters +export CONTAINER_CLI=${CONTAINER_CLI:-docker} +export CLIENT_LANGUAGE=${CLIENT_LANGUAGE:-typescript} + +# Fabric version and Docker registry source: use the latest stable tag image from JFrog +export FABRIC_VERSION=${FABRIC_VERSION:-2.4} +export TEST_NETWORK_FABRIC_CONTAINER_REGISTRY=hyperledger-fabric.jfrog.io +export TEST_NETWORK_FABRIC_VERSION=amd64-${FABRIC_VERSION}-stable +export TEST_NETWORK_FABRIC_CA_VERSION=amd64-${FABRIC_VERSION}-stable + +# test-network-k8s parameters +export TEST_TAG=$(git describe) +export TEST_NETWORK_KIND_CLUSTER_NAME=${TEST_NETWORK_KIND_CLUSTER_NAME:-kind} +export TEST_NETWORK_CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic} +export TEST_NETWORK_CHAINCODE_IMAGE=${TEST_NETWORK_CHAINCODE_NAME}:${TEST_TAG} +export TEST_NETWORK_CHAINCODE_PATH=${TEST_NETWORK_CHAINCODE_PATH:-../asset-transfer-basic/chaincode-external} + +# gateway client application parameters +export GATEWAY_CLIENT_APPLICATION_PATH=${GATEWAY_CLIENT_APPLICATION_PATH:-../asset-transfer-basic/application-gateway-${CLIENT_LANGUAGE}} +export CHANNEL_NAME=${TEST_NETWORK_CHANNEL_NAME:-mychannel} +export CHAINCODE_NAME=${TEST_NETWORK_CHAINCODE_NAME:-asset-transfer-basic} +export MSP_ID=${MSP_ID:-Org1MSP} +export CRYPTO_PATH=${CRYPTO_PATH:-../../test-network-k8s/build/organizations/peerOrganizations/org1.example.com} +export KEY_DIRECTORY_PATH=${KEY_DIRECTORY_PATH:-../../test-network-k8s/build/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore} +export CERT_PATH=${CERT_PATH:-../../test-network-k8s/build/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/cert.pem} +export TLS_CERT_PATH=${TLS_CERT_PATH:-../../test-network-k8s/build/organizations/peerOrganizations/org1.example.com/msp/tlscacerts/org1-tls-ca.pem} +export PEER_ENDPOINT=${PEER_ENDPOINT:-localhost:7051} +export PEER_HOST_ALIAS=${PEER_HOST_ALIAS:-org1-peer1} + +function print() { + GREEN='\033[0;32m' + NC='\033[0m' + echo + echo -e "${GREEN}${1}${NC}" +} + +function touteSuite() { + createCluster + buildChaincodeImage +} + +function quitterLaScene() { + destroyCluster + scrubCCImages +} + +function createCluster() { + print "Initializing KIND Kubernetes cluster" + ./network kind +} + +function destroyCluster() { + print "Destroying KIND Kubernetes cluster" + ./network unkind +} + +function buildChaincodeImage() { + print "Building chaincode image $TEST_NETWORK_CHAINCODE_IMAGE" + ${CONTAINER_CLI} build -t $TEST_NETWORK_CHAINCODE_IMAGE $TEST_NETWORK_CHAINCODE_PATH + + # todo: work with local reg, or k3s, or KIND, or ... + kind load docker-image $TEST_NETWORK_CHAINCODE_IMAGE +} + +function scrubCCImages() { + print "Scrubbing chaincode images" + ${CONTAINER_CLI} rmi $TEST_NETWORK_CHAINCODE_IMAGE +} + +function createNetwork() { + print "Launching network" + ./network up + ./network channel create + + print "Opening gateway port-forward to 'localhost:7051'" + kubectl -n test-network port-forward svc/org1-peer1 7051:7051 & + + print "Deploying chaincode" + ./network chaincode deploy + + print "Extracting certificates" + kubectl \ + -n test-network \ + exec deploy/org1-peer1 \ + -c main \ + -- tar zcf - -C /var/hyperledger/fabric organizations/peerOrganizations/org1.example.com \ + | tar zxvf - -C ../test-network-k8s/build/ +} + +function stopNetwork() { + pkill -f "port-forward" + + print "Stopping network" + ./network down + + print "Cleaning client certificates" + rm -rf ../test-network-k8s/build/ +} + +# Set up the suite with a KIND cluster +touteSuite +trap "quitterLaScene" EXIT + +# invoke / query +createNetwork + +print "Inserting and querying assets" +( ./network chaincode invoke '{"Args":["InitLedger"]}' \ + && sleep 5 \ + && ./network chaincode query '{"Args":["ReadAsset","asset1"]}' ) +print "OK" + +print "Running rest-easy test" +( ./network rest-easy \ + && sleep 5 \ + && export SAMPLE_APIKEY='97834158-3224-4CE7-95F9-A148C886653E' \ + && curl -s --header "X-Api-Key: ${SAMPLE_APIKEY}" "http://localhost/api/assets/asset1" | jq \ + && curl -s --insecure --header "X-Api-Key: ${SAMPLE_APIKEY}" "https://localhost/api/assets/asset1" | jq ) +print "OK" + +stopNetwork + +# Run the basic-asset-transfer basic application +createNetwork +print "Running Gateway client application" +( pushd ${GATEWAY_CLIENT_APPLICATION_PATH} \ + && npm install \ + && npm start ) +print "OK" +stopNetwork + +# Run additional test ... +# Run additional test ... +# Run additional test ... + +# destroyCluster will be invoked on EXIT trap handler at the end of this suite. diff --git a/ci/templates/install-k8s-deps.yml b/ci/templates/install-k8s-deps.yml new file mode 100644 index 00000000..006a2d41 --- /dev/null +++ b/ci/templates/install-k8s-deps.yml @@ -0,0 +1,9 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +steps: + - task: NodeTool@0 + inputs: + versionSpec: $(NODE_VER) + displayName: Install Node.js diff --git a/test-network-k8s/scripts/test_network.sh b/test-network-k8s/scripts/test_network.sh index ee2faf09..70094fff 100755 --- a/test-network-k8s/scripts/test_network.sh +++ b/test-network-k8s/scripts/test_network.sh @@ -91,8 +91,8 @@ function create_org1_local_MSP() { fabric-ca-client register --id.name org1-peer2 --id.secret peerpw --id.type peer --url https://org1-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-ca/rcaadmin/msp fabric-ca-client register --id.name org1-admin --id.secret org1adminpw --id.type admin --url https://org1-ca --mspdir $FABRIC_CA_CLIENT_HOME/org1-ca/rcaadmin/msp --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*,hf.Revoker=true,hf.GenCRL=true,admin=true:ecert,abac.init=true:ecert" - fabric-ca-client enroll --url https://org1-peer1:peerpw@org1-ca --csr.hosts org1-peer1,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp - fabric-ca-client enroll --url https://org1-peer2:peerpw@org1-ca --csr.hosts org1-peer2,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/msp + fabric-ca-client enroll --url https://org1-peer1:peerpw@org1-ca --csr.hosts localhost,org1-peer1,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer1.org1.example.com/msp + fabric-ca-client enroll --url https://org1-peer2:peerpw@org1-ca --csr.hosts localhost,org1-peer2,org1-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/peers/org1-peer2.org1.example.com/msp fabric-ca-client enroll --url https://org1-admin:org1adminpw@org1-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp cp /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/server.key @@ -130,8 +130,8 @@ function create_org2_local_MSP() { fabric-ca-client register --id.name org2-peer2 --id.secret peerpw --id.type peer --url https://org2-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-ca/rcaadmin/msp fabric-ca-client register --id.name org2-admin --id.secret org2adminpw --id.type admin --url https://org2-ca --mspdir $FABRIC_CA_CLIENT_HOME/org2-ca/rcaadmin/msp --id.attrs "hf.Registrar.Roles=client,hf.Registrar.Attributes=*,hf.Revoker=true,hf.GenCRL=true,admin=true:ecert,abac.init=true:ecert" - fabric-ca-client enroll --url https://org2-peer1:peerpw@org2-ca --csr.hosts org2-peer1,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp - fabric-ca-client enroll --url https://org2-peer2:peerpw@org2-ca --csr.hosts org2-peer2,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/msp + fabric-ca-client enroll --url https://org2-peer1:peerpw@org2-ca --csr.hosts localhost,org2-peer1,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer1.org2.example.com/msp + fabric-ca-client enroll --url https://org2-peer2:peerpw@org2-ca --csr.hosts localhost,org2-peer2,org2-peer-gateway-svc --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/peers/org2-peer2.org2.example.com/msp fabric-ca-client enroll --url https://org2-admin:org2adminpw@org2-ca --mspdir /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp cp /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/*_sk /var/hyperledger/fabric/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp/keystore/server.key