Apache Cassandra 构建与测试工具集
项目概述
本项目是 Apache Cassandra 分布式数据库的核心构建与测试工具集,提供了一系列自动化脚本来简化 Cassandra 的开发、测试、打包和持续集成流程。通过 Docker 容器化技术,确保在不同环境下构建和测试的一致性,同时支持多 JDK 版本、多种测试类型(单元测试、集成测试、dtest)以及多种发布包格式(tarball、Debian、RPM)。
功能特性
- Docker 集成构建:支持在 Docker 容器中执行构建和测试任务,确保环境一致性
- 多 JDK 版本支持:自动检测和切换 JDK 版本,支持 Java 8/11/17 等版本
- 完整测试体系:包含单元测试、集成测试、dtest、压力测试、稳定性测试等
- 多平台打包:支持生成源码包、Debian 包、RPM 包
- CI/CD 自动化:与 Jenkins、CircleCI 深度集成,支持 Kubernetes 上的 CI 环境部署
- 代码质量检查:集成代码检查工具,支持 SonarQube 静态分析
- Git Hooks 集成:自动安装 Git hooks,确保提交质量
安装指南
系统要求
- Linux 或 macOS 操作系统
- Java JDK 8 或更高版本
- Apache Ant 1.9 或更高版本
- Docker(可选,用于容器化构建)
- Python 3.6-3.11(用于测试和 cqlsh)
依赖安装
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install -y ant openjdk-11-jdk python3 python3-pip docker.io
# CentOS/RHEL
sudo yum install -y ant java-11-openjdk python3 python3-pip docker
# 安装 Python 依赖
pip3 install -r pylib/requirements.txt
构建项目
# 克隆仓库
git clone https://github.com/apache/cassandra.git
cd cassandra
# 构建 JAR 文件
.build/build-jars.sh
# 构建完整 artifact(tarball 和 Maven 包)
.build/build-artifacts.sh
# 构建 Debian 包
.build/docker/build-debian.sh
# 构建 RPM 包
.build/docker/build-redhat.sh
使用说明
运行代码检查
# 在 Docker 中运行代码检查
.build/docker/check-code.sh 11
# 本地运行代码检查
.build/check-code.sh
运行测试
# 运行单元测试
.build/run-tests.sh -a test
# 运行指定测试类
.build/run-tests.sh -a test -t "VerifyTest"
# 运行测试分片(例如第1/64分片)
.build/run-tests.sh -a test -c 1/64
# 运行 dtest(Python 集成测试)
.build/dtest-python.sh -a dtest -t "test_name"
# 运行压力测试
.build/run-tests.sh -a stress-test
CI/CD 集成
# 在 Kubernetes 集群中运行 CI 管道
.build/run-ci --repository https://github.com/yourfork/cassandra.git \
--branch feature-branch \
--profile pre-commit \
--jdk 11
# 仅设置 Jenkins 环境
.build/run-ci --only-setup
# 下载已有构建结果
.build/run-ci --download-results 123
核心代码
构建脚本示例
#!/bin/bash
# build-artifacts.sh - 构建 Cassandra 发布包
set -e
CASSANDRA_DIR="$(readlink -f $(dirname "$0")/..)"
command -v ant >/dev/null 2>&1 || { echo "ant needs to be installed"; exit 1; }
# 构建 artifact,跳过文档生成和检查
ant -f "${CASSANDRA_DIR}/build.xml" artifacts \
-Dant.gen-doc.skip=true \
-Dcheck.skip=true
exit $?
测试运行器示例
#!/bin/bash
# run-tests.sh - 运行 Cassandra 测试套件
[ "x${CASSANDRA_DIR}" != "x" ] || CASSANDRA_DIR="$(readlink -f $(dirname "$0")/..)"
command -v ant >/dev/null 2>&1 || { echo "ant needs to be installed"; exit 1; }
# 解析测试参数
while getopts "a:t:c:j:h" opt; do
case $opt in
a) TEST_TARGET="$OPTARG" ;;
t) TEST_REGEXP="$OPTARG" ;;
c) TEST_CHUNK="$OPTARG" ;;
j) JAVA_VERSION="$OPTARG" ;;
h) print_help; exit 0 ;;
esac
done
# 执行测试
ant -f "${CASSANDRA_DIR}/build.xml" ${TEST_TARGET} \
-Dtest.name="${TEST_REGEXP}" \
-Dtest.split.chunk="${TEST_CHUNK}"
Python 测试结果解析器
#!/usr/bin/env python3
# ci_parser.py - 解析 JUnit 测试结果并生成 HTML 报告
import xml.etree.ElementTree as ET
from bs4 import BeautifulSoup
from typing import Dict, List
class JUnitTestSuite:
"""JUnit 测试套件表示类"""
def __init__(self, name: str):
self.name = name
self.cases = []
self.errors = 0
self.failures = 0
self.skipped = 0
self.tests = 0
def add_case(self, case):
self.cases.append(case)
self.tests += 1
if case.status == 'failure':
self.failures += 1
elif case.status == 'error':
self.errors += 1
elif case.status == 'skipped':
self.skipped += 1
def parse_junit_xml(xml_path: str) -> List[JUnitTestSuite]:
"""解析 JUnit XML 文件"""
tree = ET.parse(xml_path)
root = tree.getroot()
suites = []
for suite_elem in root.findall('testsuite'):
suite = JUnitTestSuite(suite_elem.get('name'))
for case_elem in suite_elem.findall('testcase'):
case = JUnitTestCase(case_elem.get('name'))
# 解析测试结果
if case_elem.find('failure') is not None:
case.status = 'failure'
elif case_elem.find('error') is not None:
case.status = 'error'
elif case_elem.find('skipped') is not None:
case.status = 'skipped'
suite.add_case(case)
suites.append(suite)
return suites
审计日志模块
// BinAuditLogger.java - 二进制审计日志记录器
package org.apache.cassandra.audit;
import net.openhft.chronicle.wire.WireOut;
import org.apache.cassandra.utils.binlog.BinLog;
public class BinAuditLogger implements IAuditLogger {
private volatile BinLog binLog;
private final String keyValueSeparator;
private final String fieldSeparator;
public BinAuditLogger(AuditLogOptions options) {
this.keyValueSeparator = options.key_value_separator;
this.fieldSeparator = options.field_separator;
initializeBinLog(options);
}
private void initializeBinLog(AuditLogOptions options) {
this.binLog = new BinLog(options.audit_logs_dir,
options.roll_cycle,
options.block,
options.max_queue_weight,
maxQueueSize);
}
@Override
public void log(AuditLogEntry entry) {
if (binLog == null || !isEnabled()) return;
binLog.acquireWriteSegment(wire -> {
WireOut out = wire.write();
out.write("version").int64(CURRENT_VERSION)
.write("type").text(AUDITLOG_TYPE)
.write("message").text(entry.getLogString(keyValueSeparator, fieldSeparator));
});
}
@Override
public boolean isEnabled() {
return binLog != null && binLog.isEnabled();
}
}
Zz0R1F5Mw+N/PxbzAtbIALQx6gpNADOFLDFL/rsPQ94=