diff --git a/.github/workflows/example-test.yaml b/.github/workflows/example-test.yaml new file mode 100644 index 00000000..1e6997d8 --- /dev/null +++ b/.github/workflows/example-test.yaml @@ -0,0 +1,60 @@ +name: Example Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +env: + LOG_DIR: "/tmp/fabric-operator-example-test/logs" + +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +jobs: + build-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build + run: | + scripts/install-tools.sh + make image + - name: save image to /tmp + run: | + docker save hyperledgerk8s/fabric-operator:latest > /tmp/operator.image.tar + - name: Upload operator image + uses: actions/cache/save@v3 + with: + key: operator.image-${{ github.sha }} + path: /tmp/operator.image.tar + + example-test: + needs: + - build-image + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + no: [1, 2, 3, 4, 5] + steps: + - uses: actions/checkout@v3 + - name: Download operator image + uses: actions/cache/restore@v3 + with: + key: operator.image-${{ github.sha }} + path: /tmp/operator.image.tar + - name: Load Docker image + run: | + docker load --input /tmp/operator.image.tar + - name: Example Test + run: config/samples/example-test.sh + - name: Upload logs + if: failure() + uses: actions/upload-artifact@v3 + with: + name: ${{ github.sha }}-${{ matrix.no }}.logs + path: ${{ env.LOG_DIR }} diff --git a/config/samples/example-test.sh b/config/samples/example-test.sh new file mode 100755 index 00000000..e1ad1e7a --- /dev/null +++ b/config/samples/example-test.sh @@ -0,0 +1,645 @@ +#!/bin/bash +# +# Copyright contributors to the Hyperledger Fabric Operator project +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed 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. +# +#set -x +export TERM=xterm-color + +KindName=${KindName:-"fabric-example-test"} +TimeoutSeconds=${TimeoutSeconds:-"600"} +KindVersion=${KindVersion:-"v1.24.4"} +TempFilePath=${TempFilePath:-"/tmp/fabric-operator-example-test"} +KindConfigPath=${TempFilePath}/kind-config.yaml +InstallDirPath=${TempFilePath}/installer +DefaultPassWord=${DefaultPassWord:-'admiN$123'} +LOG_DIR=${LOG_DIR:-"/tmp/fabric-operator-example-test/logs"} + +Timeout="${TimeoutSeconds}s" +mkdir ${TempFilePath} || true + +function debugInfo { + if [[ $? -eq 0 ]]; then + exit 0 + fi + if [[ $debug -ne 0 ]]; then + exit 1 + fi + + warning "debugInfo start ๐Ÿง" + mkdir -p $LOG_DIR + + warning "1. Try to get all resources " + kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl get -A --ignore-not-found=true --show-kind=true >$LOG_DIR/get-all-resources.log + + warning "2. Try to describe all resources " + kubectl api-resources --verbs=list -o name | xargs -n 1 kubectl describe -A >$LOG_DIR/describe-all-resources.log + + warning "3. Try to export kind logs to $LOG_DIR..." + kind export logs --name=${KindName} $LOG_DIR + sudo chown -R $USER:$USER $LOG_DIR + + warning "debugInfo finished ! " + warning "This means that some tests have failed. Please check the log. ๐ŸŒš" + debug=1 + exit 1 +} +trap 'debugInfo $LINENO' ERR +trap 'debugInfo $LINENO' EXIT +debug=0 + +function cecho() { + declare -A colors + colors=( + ['black']='\E[0;47m' + ['red']='\E[0;31m' + ['green']='\E[0;32m' + ['yellow']='\E[0;33m' + ['blue']='\E[0;34m' + ['magenta']='\E[0;35m' + ['cyan']='\E[0;36m' + ['white']='\E[0;37m' + ) + local defaultMSG="No message passed." + local defaultColor="black" + local defaultNewLine=true + while [[ $# -gt 1 ]]; do + key="$1" + case $key in + -c | --color) + color="$2" + shift + ;; + -n | --noline) + newLine=false + ;; + *) + # unknown option + ;; + esac + shift + done + message=${1:-$defaultMSG} # Defaults to default message. + color=${color:-$defaultColor} # Defaults to default color, if not specified. + newLine=${newLine:-$defaultNewLine} + echo -en "${colors[$color]}" + echo -en "$message" + if [ "$newLine" = true ]; then + echo + fi + tput sgr0 # Reset text attributes to normal without clearing screen. + return +} + +function warning() { + cecho -c 'yellow' "$@" +} + +function error() { + cecho -c 'red' "$@" +} + +function info() { + cecho -c 'blue' "$@" +} + +info "1. create kind cluster..." +cat >>${KindConfigPath} </${KindName}-worker/g" ${InstallDirPath}/u4a-component/charts/cluster-component/values.yaml +helm install --wait --timeout=${Timeout} cluster-component -n u4a-system ${InstallDirPath}/u4a-component/charts/cluster-component +if [[ $? -ne 0 ]]; then + exit $? +fi + +info "2.2 install u4a services" +sed -i -e "s//${workerNode1IP}/g" ${InstallDirPath}/u4a-component/values.yaml +sed -i -e "s//${KindName}-worker2/g" ${InstallDirPath}/u4a-component/values.yaml +sed -i -e "s//${workerNode2IP}/g" ${InstallDirPath}/u4a-component/values.yaml +# TODO remove there change after https://github.com/bestchains/installer/pull/2 merged +warning "Temporary testing, Verify that the readinessProbe in oidc-server prevents the pod from being ready!" +cat >>${TempFilePath}/deploy.yaml </${Admin1Token}/g" config/samples/orgs/org1.yaml +sed -i -e "s//${Admin2Token}/g" config/samples/orgs/org2.yaml +sed -i -e "s//${Admin3Token}/g" config/samples/orgs/org3.yaml + +info "4.1.1 create org=org1, wait for the relevant components to start up." +kubectl create -f config/samples/orgs/org1.yaml --dry-run=client -o json | + jq '.spec.caSpec.ingress.class = "'$IngressClassName'"' | jq '.spec.caSpec.storage.ca.class = "'$StorageClassName'"' | + kubectl create --as=org1admin -f - +function waitOrgReady() { + orgName=$1 + START_TIME=$(date +%s) + while true; do + status=$(kubectl get org $orgName --ignore-not-found=true -o json | jq -r .status.type) + if [ "$status" == "Deployed" ]; then + break + fi + + # TODO after finish https://github.com/bestchains/fabric-operator/issues/79, remove check ca status + caStatus=$(kubectl get ibpca -n $orgName $orgName --ignore-not-found=true -o json | jq -r '.status.type') + if [ "$caStatus" == "Deployed" ]; then + break + fi + + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + kubectl describe pod -n $orgName + kubectl get org $orgName -oyaml + kubectl get ibpca -n $orgName $orgName -oyaml + exit 1 + fi + sleep 5 + done +} +waitOrgReady org1 + +info "4.1.2 create org=org2, wait for the relevant components to start up." +kubectl create -f config/samples/orgs/org2.yaml --dry-run=client -o json | + jq '.spec.caSpec.ingress.class = "'$IngressClassName'"' | jq '.spec.caSpec.storage.ca.class = "'$StorageClassName'"' | + kubectl create --as=org2admin -f - +waitOrgReady org2 + +info "4.1.3 create org=org3, wait for the relevant components to start up." +kubectl create -f config/samples/orgs/org3.yaml --dry-run=client -o json | + jq '.spec.caSpec.ingress.class = "'$IngressClassName'"' | jq '.spec.caSpec.storage.ca.class = "'$StorageClassName'"' | + kubectl create --as=org3admin -f - +waitOrgReady org3 + +info "4.2 create federation resources: federation-sample" +kubectl create -f config/samples/ibp.com_v1beta1_federation.yaml --as=org1admin +function waitFed() { + fedName=$1 + check=$2 + START_TIME=$(date +%s) + while true; do + if [[ $check == "Exist" ]]; then + name=$(kubectl get fed $fedName --no-headers --ignore-not-found=true | awk '{print $1}') + if [[ $name != "" ]]; then + break + fi + elif [[ $check == "Activated" ]]; then + status=$(kubectl get fed $fedName -o json | jq -r '.status.type') + if [ "$status" == "FederationActivated" ]; then + break + fi + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + + #todo remove patch after + if [[ $check == "Activated" ]]; then + kubectl patch fed $fedName --subresource=status --type='json' \ + -p='[{"op": "replace", "path": "/status/type", "value": "FederationActivated"}]' + START_TIME=CURRENT_TIME + continue + fi + + error "Timeout reached" + exit 1 + fi + sleep 5 + done +} +waitFed federation-sample "Exist" + +info "4.3 create federation create proposal for fed=federation-sample" + +info "4.3.1 create proposal pro=create-federation-sample" +kubectl create -f config/samples/ibp.com_v1beta1_proposal_create_federation.yaml --as=org1admin + +info "4.3.2 user=org2admin vote for pro=create-federation-sample" +function waitVoteExist() { + ns=$1 + proposalName=$2 + START_TIME=$(date +%s) + while true; do + voteName=$(kubectl get vote -n $ns -l "bestchains.vote.proposal=$proposalName" --no-headers=true --ignore-not-found=true | awk '{print $1}') + if [[ $voteName != "" ]]; then + break + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + exit 1 + fi + sleep 5 + done +} +waitVoteExist org2 create-federation-sample +kubectl patch vote -n org2 vote-org2-create-federation-sample --type='json' \ + -p='[{"op": "replace", "path": "/spec/decision", "value": true}]' --as=org2admin + +info "4.3.3 pro=create-federation-sample become Succeeded" +function waitProposalSucceeded() { + proposalName=$1 + START_TIME=$(date +%s) + while true; do + Type=$(kubectl get pro $proposalName --ignore-not-found=true -o json | jq -r '.status.conditions[] | select(.status=="True") | .type') + if [[ $Type == "Succeeded" ]]; then + break + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + kubectl describe pro $proposalName + exit 1 + fi + sleep 5 + done +} +waitProposalSucceeded create-federation-sample + +info "4.3.4 fed=federation-sample become Activated, federation create finish!" +waitFed federation-sample "Activated" + +info "4.4 network management" +info "4.4.1 create single orderer node network" +sed -i -e "s//${Admin1Token}/g" config/samples/ibp.com_v1beta1_network.yaml +kubectl create -f config/samples/ibp.com_v1beta1_network.yaml --dry-run=client -o json | + jq '.spec.orderSpec.ingress.class = "'$IngressClassName'"' | jq '.spec.orderSpec.storage.orderer.class = "'$StorageClassName'"' | + kubectl create --as=org1admin -f - +function waitNetwork() { + networkName=$1 + orderNs=$2 + want=$3 + START_TIME=$(date +%s) + while true; do + if [[ $want == "NoExist" ]]; then + name=$(kubectl get network $networkName --no-headers=true --ignore-not-found=true | awk '{print $1}') + if [[ $name == "" ]]; then + break + fi + elif [[ $want == "Ready" ]]; then + #todo after https://github.com/bestchains/fabric-operator/issues/83 change these check + Type=$(kubectl get ibporderer -n $orderNs ${networkName} --ignore-not-found=true -o json | jq -r '.status.type') + if [[ $Type == "Deployed" ]]; then + break + fi + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + kubectl describe network $networkName + exit 1 + fi + sleep 5 + done +} +waitNetwork network-sample "org1" "Ready" + +info "4.4.2 create 3 orderer node network" +sed -i -e "s//${Admin1Token}/g" config/samples/ibp.com_v1beta1_network_size_3.yaml +kubectl create -f config/samples/ibp.com_v1beta1_network_size_3.yaml --dry-run=client -o json | + jq '.spec.orderSpec.ingress.class = "'$IngressClassName'"' | jq '.spec.orderSpec.storage.orderer.class = "'$StorageClassName'"' | + kubectl create --as=org1admin -f - +waitNetwork network-sample3 "org1" "Ready" + +info "4.4.3 delete network need create a federation dissolve network proposal for fed=federation-sample network=network-sample" + +info "4.4.3.1 create proposal pro=dissolve-network-sample" +kubectl create -f config/samples/ibp.com_v1beta1_proposal_dissolve_network.yaml --as=org1admin + +info "4.4.3.2 user=org2admin vote for pro=dissolve-network-sample" +waitVoteExist org2 dissolve-network-sample +kubectl patch vote -n org2 vote-org2-dissolve-network-sample --type='json' -p='[{"op": "replace", "path": "/spec/decision", "value": true}]' --as=org2admin + +info "4.4.3.3 pro=dissolve-network-sample become Activated" +#TODO uncomment after https://github.com/bestchains/fabric-operator/issues/87 +#waitProposalSucceeded dissolve-network-sample + +info "4.4.3.4 network=network-sample cant find, deletion finished" +waitNetwork network-sample "" "NoExist" + +info "4.7 channel management" +info "4.7.1 create channel channel=channel-sample" +kubectl create -f config/samples/ibp.com_v1beta1_channel_create.yaml --as=org1admin +function waitChannelReady() { + channelName=$1 + want=$2 + START_TIME=$(date +%s) + while true; do + if [[ $want == "NoExist" ]]; then + kubectl get network $networkName + if [[ $? -eq 1 ]]; then + break + fi + elif [[ $want == "Ready" ]]; then + Type=$(kubectl get channel $channelName --ignore-not-found=true -o json | jq -r '.status.type') + if [[ $Type == "ChannelCreated" ]]; then + break + fi + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + kubectl describe channel $channelName + exit 1 + fi + sleep 5 + done +} +waitChannelReady channel-sample "Ready" + +info "4.7.2 create peer node peer=org1peer1" +Org1CaCert=$(kubectl get cm -norg1 org1-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 -d | jq -r '.tls.cert') +Org1CaURI=$(kubectl get cm -norg1 org1-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 -d | jq -r '.endpoints.api') +function parseURI() { + uri=$1 + # https://stackoverflow.com/a/6174447/5939892 + # extract the protocol + proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')" + # remove the protocol + url="$(echo ${1/$proto/})" + # extract the user (if any) + user="$(echo $url | grep @ | cut -d@ -f1)" + # extract the host and port + hostport="$(echo ${url/$user@/} | cut -d/ -f1)" + # by request host without port + host="$(echo $hostport | sed -e 's,:.*,,g')" + # by request - try to extract the port + port="$(echo $hostport | sed -e 's,^.*:,:,g' -e 's,.*:\([0-9]*\).*,\1,g' -e 's,[^0-9],,g')" + # extract the path (if any) + path="$(echo $url | grep / | cut -d/ -f2-)" + if [[ $port == "" && $proto == "http" ]]; then + port="80" + elif [[ $port == "" && $proto == "https" ]]; then + port="443" + fi +} +parseURI ${Org1CaURI} +Org1CaHost=${host} +Org1CaPort=${port} +sed -i -e "s//${Admin1Token}/g" config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml +sed -i -e "s//${Org1CaCert}/g" config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml +kubectl create -f config/samples/peers/ibp.com_v1beta1_peer_org1peer1.yaml --dry-run=client -o json | + jq '.spec.ingress.class = "'$IngressClassName'"' | + jq '.spec.storage.peer.class = "'$StorageClassName'"' | jq '.spec.storage.statedb.class = "'$StorageClassName'"' | + jq '.spec.secret.enrollment.component.cahost = "'$Org1CaHost'"' | jq '.spec.secret.enrollment.tls.cahost = "'$Org1CaHost'"' | + jq '.spec.secret.enrollment.component.caport = "'$Org1CaPort'"' | jq '.spec.secret.enrollment.tls.caport = "'$Org1CaPort'"' | + kubectl create --as=org1admin -f - +function waitPeerReady() { + peerName=$1 + ns=$2 + want=$3 + START_TIME=$(date +%s) + while true; do + if [[ $want == "" ]]; then + Type=$(kubectl get ibppeer -n $ns $peerName --ignore-not-found=true -o json | jq -r '.status.type') + if [[ $Type == "Deployed" ]]; then + break + fi + fi + CURRENT_TIME=$(date +%s) + ELAPSED_TIME=$((CURRENT_TIME - START_TIME)) + if [ $ELAPSED_TIME -gt $TimeoutSeconds ]; then + error "Timeout reached" + kubectl describe ibppeer -n $ns $peerName + exit 1 + fi + sleep 5 + done +} +waitPeerReady org1peer1 org1 + +info "4.7.3 create peer node peer=org2peer1" +Org2CaCert=$(kubectl get cm -norg2 org2-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 -d | jq -r '.tls.cert') +Org2CaURI=$(kubectl get cm -norg2 org2-connection-profile -ojson | jq -r '.binaryData."profile.json"' | base64 -d | jq -r '.endpoints.api') +parseURI ${Org2CaURI} +Org2CaHost=${host} +Org2CaPort=${port} +sed -i -e "s//${Admin2Token}/g" config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml +sed -i -e "s//${Org2CaCert}/g" config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml +kubectl create -f config/samples/peers/ibp.com_v1beta1_peer_org2peer1.yaml --dry-run=client -o json | + jq '.spec.ingress.class = "'$IngressClassName'"' | + jq '.spec.storage.peer.class = "'$StorageClassName'"' | jq '.spec.storage.statedb.class = "'$StorageClassName'"' | + jq '.spec.secret.enrollment.component.cahost = "'$Org2CaHost'"' | jq '.spec.secret.enrollment.tls.cahost = "'$Org2CaHost'"' | + jq '.spec.secret.enrollment.component.caport = "'$Org2CaPort'"' | jq '.spec.secret.enrollment.tls.caport = "'$Org2CaPort'"' | + kubectl create --as=org2admin -f - +waitPeerReady org2peer1 org2 + +info "4.7.4 add peer node to channel peer=org1peer1 channel=channel-sample" +kubectl apply -f config/samples/ibp.com_v1beta1_channel_join_org1.yaml --as=org1admin +# todo Verify that peers successfully join channel +sleep 5 + +info "4.7.5 add peer node to channel peer=org2peer1 channel=channel-sample" +kubectl apply -f config/samples/ibp.com_v1beta1_channel_join_org2.yaml --as=org2admin +# todo Verify that peers successfully join channel + +info "all finished! โœ…" diff --git a/config/samples/ibp.com_v1beta1_channel_join_org1.yaml b/config/samples/ibp.com_v1beta1_channel_join_org1.yaml new file mode 100644 index 00000000..60e784a8 --- /dev/null +++ b/config/samples/ibp.com_v1beta1_channel_join_org1.yaml @@ -0,0 +1,15 @@ +apiVersion: ibp.com/v1beta1 +kind: Channel +metadata: + name: channel-sample +spec: + license: + accept: true + network: "network-sample3" + members: + - name: org1 + - name: org2 + peers: + - name: org1peer1 + namespace: org1 + description: "channel with org1 & org2" diff --git a/config/samples/ibp.com_v1beta1_channel_join.yaml b/config/samples/ibp.com_v1beta1_channel_join_org2.yaml similarity index 100% rename from config/samples/ibp.com_v1beta1_channel_join.yaml rename to config/samples/ibp.com_v1beta1_channel_join_org2.yaml diff --git a/config/samples/readme.md b/config/samples/readme.md index 42e835bc..88a2cc10 100644 --- a/config/samples/readme.md +++ b/config/samples/readme.md @@ -2892,5 +2892,6 @@ kubectl apply -f config/samples/ibp.com_v1beta1_channel_create.yaml #### 2. peer่Š‚็‚นๅŠ ๅ…ฅ้€š้“ ```bash -kubectl apply -f config/samples/ibp.com_v1beta1_channel_join.yaml +kubectl apply -f config/samples/ibp.com_v1beta1_channel_join_org1.yaml +kubectl apply -f config/samples/ibp.com_v1beta1_channel_join_org2.yaml ```