测试文件系统契约

运行测试

正常的 Hadoop 测试运行将测试那些可以通过本地文件系统在本地进行测试的文件系统。这通常意味着通过 HDFS MiniCluster 访问 file:// 及其底层的 LocalFileSystem,以及 hdfs://

除非提供文件系统的远程服务器有特定配置,否则将跳过其他文件系统。

这些文件系统绑定必须在 XML 配置文件中定义,通常为 hadoop-common-project/hadoop-common/src/test/resources/contract-test-options.xml。此文件被排除在外,不应签入。

ftp://

contract-test-options.xml 中,文件系统名称必须在属性 fs.contract.test.fs.ftp 中定义。然后必须提供连接到 FTP 服务器的特定登录选项。

还必须在选项 fs.contract.test.ftp.testdir 中提供测试目录的路径。这是执行操作的目录。

示例

<configuration>
  <property>
    <name>fs.contract.test.fs.ftp</name>
    <value>ftp://server1/</value>
  </property>

  <property>
    <name>fs.ftp.user.server1</name>
    <value>testuser</value>
  </property>

  <property>
    <name>fs.contract.test.ftp.testdir</name>
    <value>/home/testuser/test</value>
  </property>

  <property>
    <name>fs.ftp.password.server1</name>
    <value>secret-login</value>
  </property>
</configuration>

测试新文件系统

将新文件系统添加到契约测试的核心是添加新契约类,然后为要测试的每个测试套件创建一个新的非抽象测试类。

  1. 不要尝试将这些测试添加到 Hadoop 本身。它们不会被添加到源代码树中。测试必须与您自己的文件系统源一起存在。
  2. contract 下创建包(通常)在您自己的测试源代码树中,用于文件和测试。
  3. 为自己的契约实现子类化 AbstractFSContract
  4. 对于您计划支持的每个测试套件,创建一个非抽象子类,名称以 Test 和文件系统名称开头。示例:TestHDFSRenameContract
  5. 这些非抽象类必须实现抽象方法 createContract()
  6. 识别和记录必须在特定项目的 src/test/resources/contract-test-options.xml 文件中定义的任何文件系统绑定。
  7. 运行测试,直到它们正常工作。

例如,以下是本地文件系统 create() 测试的测试实现。

package org.apache.hadoop.fs.contract.localfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.contract.AbstractCreateContractTest;
import org.apache.hadoop.fs.contract.AbstractFSContract;

public class TestLocalCreateContract extends AbstractCreateContractTest {
  @Override
  protected AbstractFSContract createContract(Configuration conf) {
    return new LocalFSContract(conf);
  }
}

AbstractFSContract 子类的标准实现技术是完全由存储在测试资源树中的 Hadoop XML 配置文件驱动的。最佳做法是将其存储在 /contract 下,文件系统名称为 contract/localfs.xml。让 XML 文件定义所有文件系统选项使文件系统行为的列表立即可见。

LocalFSContract 是这种情况的一个特例,因为它必须根据其运行的操作系统调整其大小写敏感策略:对于 Windows 和 OS/X,文件系统不区分大小写,因此 ContractOptions.IS_CASE_SENSITIVE 选项必须设置为 false。此外,Windows 文件系统不支持 Unix 文件和目录权限,因此也必须设置相关标志。这是在从资源树加载 XML 契约文件之后完成的,只需更新现在加载的配置选项即可

  getConf().setBoolean(getConfKey(ContractOptions.SUPPORTS_UNIX_PERMISSIONS), false);

处理测试失败

如果您的新 FileSystem 测试用例未能通过契约测试之一,您可以做什么?

