Apache Cassandra 构建与测试工具集

4 阅读3分钟

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=