diff --git a/.github/workflows/terraform.yaml b/.github/workflows/terraform.yaml new file mode 100644 index 0000000..0986458 --- /dev/null +++ b/.github/workflows/terraform.yaml @@ -0,0 +1,79 @@ +name: Terraform in Daily Test + +on: + schedule: + - cron: '0 18 * * *' # 每天 UTC 时间 18:00,即中国时间凌晨 2 点 + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Check GitHub Secrets + run: | + if [ -z "${{ secrets.MYAPP_GITHUB_TOKEN }}" ]; then + echo "GitHub Token is missing" + else + echo "GitHub Token is set" + fi + + if [ -z "${{ secrets.MYAPP_USER_EMAIL }}" ]; then + echo "User Email is missing" + else + echo "User Email is set" + fi + + if [ -z "${{ secrets.MYAPP_USER_NAME }}" ]; then + echo "User Name is missing" + else + echo "User Name is set" + fi + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: dailyTest + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.9.4 + + - name: Initialize Terraform + env: + TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} + TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} + TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} + TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} + TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} + run: terraform -chdir=./Terraform/Aliyun init + + - name: Apply Terraform Configuration + env: + TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} + TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} + TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} + TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} + TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} + run: terraform -chdir=./Terraform/Aliyun apply -auto-approve + + - name: Print current directory and file list + run: | + cd ./Terraform/Aliyun + echo "Current directory:" + pwd + echo "Files in the current directory:" + ls + + - name: Wait for 20 minutes before destroying resources + run: sleep 1200 # 等待 20 分钟 + + - name: Destroy Terraform Configuration + env: + TF_VAR_access_key: ${{ secrets.ALI_ACCESS_KEY }} + TF_VAR_secret_key: ${{ secrets.ALI_SECRET_KEY }} + TF_VAR_github_token: ${{ secrets.MYAPP_GITHUB_TOKEN }} + TF_VAR_user_name: ${{ secrets.MYAPP_USER_NAME }} + TF_VAR_user_email: ${{ secrets.MYAPP_USER_EMAIL }} + run: terraform -chdir=./Terraform/Aliyun destroy -auto-approve + diff --git a/.gitignore b/.gitignore index c6b1494..ba26600 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ test.json venv html -__pycache__ \ No newline at end of file +__pycache__ diff --git a/Terraform/Aliyun/main.tf b/Terraform/Aliyun/main.tf new file mode 100644 index 0000000..de03435 --- /dev/null +++ b/Terraform/Aliyun/main.tf @@ -0,0 +1,338 @@ +variable "access_key" { + description = "Access key for Alicloud provider" + type = string +} + +variable "secret_key" { + description = "Secret key for Alicloud provider" + type = string +} + +variable "github_token" { + description = "GitHub token for accessing GitHub API" + type = string +} + +variable "user_name" { + description = "GitHub user name" + type = string +} + +variable "user_email" { + description = "GitHub user email" + type = string +} + +locals { + selected_zone_id = "cn-hongkong-b" +} + +provider "alicloud" { + access_key = var.access_key + secret_key = var.secret_key + region = "cn-hongkong" +} + +# 创建 VPC +resource "alicloud_vpc" "my_vpc" { + vpc_name = "MyVPC" + cidr_block = "172.16.0.0/24" +} + +# 创建 VSwitch +resource "alicloud_vswitch" "my_vswitch" { + vswitch_name = "glcc_vswitch" + vpc_id = alicloud_vpc.my_vpc.id + cidr_block = "172.16.0.0/24" + zone_id = local.selected_zone_id +} + +resource "alicloud_security_group" "my_sg" { + name = "glcc_test_security_group" + vpc_id = alicloud_vpc.my_vpc.id + description = "Security Group for testing" +} + +resource "alicloud_security_group_rule" "allow_ssh" { + security_group_id = alicloud_security_group.my_sg.id + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "22/22" + priority = 1 + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "allow_http" { + security_group_id = alicloud_security_group.my_sg.id + type = "ingress" + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "80/80" + priority = 100 + cidr_ip = "0.0.0.0/0" +} + +resource "alicloud_security_group_rule" "allow_https_outbound" { + type = "egress" + security_group_id = alicloud_security_group.my_sg.id + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "443/443" + cidr_ip = "0.0.0.0/0" + description = "Allow outbound HTTPS traffic to external services" +} + +resource "alicloud_security_group_rule" "allow_redis" { + type = "ingress" + security_group_id = alicloud_security_group.my_sg.id + ip_protocol = "tcp" + nic_type = "intranet" + policy = "accept" + port_range = "6379/17005" + cidr_ip = "172.16.0.10/16" # 仅允许特定 ECS 实例的私有 IP 访问 + description = "Allow inbound Redis traffic" +} + +# 云原生 1GB 标准版 Tair 6.0 +resource "alicloud_kvstore_instance" "my_tair_standard" { + db_instance_name = "glcc_tair_standard" + instance_class = "tair.rdb.1g" + instance_type = "Redis" + engine_version = "7.0" + zone_id = local.selected_zone_id + vswitch_id = alicloud_vswitch.my_vswitch.id + payment_type = "PostPaid" + password = "T123456@*" + security_ips = ["172.16.0.10"] +} + + +# 云原生 1GB 集群版 Tair 6.0 +resource "alicloud_kvstore_instance" "my_tair_cluster" { + db_instance_name = "glcc_tair_cluster" + instance_class = "tair.rdb.with.proxy.1g" + instance_type = "Redis" + engine_version = "7.0" + shard_count = "8" + zone_id = local.selected_zone_id + vswitch_id = alicloud_vswitch.my_vswitch.id + payment_type = "PostPaid" + password = "T123456@*" + security_ips = ["172.16.0.10"] +} + +# 云原生 1GB 标准版 Redis 6.0 +resource "alicloud_kvstore_instance" "my_redis_standard" { + db_instance_name = "glcc_redis_standard" + instance_class = "redis.shard.small.2.ce" + instance_type = "Redis" + engine_version = "7.0" + zone_id = local.selected_zone_id + vswitch_id = alicloud_vswitch.my_vswitch.id + payment_type = "PostPaid" + password = "T123456@*" + security_ips = ["172.16.0.10"] +} + + +# 云原生 1GB 集群版 Redis 6.0 +resource "alicloud_kvstore_instance" "my_redis_cluster" { + db_instance_name = "glcc_redis_cluster" + instance_class = "redis.shard.with.proxy.small.ce" + instance_type = "Redis" + engine_version = "7.0" + shard_count = "8" + zone_id = local.selected_zone_id + vswitch_id = alicloud_vswitch.my_vswitch.id + payment_type = "PostPaid" + password = "T123456@*" + security_ips = ["172.16.0.10"] +} + + +# 输出实例信息,目前用于验证 +output "tair_standard_instance_address" { + value = alicloud_kvstore_instance.my_tair_standard.connection_domain +} + +output "tair_standard_instance_port" { + value = alicloud_kvstore_instance.my_tair_standard.private_connection_port +} + +output "tair_standard_instance_password" { + value = alicloud_kvstore_instance.my_tair_standard.password + sensitive = true +} + +output "tair_cluster_instance_address" { + value = alicloud_kvstore_instance.my_tair_cluster.connection_domain +} + +output "tair_cluster_instance_port" { + value = alicloud_kvstore_instance.my_tair_cluster.private_connection_port +} + +output "tair_cluster_instance_password" { + value = alicloud_kvstore_instance.my_tair_cluster.password + sensitive = true +} + +output "redis_standard_instance_address" { + value = alicloud_kvstore_instance.my_redis_standard.connection_domain +} + +output "redis_standard_instance_port" { + value = alicloud_kvstore_instance.my_redis_standard.private_connection_port +} + +output "redis_standard_instance_password" { + value = alicloud_kvstore_instance.my_redis_standard.password + sensitive = true +} + +output "redis_cluster_instance_address" { + value = alicloud_kvstore_instance.my_redis_cluster.connection_domain +} + +output "redis_cluster_instance_port" { + value = alicloud_kvstore_instance.my_redis_cluster.private_connection_port +} + +output "redis_cluster_instance_password" { + value = alicloud_kvstore_instance.my_redis_cluster.password + sensitive = true +} + +# 创建 ECS 实例 +resource "alicloud_instance" "my_ecs" { + private_ip = "172.16.0.10" + instance_type = "ecs.g6.xlarge" + security_groups = [alicloud_security_group.my_sg.id] + instance_charge_type = "PostPaid" + internet_charge_type = "PayByTraffic" + internet_max_bandwidth_out = 10 + image_id = "ubuntu_22_04_x64_20G_alibase_20240130.vhd" + instance_name = "glcc_ecs" + vswitch_id = alicloud_vswitch.my_vswitch.id + system_disk_category = "cloud_efficiency" + password = "T123456@*" + + lifecycle { + create_before_destroy = true + } + + user_data = <=6.0" +git config --global credential.helper 'store' +source /etc/profile + +# 写入数据库配置信息 +cat <> /root/db_config.yml +AliyunTair: + host: ${alicloud_kvstore_instance.my_tair_standard.connection_domain} + port: ${alicloud_kvstore_instance.my_tair_standard.private_connection_port} + password: T123456@* + ssl: false + cluster: false + version: 7.0 + +AliyunTairCluster: + host: ${alicloud_kvstore_instance.my_tair_cluster.connection_domain} + port: ${alicloud_kvstore_instance.my_tair_cluster.private_connection_port} + password: T123456@* + ssl: false + cluster: true + version: 7.0 + +AliyunRedis: + host: ${alicloud_kvstore_instance.my_redis_standard.connection_domain} + port: ${alicloud_kvstore_instance.my_redis_standard.private_connection_port} + password: T123456@* + ssl: false + cluster: false + version: 7.0 + +AliyunRedisCluster: + host: ${alicloud_kvstore_instance.my_redis_cluster.connection_domain} + port: ${alicloud_kvstore_instance.my_redis_cluster.private_connection_port} + password: T123456@* + ssl: false + cluster: true + version: 7.0 +EOT + + +# 拉取和运行数据库容器 +docker pull docker.io/redis:latest +docker run --name redis -d -p 6379:6379 redis + +docker pull docker.dragonflydb.io/dragonflydb/dragonfly:latest +docker run --name dragonflydb -d -p 6380:6379 docker.dragonflydb.io/dragonflydb/dragonfly + +docker pull apache/kvrocks:latest +docker run --name kvrocks -d -p 6381:6666 apache/kvrocks + +docker pull docker.io/eqalpha/keydb:latest +docker run --name keydb -d -p 6382:6379 eqalpha/keydb + +docker pull docker.io/pikadb/pika:latest +docker run --name pika -d -p 6383:6383 pikadb/pika + +docker pull valkey/valkey:latest +docker run --name valkey -d -p 6384:6379 valkey/valkey + + +# 配置Git +echo "https://${var.github_token}:x-oauth-basic@github.com" > ~/.git-credentials +git config --global user.name "${var.user_name}" +git config --global user.email "${var.user_email}" + +git clone https://github.com/redis/redis.git +cd redis +make -j +cd utils/create-cluster + ./create-cluster start +yes yes | ./create-cluster create + +# 可替换为含有dailyTest分支仓库的url +REPO_URL="https://github.com/tair-opensource/resp-compatibility.git" +RETRY_COUNT=0 +MAX_RETRIES=10 +SLEEP_DURATION=30 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + if git clone $REPO_URL; then + echo "Git clone succeeded" + break + else + RETRY_COUNT=$((RETRY_COUNT + 1)) + echo "Git clone failed, attempt $RETRY_COUNT/$MAX_RETRIES" + sleep $SLEEP_DURATION + fi +done + +if [ $RETRY_COUNT -eq $MAX_RETRIES ]; then + echo "Git clone failed after $MAX_RETRIES attempts" >&2 + exit 1 +fi + +cd resp-compatibility +git checkout dailyTest +python3 conn.py +EOF +} + + +output "ecs_ip_address" { + value = alicloud_instance.my_ecs.private_ip +} + diff --git a/config.yaml b/config.yaml index 13b3600..42e6dbd 100644 --- a/config.yaml +++ b/config.yaml @@ -14,31 +14,39 @@ SpecificVersion: # the host, port, password of all the db to be tested Database: Redis: - host: 127.0.0.1 + host: 172.16.0.10 port: 6379 password: ssl: false cluster: false - version: unstable - - DragonflyDB: + version: latest + + RedisCluster: host: 127.0.0.1 - port: 6380 + port: 30001 + password: + ssl: false + cluster: true + version: latest + + Valkey: + host: 172.16.0.10 + port: 6384 password: ssl: false cluster: false version: latest Kvrocks: - host: 127.0.0.1 + host: 172.16.0.10 port: 6381 password: ssl: false cluster: false - version: 2.2.0 + version: latest KeyDB: - host: 127.0.0.1 + host: 172.16.0.10 port: 6382 password: ssl: false @@ -46,29 +54,13 @@ Database: version: latest Pika: - host: 127.0.0.1 - port: 6383 + host: 172.17.0.6 + port: 9221 password: ssl: false cluster: false version: latest - Tair: - host: - port: - password: - ssl: - cluster: false - version: - - Kvstore: - host: - port: - password: - ssl: - cluster: false - version: - ElastiCache: host: port: @@ -115,4 +107,4 @@ Database: password: ssl: cluster: false - version: \ No newline at end of file + version: diff --git a/conn.py b/conn.py new file mode 100644 index 0000000..b35907f --- /dev/null +++ b/conn.py @@ -0,0 +1,102 @@ +import os +import threading +import time +import yaml +import subprocess + +# 更新配置文件 +try: + yml_file_path = '/root/db_config.yml' + config_file_path = 'config.yaml' + + with open(yml_file_path, 'r') as yml_file, open(config_file_path, 'r') as config_file: + yml_data = yaml.safe_load(yml_file) + config_data = yaml.safe_load(config_file) + + for db_name, db_config in yml_data.items(): + if db_name in config_data['Database']: + for key, value in db_config.items(): + config_data['Database'][db_name][key] = value + else: + config_data['Database'][db_name] = db_config + + with open(config_file_path, 'w') as config_file: + yaml.dump(config_data, config_file, default_flow_style=False) + + print("Config file updated successfully.") + +except FileNotFoundError as e: + print(f"Error: {e}. Please check the file paths.") +except yaml.YAMLError as e: + print(f"Error in YAML processing: {e}") +except Exception as e: + print(f"An unexpected error occurred: {e}") + + +# 执行命令并判断是否成功 +def execute_command(commands): + for command in commands: + result = subprocess.run(command, shell=True, capture_output=True, text=True) + if result.returncode != 0: + print(f"Error executing command '{command}': {result.stderr}") + return False + else: + print(f"Successfully executed command '{command}': {result.stdout}") + return True + + + +commands = [ + "apt-get update", + "apt-get install -y python3-pip", +] +if not execute_command(commands): + print("Failed to update or install packages. Exiting...") + exit(1) + +# 运行测试命令 +run_test_command = [ + "pip3 install -r requirements.txt", + "python3 resp_compatibility.py --testfile cts.json --genhtml --show-failed", +] + +def run_test(): + if not execute_command(run_test_command): + print("Test failed. Exiting...") + exit(1) + else: + print("Test completed successfully.") + +# 启动测试脚本的线程 +test_thread = threading.Thread(target=run_test) +test_thread.start() + +time.sleep(300) + +# 提交和推送测试结果 +commit_and_push_commands = [ + "mv html /root", + "git stash -u", + "git checkout gh-pages || git checkout -b gh-pages", + "git pull origin gh-pages", + "cp -r /root/html/* .", + "git add .", + "git commit -m 'Update test results'", +] +if not execute_command(commit_and_push_commands): + print("Failed to commit and push changes. Exiting...") + exit(1) + +# 推送到 GitHub 并重试 +def git_push_with_retry(): + while True: + result = subprocess.run("git push -u origin gh-pages", shell=True, capture_output=True, text=True) + if result.returncode == 0: + print("Successfully pushed to GitHub.") + break + else: + print(f"Git push failed: {result.stderr}. Retrying in 5 seconds...") + time.sleep(5) + +git_push_with_retry() + diff --git a/resp_compatibility.py b/resp_compatibility.py index e470350..1fe5e55 100644 --- a/resp_compatibility.py +++ b/resp_compatibility.py @@ -220,40 +220,60 @@ def generate_html_report(logdir, configs): html.write("This page is automatically generated by compatibility-test-suite-for-redis " "to show the compatibility of the following Redis-Like systems and different versions of Redis.