这取决于问题的根源

  1. 案例:自定义 FileSystem 子类类未正确实现规范。修复。
  2. 案例:底层文件系统行为方式与 Hadoop 的预期不符。理想情况下,修复它。或者尝试让您的 FileSystem 子类隐藏差异,例如通过转换异常。
  3. 案例:您的文件系统和 Hadoop 之间存在基本架构差异。示例:不同的并发性和一致性模型。建议:记录并明确说明该文件系统与 HDFS 不兼容。
  4. 案例:测试与规范不符。修复:修补测试,将修补程序提交给 Hadoop。
  5. 案例:规范不正确。底层规范是(除少数例外情况外)HDFS。如果规范与 HDFS 不符,通常应假定 HDFS 是 FileSystem 应执行操作的真正定义。如果存在不匹配,请在 hdfs-dev 邮件列表中提出。请注意,虽然 FileSystem 测试存在于核心 Hadoop 代码库中,但 HDFS 团队拥有 FileSystem 规范及其附带的测试。

如果由于不支持某个功能而需要跳过测试,请在 ContractOptions 类中查找现有的配置选项。如果没有方法,短期修复方法是覆盖该方法并使用 ContractTestUtils.skip() 消息来记录跳过测试这一事实。使用此方法会将消息打印到日志,然后告诉测试运行器已跳过测试。这突出了问题。

建议的策略是调用超类,捕获异常,并验证异常类和错误字符串的一部分与当前实现引发的异常相匹配。如果超类实际上成功了,它还应该 fail() - 即它以当前实现尚未执行的方式失败了。这将确保仍然执行测试路径,测试的任何其他失败(可能是回归)都会被发现。而且,如果该功能确实得到实现,则会发现该更改。

长期解决方案是增强基本测试以添加新的可选功能键。这需要与 hdfs-dev 邮件列表中的开发人员合作。

“宽松与严格”异常

契约测试包括严格与宽松异常的概念。严格异常报告意味着:使用 IOException 的特定子类(例如 FileNotFoundExceptionEOFException 等)报告故障。宽松报告意味着抛出 IOException

虽然文件系统应该引发更严格的异常,但可能存在无法引发异常的原因。仍然允许引发宽松异常,它只是妨碍用户应用程序中故障的诊断。要声明文件系统不支持更严格的异常,请将选项 fs.contract.supports-strict-exceptions 设置为 false。

支持具有登录和身份验证参数的文件系统

针对远程文件系统的测试需要指定到文件系统的 URL;针对需要登录详细信息的远程文件系统的测试需要用户名/ID 和密码。

所有这些详细信息必须放置在文件 src/test/resources/contract-test-options.xml 中,并且配置 SCM 工具以从不将此文件提交到 Subversion、Git 或同等工具中。此外,必须配置构建以从生成的任何 -test 工件中从不捆绑此文件。Hadoop 构建执行此操作,从 JAR 文件中排除 src/test/**/*.xml。此外,需要创建 src/test/resources/auth-keys.xml。它可以是 contract-test-options.xml 的副本。如果存在,AbstractFSContract 类会自动加载此资源文件;可以添加特定测试用例的特定密钥。

例如,以下是 S3A 测试密钥的外观

<configuration>
  <property>
    <name>fs.contract.test.fs.s3a</name>
    <value>s3a://tests3contract</value>
  </property>

  <property>
    <name>fs.s3a.access.key</name>
    <value>DONOTPCOMMITTHISKEYTOSCM</value>
  </property>

  <property>
    <name>fs.s3a.secret.key</name>
    <value>DONOTEVERSHARETHISSECRETKEY!</value>
  </property>
</configuration>

如果在属性 fs.contract.test.fs.%s 中未定义文件系统 URL,则 AbstractBondedFSContract 会自动跳过测试套件,其中 %s 匹配文件系统的架构名称。

在运行测试时,需要关闭 maven.test.skip,因为它在这些测试中默认情况下为 true。可以使用类似 mvn test -Ptests-on 的命令执行此操作。

重要提示:通过测试并不能保证兼容性

