テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(akashic report) in-memory backendを実装しました

アカストは, LinuxVFSnfs-ganeshaのFSALのように, 抽象層を持っています. これをBackend Abstraction Layer (BAL)といいます. BALは, S3を実装するに十分な能力があればよく, VFSに比べると遥かにシンプルです.

この切り離しによって, テストがしやすくなります. 今までテストに使っていたのが, LocalファイルシステムをバックにしたBALです. (backend.impl.Local) これは何の準備もないしに動作させることが出来る上, デバグもしやすいです. このBALを使ってアカストの共通部分を叩いていけば, 全BAL実装が恩恵を受けることが出来ます.

2つ目のBALとして, in-memory版の実装をしました. (backend.impl.Memory)

まだ完全にテストしたわけではないですが, 実装は以下のようにシンプルです.

object Memory {
  val ID = new AtomicLong(0)
  trait Entry
  case class Directory() extends Entry {
    val children = mutable.Map[String, Entry]()
  }
  case class File(data: Array[Byte], attr: FileAttr) extends Entry

  def fromConfig(config: Config): Memory = {
    new Memory()
  }
}

class Memory extends BAL {
  import Memory._
  implicit def convertImplicitly(n: Node): Entry = n.asInstanceOf[Entry]
  val root: Directory = Directory()
  override def getRoot: Node = root
  override def lookup(dir: Node, name: String): Option[Node] = dir.asInstanceOf[Directory].children.get(name)
  override def createFile(dir: Node, name: String, data: InputStream): Unit = {
    val buf = IOUtils.toByteArray(data)
    val attr = FileAttr(System.currentTimeMillis, buf.length, Some(ID.incrementAndGet.toString))
    val parent = dir.asInstanceOf[Directory]
    parent.children += name -> File(buf, attr)
  }
  override def makeDirectory(dir: Node, name: String): Unit = {
    val parent = dir.asInstanceOf[Directory]
    val newSelf = Directory()
    parent.children += name -> newSelf
  }
  override def listDirectory(n: Node): Iterable[(String, Node)] = {
    val parent = n.asInstanceOf[Directory]
    parent.children
  }
  override def removeNode(dir: Node, name: String): Unit = {
    dir.asInstanceOf[Directory].children -= name
  }
  override def moveNode(fromDir: Node, fromName: String, toDir: Node, toName: String, replaceIfExists: Boolean): Unit = {
    val n = lookup(fromDir, fromName)
    fromDir.asInstanceOf[Directory].children -= fromName
    val newParent = toDir.asInstanceOf[Directory]
    newParent.children += toName -> n
  }
  override def isDirectory(n: Node): Boolean = n.isInstanceOf[Directory]
  override def getFileInputStream(n: Node): InputStream = new ByteArrayInputStream(n.asInstanceOf[File].data)
  override def getFileAttr(n: Node): FileAttr = n.asInstanceOf[File].attr
}

このBALには実用性はありませんが, 性能測定で用いると, BALへのアクセスを最小化することが出来ますから, アカストのコア部分の性能を測定することが出来ます. あとは, 色々なBALを実装することで抽象の見直しを出来ます. 実際にこのMemoryを実装するに当たって, removeNodeとmoveNodeのインターフェイスを変更しました.

あなたのBALを

このような形で, 色々なストレージ(KVS, 分散ファイルストレージ)の実装者が独自のBALを実装して, その上でアカストが動く世界を夢見ています.

その独自のBALは, アップストリームに貢献していただく必要はありません. なぜならば, アカストが使うBALは, Typesafe Config内でakashic.storage以下, backendセクションを変更するだけで利用出来るからです. ここで, typeはフルパスで指定出来るため, akashic.storage以下にある必要はないのです.

Localを使う場合:

backend {
  type = akashic.storage.backend.impl.Local
  # The mountpoint of the backing filesystem
  # User need to prepare this mountpoint before starting akashic-storage server.
  mountpoint = /mnt/akashic-storage
}

Memoryを使う場合:

backend {
  type = akashic.storage.backend.impl.Memory
}

このbackendセクション内から, BALインスタンスがどう生成されるかというと, リフレクションを使っています. 例としてLocalの場合を見てみましょう.

object Local {
  def fromConfig(config: Config): Local = {
    val mp = Paths.get(config.getString("mountpoint"))
    new Local(mp)
  }
}

コンパニオンにfromConfigというメソッドを実装して, これがインスタンスを返すようにすればいいのです. backendセクションに何を含むかは, 実装者が自由に決めることが出来ます.

何か面白いBALを実装したら, PRしていただいても構いません. そうやって色々なBALを実装することによって抽象層が磨かれていくのです.

BALをテストしよう

BALが正しく実装出来ていることをアカストと結合した上でテストする前に, BAL自体の単体テストをするべきだと思います.

そのためにtraitを提供しています.

trait BALTraitTest extends FunSuite with BeforeAndAfterEach {
  val config: Config
  var bal: BAL = _
  override def beforeEach(): Unit = {
    super.beforeEach()
    bal = new BALFactory(config).build
  }
  test("add directory") {
    bal.makeDirectory(bal.getRoot, "aaa")
    val dir = bal.lookup(bal.getRoot, "aaa").get
    bal.makeDirectory(dir, "bbb")
  }
}

abstract class BALTraitTestTemplate(val config: Config) extends BALTraitTest

このtraitは今はまだしょぼいですが, これから充実させていきます. ユーザはこのtraitをmix-inすることで, バックエンドのKVSなりのセットアップをした上で, 実装したBALのテストを行うことが出来ます.


アカストは趣味でやってるプロジェクトですが, 私は本気です. PRお待ちしております.

github.com