") - html.write("") - # generate header - html.write("") - html.write("") - html.write("") - for version in configs['SpecificVersion']: - html.write(f"") - html.write("") - html.write("") - # generate body - html.write("") + + # Separate databases into cluster and standalone + cluster_databases = [] + standalone_databases = [] + for config in configs['Database']: + if configs['Database'][config]['cluster']: + cluster_databases.append(config) + else: + standalone_databases.append(config) + + # Function to generate a table + def generate_table(databases, title): + html.write(f"

{title}

") + html.write("
Product / Redis Version{version}
") + # generate header + html.write("") html.write("") - html.write(f"") + html.write("") for version in configs['SpecificVersion']: - filepath = f"{logdir}/{config}-{version}.html" - if not os.path.exists(filepath): - html.write(f"") - continue - with open(filepath, 'r') as f: - s = f.read() - match = re.search(r"rate: (\d+\.\d+)%", s) - assert match - rate = match.group(1) - color = "#40de5a" - if eval(rate) < 80: - color = "#f05654" - elif eval(rate) < 100: - color = "#ffa400" - html.write(f"") + html.write(f"") html.write("") - html.write("") - html.write("
{config}({configs['Database'][config]['version']})Product / Redis Version-{rate}% detail{version}
") - html.write("
") + html.write("") + # generate body + html.write("") + for config in databases: + html.write("") + html.write(f"{config}({configs['Database'][config]['version']})") + for version in configs['SpecificVersion']: + filepath = f"{logdir}/{config}-{version}.html" + if not os.path.exists(filepath): + html.write(f"-") + continue + with open(filepath, 'r') as f: + s = f.read() + match = re.search(r"rate: (\d+\.\d+)%", s) + assert match + rate = match.group(1) + color = "#40de5a" + if eval(rate) < 80: + color = "#f05654" + elif eval(rate) < 100: + color = "#ffa400" + html.write(f"{rate}% detail") + html.write("") + html.write("") + html.write("
") + + # Generate standalone table + generate_table(standalone_databases, "Standalone Databases") + + # Generate cluster table + generate_table(cluster_databases, "Cluster Databases") + time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") html.write(f"This report was generated on {time}.") html.write("")