测试文件系统合约

运行测试

一次正常的Hadoop测试运行会测试那些可以通过本地文件系统在本地测试的文件系统。这通常意味着file://及其底层的LocalFileSystem,以及通过HDFS MiniCluster测试的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>

测试新的文件系统

向合约测试中添加新FileSystem的核心是添加一个新的合约类,然后为每个你想测试的测试套件创建一个新的非抽象测试类。

  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才是文件系统行为的真实定义标准。若发现不一致之处,请在hdfs-dev邮件列表中提出。请注意:虽然FileSystem测试存在于Hadoop核心代码库中,但文件系统规范及配套测试的所有权归属于HDFS团队。

如果某个测试因功能不支持而需要跳过,请在ContractOptions类中查找现有的配置选项。如果没有对应方法,短期解决方案是重写该方法并使用ContractTestUtils.skip()消息记录测试被跳过的事实。使用此方法会将消息打印到日志中,然后通知测试运行器该测试已被跳过。这样可以突显问题所在。

推荐的策略是调用父类,捕获异常,并验证异常类和部分错误字符串与当前实现抛出的相匹配。如果父类实际上成功了——也就是说,它以当前实现尚未做到的方式失败——它也应该fail()。这将确保测试路径仍被执行,测试的任何其他失败(可能是回归)会被捕获。并且,如果该功能确实被实现了,这一变更也会被捕获。

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

‘宽松与严格’异常处理

契约测试包含严格异常与宽松异常的概念。严格异常报告意味着:使用IOException的特定子类报告失败,例如FileNotFoundExceptionEOFException等。宽松报告意味着抛出IOException

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

支持带登录和认证参数的FileSystems

针对远程文件系统的测试需要指定文件系统的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>

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

运行测试时,需要关闭maven.test.skip,因为这些测试默认情况下该参数为true。可以通过类似mvn test -Ptests-on的命令来实现。

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

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

  • 一致性:所有变更是否立即可见?
  • 原子性:HDFS保证的操作在新文件系统上同样具有原子性。
  • 幂等性:如果文件系统实现了任何重试策略,即使其他客户端操作文件系统,是否仍保持幂等性?
  • 可扩展性:它是否支持像HDFS那样大的文件,或者单个目录中包含大量文件?
  • 持久性:文件是否真的持久保存 - 能保存多久?

使用FileSystem API还有一些特定的方面:

  • 兼容 hadoop -fs 命令行界面。
  • 块大小策略是否生成适合分析工作的文件分片。(例如,块大小为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) 在setup方法中添加检查以在根测试被禁用时跳过测试:

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

可扩展性测试

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

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

扩展规范

该规范尚不完整。它没有全面覆盖FileSystem类,并且现有指定类中可能还有部分内容未被涵盖。

  1. 查看类/接口/方法的实现以了解它们的功能,特别是HDFS和本地实现。这些文档记录了当前的功能实现。
  2. 查看POSIX API规范。
  3. 在HDFS JIRA中搜索有关FileSystem主题的讨论,尝试理解预期行为与实际发生的情况。
  4. 使用IDE来了解方法在Hadoop、HBase及技术栈其他组件中的使用方式。虽然这假设了这些是典型的Hadoop应用案例,但至少能展示应用程序期望文件系统如何运作。
  5. 查阅java.io源代码以了解绑定的FileSystem类预期行为 - 并仔细阅读它们的javadoc文档。
  6. 如果有不清楚的地方 - 就像在hdfs-dev邮件列表中那样。
  7. 不要害怕编写测试来充当实验,以明确实际发生的情况。以HDFS行为作为规范性指南。