通过所有文件系统契约测试并不意味着文件系统可以被描述为“与 HDFS 兼容”。这些测试尝试查看每个操作的隔离功能,并关注每个操作的前提条件和后置条件。未涵盖的核心领域是并发性和分布式系统中的故障方面。

  • 一致性:所有更改是否立即可见?
  • 原子性:HDFS 保证为原子的操作在新文件系统上是否同样如此。
  • 幂等性:如果文件系统实现任何重试策略,即使其他客户端操作文件系统,它是否仍然是幂等的?
  • 可扩展性:它是否支持与 HDFS 一样大的文件,或在单个目录中支持与 HDFS 一样多的文件?
  • 持久性:文件是否真的持久存在 - 并且持续多长时间?

文件系统 API 的使用还有一些特定方面

  • hadoop -fs CLI 的兼容性。
  • 块大小策略是否生成适合分析工作的文件拆分。(例如,1 的块大小符合规范,但由于它指示 MapReduce 作业一次处理一个字节,因此不可用)。

当然,欢迎对这些行为进行验证的测试。

添加新测试套件

  1. 新测试应按每个操作拆分为一个测试类,如 seek()rename()create() 等。这是为了匹配文件系统契约规范按操作拆分的方式。它还使文件系统实现者一次处理一个测试套件变得更容易。
  2. 使用新的抽象测试套件类对 AbstractFSContractTestBase 进行子类化。同样,在标题中使用 Abstract
  3. 查看 org.apache.hadoop.fs.contract.ContractTestUtils,了解有助于测试的实用程序类,其中包含许多以文件系统为中心的断言。使用这些断言对文件系统状态进行断言,并在断言实际失败时包含诊断信息,如目录列表和不匹配文件的转储。
  4. 为本地、原始本地和 HDFS 文件系统编写测试 - 如果其中一个测试失败,则表明存在问题 - 尽管要注意它们确实有差异
  5. 在核心文件系统通过测试后,在对象存储上进行测试。
  6. 尝试尽可能详细地记录故障 - 调试故障的人员会很感激。

根操作测试

一些测试直接针对根文件系统进行,尝试执行重命名“/”和类似操作等操作。根目录是“特殊的”,测试这一点非常重要,尤其是在非 POSIX 文件系统(如对象存储)上。这些测试可能对本机文件系统非常具有破坏性,因此请小心使用。

  1. AbstractRootDirectoryContractTest 下添加测试,或创建一个新测试,其中 (a) 标题中包含 Root,(b) 设置方法中包含一个检查,以在禁用根测试时跳过测试

      skipIfUnsupported(TEST_ROOT_TESTS_ENABLED);
    
  2. 不要提供针对本地 FS 运行此测试套件的实现。

可扩展性测试

旨在生成可扩展负载的测试 - 包括大量小文件以及较少的大文件,应设计为可配置的,以便测试套件的用户可以配置文件数量和大小。

请注意,在对象存储上,目录重命名操作通常为 O(files)*O(data),而删除操作为 O(files)。后者意味着即使任何目录清理操作也可能需要时间,并且可能会超时。设计针对远程文件系统的测试非常重要,因为所有操作都可能延迟。

扩展规范

该规范不完整。它没有完全涵盖 FileSystem 类,并且可能有一些未涵盖的现有指定类部分。

  1. 查看类/接口/方法的实现,了解它们的作用,尤其是 HDFS 和本地。这些是当前所做工作的文档。
  2. 查看 POSIX API 规范。
  3. 搜索 HDFS JIRA 中关于 FileSystem 主题的讨论,并尝试理解预期发生的事情以及实际发生的事情。
  4. 使用 IDE 找出 Hadoop、HBase 和堆栈其他部分中如何使用方法。尽管这假设这些是具有代表性的 Hadoop 应用程序,但它至少会显示应用程序期望 FileSystem 如何表现。
  5. 查看 java.io 源代码,了解如何预期捆绑的 FileSystem 类表现 - 并仔细阅读它们的 javadoc。
  6. 如果某些内容不清楚 - 如 hdfs-dev 列表中。
  7. 不要害怕编写测试作为实验并阐明实际发生的事情。使用 HDFS 行为作为规范指南。