From b039afd905be743a520c4f689b8c48551fa9cb0e Mon Sep 17 00:00:00 2001 From: themanforfree Date: Thu, 19 Jan 2023 12:10:48 +0800 Subject: [PATCH] Execute etcd validation test automatically close #151 --- .github/workflows/validation.yml | 34 +++++++ .gitignore | 1 + .mergify.yml | 3 +- scripts/quick_start.sh | 13 ++- scripts/validation_test.sh | 164 +++++++++++++++++++++++++++++++ xline/src/bin/lock_client.rs | 51 ++++++++++ 6 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/validation.yml create mode 100755 scripts/validation_test.sh create mode 100644 xline/src/bin/lock_client.rs diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 000000000..f1c629d21 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,34 @@ +name: Validation + +on: + pull_request: + branches: [ master ] + +env: + CI_RUST_TOOLCHAIN: 1.61.0 + +jobs: + becnmark: + name: validation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: sudo bash ./.github/scripts/install_deps.sh + - uses: actions-rs/toolchain@v1 + with: + profile: default + toolchain: ${{ env.CI_RUST_TOOLCHAIN }} + override: true + - run: RUSTFLAGS="-C target-feature=+crt-static" cargo build --release --bin lock_client --target x86_64-unknown-linux-gnu + - uses: actions-rs/cargo@v1 + with: + command: build + args: --release + - run: | + cd scripts + cp ../target/release/{xline,benchmark} . + cp ../target/x86_64-unknown-linux-gnu/release/lock_client . + cp ../xline/tests/{private,public}.pem . + docker build . -t datenlord/xline:latest + docker pull gcr.io/etcd-development/etcd:v3.5.5 + bash ./validation_test.sh diff --git a/.gitignore b/.gitignore index af2517d58..b8e5d38e4 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ scripts/* !scripts/benchmark.sh !scripts/quick_start.sh !scripts/coverage.sh +!scripts/validation_test.sh coverage *.profraw diff --git a/.mergify.yml b/.mergify.yml index 824a32301..6a8a0f64a 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,7 +1,7 @@ pull_request_rules: - name: Automatic merge on approval conditions: - - "#approved-reviews-by>=1" + - "#approved-reviews-by>=2" - check-success=Outdated - check-success=Audit - check-success=Check @@ -9,6 +9,7 @@ pull_request_rules: - check-success=Test - check-success=Clippy - check-success=Coverage + - check-success=Validation actions: merge: method: rebase diff --git a/scripts/quick_start.sh b/scripts/quick_start.sh index 8251464a2..aeb143b81 100755 --- a/scripts/quick_start.sh +++ b/scripts/quick_start.sh @@ -1,5 +1,8 @@ #!/bin/bash -WORKDIR=$(pwd) +DIR=$( + cd "$(dirname "$0")" + pwd +) SERVERS=("172.20.0.2" "172.20.0.3" "172.20.0.4" "172.20.0.5") MEMBERS="node1=${SERVERS[1]}:2379,node2=${SERVERS[2]}:2379,node3=${SERVERS[3]}:2379" @@ -9,7 +12,9 @@ MEMBERS="node1=${SERVERS[1]}:2379,node2=${SERVERS[2]}:2379,node3=${SERVERS[3]}:2 run_xline() { cmd="/usr/local/bin/xline \ --name node${1} \ - --members ${MEMBERS}" + --members ${MEMBERS} \ + --auth-public-key /mnt/public.pem \ + --auth-private-key /mnt/private.pem" if [ ${1} -eq 1 ]; then cmd="${cmd} --is-leader" @@ -45,9 +50,9 @@ run_container() { size=${1} image="datenlord/xline:latest" for ((i = 1; i <= ${size}; i++)); do - docker run -d -it --rm --name=node${i} --net=xline_net --ip=172.20.0.$((i + 2)) --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v ${WORKDIR}:/mnt ${image} bash & + docker run -d -it --rm --name=node${i} --net=xline_net --ip=172.20.0.$((i + 2)) --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v ${DIR}:/mnt ${image} bash & done - docker run -d -it --rm --name=node4 --net=xline_net --ip=172.20.0.2 --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v ${WORKDIR}:/mnt gcr.io/etcd-development/etcd:v3.5.5 bash & + docker run -d -it --rm --name=node4 --net=xline_net --ip=172.20.0.2 --cap-add=NET_ADMIN --cpu-shares=1024 -m=512M -v ${DIR}:/mnt gcr.io/etcd-development/etcd:v3.5.5 bash & wait echo container started } diff --git a/scripts/validation_test.sh b/scripts/validation_test.sh new file mode 100755 index 000000000..4034ecd11 --- /dev/null +++ b/scripts/validation_test.sh @@ -0,0 +1,164 @@ +DIR="$(dirname $0)" +QUICK_START="${DIR}/quick_start.sh" +bash ${QUICK_START} +ETCDCTL="docker exec -i node4 etcdctl --endpoints=http://172.20.0.3:2379" +LOCK_CLIENT="docker exec -i node4 /mnt/lock_client --endpoints=http://172.20.0.3:2379" + +# run a command with expect output +# args: +# $1: command to run +# $2: expect output +run_with_expect() { + cmd="${1}" + res=$(eval ${cmd}) + # echo -e "res:\n${res}\n" + expect=$(echo -e ${2}) + if [ "${res}" == "${expect}" ]; then + echo "command run success" + else + echo "command run failed" + echo "command: ${cmd}" + echo "expect: ${expect}" + echo "result: ${res}" + exit 1 + fi +} + +# validate kv requests +kv_validation() { + echo "kv validation test running..." + run_with_expect "${ETCDCTL} put key1 value1" "OK" + stdin='value(\"key1\") = \"value1\"\n\nput key2 success\n\nput key2 failure\n' + run_with_expect "echo -e \"${stdin}\" | ${ETCDCTL} txn" "SUCCESS\n\nOK" + run_with_expect "${ETCDCTL} get key2" "key2\nsuccess" + run_with_expect "${ETCDCTL} del key1" "1" + # TODO compact + echo "kv validation test passed" +} + +# validate watch requests +watch_validation() { + echo "watch validation test running..." + command="${ETCDCTL} watch watch_key" + want=("PUT" "watch_key" "value" "DELETE" "watch_key" "value" "watch_key") + ${command} | while read line; do + if [ "${line}" == "${want[0]}" ]; then + unset want[0] + want=("${want[@]}") + else + echo "command run failed" + echo "command: ${command}" + echo "expect: ${want[0]}" + echo "result: ${line}" + exit 1 + fi + done & + sleep 0.1 # wait watch + run_with_expect "${ETCDCTL} put watch_key value" "OK" + run_with_expect "${ETCDCTL} del watch_key" "1" + echo "watch validation test passed" +} + +# validate lease requests +lease_validation() { + echo "lease validation test running..." + command="${ETCDCTL} lease grant 5" + out=$(${command}) + patten='lease [0-9a-z]+ granted with TTL\([0-9]+s\)' + if [[ ${out} =~ ${patten} ]]; then + echo "command run success" + else + echo "command run failed" + echo "command: ${command}" + echo "expect: ${patten}" + echo "result: ${out}" + exit 1 + fi + lease_id=$(echo ${out} | awk '{print $2}') + run_with_expect "${ETCDCTL} lease list" "found 1 leases\n${lease_id}" + command="${ETCDCTL} lease keep-alive ${lease_id}" + ${command} | while read line; do + + if [ "${line}" != "lease ${lease_id} keepalived with TTL(5)" ]; then + if [ "${line}" != "lease ${lease_id} expired or revoked." ]; then + echo "command run failed" + echo "command: ${command}" + echo "expect: lease ${lease_id} keepalived with TTL(5) or lease ${lease_id} expired or revoked." + echo "result: ${line}" + exit 1 + fi + fi + done & + run_with_expect "${ETCDCTL} put key1 value1 --lease=${lease_id}" "OK" + run_with_expect "${ETCDCTL} get key1" "key1\nvalue1" + command="${ETCDCTL} lease timetolive ${lease_id}" + remaining=$(${command} | sed -r "s/lease ${lease_id} granted with TTL\(5s\), remaining\(([0-9]+)s\)/\1/g") + if [ ${remaining} -le 5 ]; then + echo "command run success" + else + echo "command run failed" + echo "command: ${command}" + echo "remaining should be less than 5, but got ${remaining}" + exit 1 + fi + + run_with_expect "${ETCDCTL} lease revoke ${lease_id}" "lease ${lease_id} revoked" + run_with_expect "${ETCDCTL} get key1" "" + echo "lease validation test passed" +} + +# validate auth requests +auth_validation() { + echo "auth validation test running..." + run_with_expect "${ETCDCTL} user add root:root" "User root created" + run_with_expect "${ETCDCTL} role add root" "Role root created" + run_with_expect "${ETCDCTL} user grant-role root root" "Role root is granted to user root" + run_with_expect "${ETCDCTL} auth enable" "Authentication Enabled" + run_with_expect "${ETCDCTL} --user root:root auth status" "Authentication Status: true\nAuthRevision: 5" + run_with_expect "${ETCDCTL} --user root:root user add u:u" "User u created" + run_with_expect "${ETCDCTL} --user root:root role add r" "Role r created" + run_with_expect "${ETCDCTL} --user root:root user grant-role u r" "Role r is granted to user u" + run_with_expect "${ETCDCTL} --user root:root role grant-permission r readwrite key1" "Role r updated" + run_with_expect "${ETCDCTL} --user u:u put key1 value1" "OK" + run_with_expect "${ETCDCTL} --user u:u get key1" "key1\nvalue1" + run_with_expect "${ETCDCTL} --user u:u role get r" "Role r\nKV Read:\n\tkey1\nKV Write:\n\tkey1" + run_with_expect "${ETCDCTL} --user u:u user get u" "User: u\nRoles: r" + run_with_expect "echo 'new_password' | ${ETCDCTL} --user root:root user passwd --interactive=false u" "Password updated" + run_with_expect "${ETCDCTL} --user root:root role revoke-permission r key1" "Permission of key key1 is revoked from role r" + run_with_expect "${ETCDCTL} --user root:root user revoke-role u r" "Role r is revoked from user u" + run_with_expect "${ETCDCTL} --user root:root user list" "root\nu" + run_with_expect "${ETCDCTL} --user root:root role list" "r\nroot" + run_with_expect "${ETCDCTL} --user root:root user delete u" "User u deleted" + run_with_expect "${ETCDCTL} --user root:root role delete r" "Role r deleted" + run_with_expect "${ETCDCTL} --user root:root auth disable" "Authentication Disabled" + echo "auth validation test passed" +} + +lock_validation() { + echo "lock validation test running..." + run_with_expect "${ETCDCTL} lock mutex echo success" "success" + echo "lock validation test passed" +} + +lock_rpc_validation() { + echo "lock rpc validation test running..." + command="${LOCK_CLIENT} lock mutex" + key=$(${command}) + if [[ ${key} == mutex* ]]; then + echo "command run success" + else + echo "command run failed" + echo "command: ${command}" + echo "expect: mutex*" + echo "result: ${key}" + fi + run_with_expect "${LOCK_CLIENT} unlock ${key}" "unlock success" + echo "lock rpc validation test passed" +} + +kv_validation +watch_validation +lease_validation +auth_validation +lock_validation +lock_rpc_validation diff --git a/xline/src/bin/lock_client.rs b/xline/src/bin/lock_client.rs new file mode 100644 index 000000000..27e102f64 --- /dev/null +++ b/xline/src/bin/lock_client.rs @@ -0,0 +1,51 @@ +//! this binary is only used for the validation of lock service + +use clap::{Parser, Subcommand}; +use etcd_client::Client; + +#[derive(Parser, Debug, Clone)] +struct ClientArgs { + #[clap(short, long, value_delimiter = ',')] + endpoints: Vec, + #[clap(subcommand)] + command: Commands, +} + +/// Types of sub command +#[derive(Subcommand, Debug, Clone)] +pub enum Commands { + /// Lock args + Lock { + /// Lock name + #[clap(value_parser)] + name: String, + }, + /// UnLock args + Unlock { + /// Lock name + #[clap(value_parser)] + key: String, + }, +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + let args: ClientArgs = ClientArgs::parse(); + let endpoints = if args.endpoints.is_empty() { + vec!["http://127.0.0.1:2379".to_owned()] + } else { + args.endpoints + }; + let mut client = Client::connect(endpoints, None).await?; + match args.command { + Commands::Lock { name } => { + let lock_res = client.lock(name, None).await?; + println!("{}", String::from_utf8_lossy(lock_res.key())) + } + Commands::Unlock { key } => { + let _unlock_res = client.unlock(key).await?; + println!("unlock success"); + } + }; + Ok(()) +}