NVMe SSDにfault injectionしてみる

この記事は

Linux kernelのfalult injection機能の(特にNVMe SSDに関連する部分について)
ソースを読んだり、実際に使ってみたりする記事のうちの一つ

記事一覧

その1:fault injectionってなあに
その2:fault injection機能が埋まっている箇所のソースを読む
その3:NVMe SSDにfault injectionしてみる ★今回はこれ

NVMe SSDにfault injectionしてみる

環境準備

OSインストール

なんでもいいんですが
私はVirtualBox上にUbuntu22.04.3をインストールした
SATA(AHCI)コントローラーに接続されているHDDがシステムディスク
NVMeコントローラに接続されているSSDがデータディスク

システムディスクにfalult injectionするのはちょっと不安なので、
NVMe SSDはデータディスクとして準備するのがおすすめ

VirtualBox ストレージ設定

fault injection機能の有効化

Ubuntu22.04.3では、fault injectionは無効になっているため
機能を有効化するためにKernelをビルドする

Linux Kernelのソースを入手する

手順はUbuntuの公式Wiki (と自分の記憶)などを参考にしました

/etc/apt/source.listのdeb-srcの行のコメントをすべて外す

deb-src hogehoge

パッケージのリストを更新

apt update

適当な作業用ディレクトリに移動し、ソースのパッケージをダウンロード

apt-get source linux-image-$(uname -r)

以下三つのファイルが落ちてくるので

以下をたたいてUbuntu向けの修正が反映されたソースを生成する

dpkg-source -x linux_hoge.dsc

Linux Kernelをビルドする

kernelのビルドに必要なパッケージをインストールする

apt install make gcc flex bison libncurses-dev libelf-dev libssl-dev

ソースのルートディレクトリに移動
デフォルトのconfigファイルをKernelビルドに再利用するためコピー

cp /boot/config-hoge ./.config

fault injectionを有効にするよう.configを編集

CONFIG_FAULT_INJECTION=y

make oldconfigたたいてfault injection関係と思われるものはyを選択

Kernelのビルドとdpkgの作成

make -$(nproc) deb-pkg

ビルドしたKernelの適用

apt install linux-headers-hoge_amd64.deb
apt install linux-headers-hoge_amd64.deb

再起動する(ビルドしたKernelで起動しなおす)

/sys/kernel/debug/nvme*が存在することを確認する

root@nodoguro-VirtualBox:/home/nodoguro# ls /sys/kernel/debug/nvme0

nvme0/   nvme0n1/ 

実践編

ターゲットの確認

ターゲットとなるディスクを確認しておく 今回は/dev/nvme0n1

root@nodoguro-VirtualBox:/home/nodoguro# parted -l

Model: ATA VBOX HARDDISK (scsi)

Disk /dev/sda: 107GB

Sector size (logical/physical): 512B/512B

Partition Table: gpt

Disk Flags: 



Number  Start   End    Size   File system  Name                  Flags

 1      1049kB  538MB  537MB  fat32        EFI System Partition  boot, esp

 2      538MB   107GB  107GB  ext4





Error: /dev/nvme0n1: unrecognised disk label

Model: ORCL-VBOX-NVME-VER12 (nvme)                                        

Disk /dev/nvme0n1: 26.8GB

Sector size (logical/physical): 512B/512B

Partition Table: unknown

Disk Flags: 

fault injectionしてみる

今回は公式ドキュメントのnvme-fault-injectionに記載のある以下3種のエラー注入を試してみる

Example 1:Inject default status code with no retry
Example 2: Inject default status code with retry
Example 3: Inject an error into the 10th admin command

今回は、ddコマンドでランダムなデータを書き込んでテストする
正常に終了した場合は以下のようになる

root@nodoguro-VirtualBox:/home/nodoguro# dd if=/dev/urandom of=/dev/nvme0n1 count=10000

10000+0 records in

10000+0 records out

5120000 bytes (5.1 MB, 4.9 MiB) copied, 0.526876 s, 9.7 MB/s

Example 1:Inject default status code with no retry

100%の確率で1度だけfault injectionする
リトライしない

設定
echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
結果
root@nodoguro-VirtualBox:/home/nodoguro/source# dd if=/dev/urandom of=/dev/nvme0n1 count=10000

dd: writing to '/dev/nvme0n1': Input/output error

1+0 records in

0+0 records out

0 bytes copied, 0.00110659 s, 0.0 kB/s

エラーでこけていて期待通り

Example 2: Inject default status code with retry

100%の確率で1度だけfault injectionする
リトライする

設定
echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
echo 0 > /sys/kernel/debug/nvme0n1/fault_inject/dont_retry
結果
root@nodoguro-VirtualBox:/home/nodoguro/source# dd if=/dev/urandom of=/dev/nvme0n1 count=10000

10000+0 records in

10000+0 records out

5120000 bytes (5.1 MB, 4.9 MiB) copied, 0.600538 s, 8.5 MB/s

リトライするのでエラー見えないで期待通り

Example 3: Inject an error into the 10th admin command

しばらくしてから100%の確率で1度だけfault injectionする
※admin commandと書いてあるが、他と合わせてddで試す

設定
echo 100 > /sys/kernel/debug/nvme0n1/fault_inject/probability
echo 10 > /sys/kernel/debug/nvme0n1/fault_inject/space
echo 1 > /sys/kernel/debug/nvme0n1/fault_inject/times
結果
root@nodoguro-VirtualBox:/home/nodoguro/source# dd if=/dev/urandom of=/dev/nvme0n1 count=10000

dd: writing to '/dev/nvme0n1': Input/output error

73+0 records in

72+0 records out

36864 bytes (37 kB, 36 KiB) copied, 0.00613897 s, 6.0 MB/s

最初はIO成功、途中でIOが失敗しており期待通り

最後に

NVMe SSDへのfault injectionを実際に使ってみるところまでお試しできた
当初の予定通り、使ってみるところまでできて良かった

何か調べたいときに適したツールでサクッと調査できるしぐさに常に憧れがあるが
こういった、Linux Kernelに埋まっている、今はまだ知らない機能たちを触っていくのがそれに近づく一歩な気がしている
(これに関しては使うにはKernelのビルドが必要で超時間がかかっちゃうけどね..)

fault injection機能が埋まっている箇所のソースを読む

この記事は

Linux kernelのfalult injection機能の(特にNVMe SSDに関連する部分について)
ソースを読んだり、実際に使ってみたりする記事のうちの一つ

記事一覧

その1:fault injectionってなあに
その2:fault injection機能が埋まっている箇所のソースを読む ★今回はこれ
その3:NVMe SSDにfault injectionしてみる

fault injection機能が埋まっている箇所のソースを読む

なお、ここで読んでいくソースはUbuntu 22.04.3(6.2.0-39-generic)のものです

お目当ての処理をどうやって探そうか?

Linux Kernelのドキュメントのfault-injectionのページによると、

fault injection機能を新規に追加したい開発者向けに、以下のマクロが準備されている

  • DECLARE_FAULT_ATTR()

また、関数レベルでのfault injectionを新規に追加*1 したい開発者向けに、以下のマクロが準備されている

  • ALLOW_ERROR_INJECTION()

と、いうことで、
このあたりにエラー注入できたりしないかな/エラー注入は具体的に何をどうしているのかな、と探すときは
以下くらいでgrepするとよさそう

  • DECLARE_FAULT_ATTR
  • ALLOW_ERROR_INJECTION

*1 fail_function機能の一部として、関数の戻り値の差し替えを実装するということ

NVMe SSDのfault injection機能が埋まっている箇所を探す

DECLARE_FAULT_ATTRでgrepしてみると、以下が見つかった

drivers/nvme/host/fault_inject.c

static DECLARE_FAULT_ATTR(fail_default_attr);

ファイル名からして、まさにこれです、私が欲しかったのは、という感じがする

NVMe SSDのfault injection機能周りを読んでみる

Makefile

drivers/nvme/host/fault_inject.c と同じ階層にあるMakefileを見てみると
CONFIG_FAULT_INJECTION_DEBUG_FSがy以外の場合には
fault_inject.oは使用しない設定になっている
(Ubuntu 22.04.3では使用しない設定)

drivers/nvme/host/Makefile

nvme-core-$(CONFIG_FAULT_INJECTION_DEBUG_FS)    += fault_inject.o

実際に使ってみるにはKernelのビルドが必要

エラーの注入処理

drivers/nvme/host/fault_inject.cにある
nvme_should_fail()がエラー注入の箇所そのものの様子
リクエストのステータスとしてNVME_SC_INVALID_OPCODE | NVME_SC_DNRを設定する
(初期化時に、fault_inject->statusにはNVME_SC_INVALID_OPCODEが設定されるため)

void nvme_should_fail(struct request *req)
{
    struct gendisk *disk = req->q->disk;
    struct nvme_fault_inject *fault_inject = NULL;
    u16 status;

    if (disk) {
        struct nvme_ns *ns = disk->private_data;

        if (ns)
            fault_inject = &ns->fault_inject;
        else
            WARN_ONCE(1, "No namespace found for request\n");
    } else {
        fault_inject = &nvme_req(req)->ctrl->fault_inject;
    }

    if (fault_inject && should_fail(&fault_inject->attr, 1)) {
        /* inject status code and DNR bit */
        status = fault_inject->status;
        if (fault_inject->dont_retry)
            status |= NVME_SC_DNR;
        nvme_req(req)->status =   status;
    }
}

このnvme_should_fail()は、nvme_try_complete_req()からのみコールされており
nvme_try_complete_req()は名前からして、ストレージに書き込みが完了した後にコールされるような雰囲気
ストレージにはIO発行しているけど、ステータスとしては失敗したように見せるのか
(NVMeのIO周りは土地勘がなくて確実なところは言えないけど)

追記:NVMe SSDにfault injectionしてみるで見えたCall stackより、
ストレージ書き込み完了後のHw割り込み延長でエラー注入で間違いなさそう

12月 26 01:47:47 nodoguro-VirtualBox kernel: CPU: 4 PID: 0 Comm: swapper/4 Tainted: G        W  OE      6.2.16 #1
12月 26 01:47:47 nodoguro-VirtualBox kernel: Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
12月 26 01:47:47 nodoguro-VirtualBox kernel: Call Trace:
12月 26 01:47:47 nodoguro-VirtualBox kernel:  <IRQ>
12月 26 01:47:47 nodoguro-VirtualBox kernel:  dump_stack_lvl+0x48/0x70
12月 26 01:47:47 nodoguro-VirtualBox kernel:  dump_stack+0x10/0x20
12月 26 01:47:47 nodoguro-VirtualBox kernel:  should_fail_ex+0x1ab/0x1b0
12月 26 01:47:47 nodoguro-VirtualBox kernel:  should_fail+0xb/0x20
12月 26 01:47:47 nodoguro-VirtualBox kernel:  nvme_should_fail+0x46/0xd0 [nvme_core]
12月 26 01:47:47 nodoguro-VirtualBox kernel:  nvme_poll_cq+0x16e/0x390 [nvme]
12月 26 01:47:47 nodoguro-VirtualBox kernel:  ? srso_alias_return_thunk+0x5/0x7f
12月 26 01:47:47 nodoguro-VirtualBox kernel:  nvme_irq+0x40/0x90 [nvme]
12月 26 01:47:47 nodoguro-VirtualBox kernel:  __handle_irq_event_percpu+0x4f/0x1b0
12月 26 01:47:47 nodoguro-VirtualBox kernel:  handle_irq_event+0x39/0x80
12月 26 01:47:47 nodoguro-VirtualBox kernel:  handle_fasteoi_irq+0x7d/0x1d0
12月 26 01:47:47 nodoguro-VirtualBox kernel:  __common_interrupt+0x52/0x110
12月 26 01:47:47 nodoguro-VirtualBox kernel:  common_interrupt+0x9f/0xb0
12月 26 01:47:47 nodoguro-VirtualBox kernel:  </IRQ>
12月 26 01:47:47 nodoguro-VirtualBox kernel:  <TASK>
12月 26 01:47:47 nodoguro-VirtualBox kernel:  asm_common_interrupt+0x27/0x40

fault injectionってなあに

この記事は

Linux kernelのfalult injection機能の(特にNVMe SSDに関連する部分について)
ソースを読んだり、実際に使ってみたりする記事のうちの一つ

記事一覧

その1:fault injectionってなあに ★今回はこれ
その2:fault injection機能が埋まっている箇所のソースを読む
その3:NVMe SSDにfault injectionしてみる

(順次追加予定)

事の始まり

Kernel/VM探検隊@北陸 Part 6 - connpassへ参加してきた

satさんのセッションでは、タイトルである
「device mapperによるディスクI/O障害のエミュレーション」のほか、
それ以外のディスクI/O障害のエミュレーションの手法についても簡単な紹介があった

そもそも、kernel/driverに自分でデバッグコード埋め込まなくてもそんなことできるって初めて知ったぞ…

私はHwよりのところのドライバを触っている人なので、
紹介のあった手法の中で、よりHwに近い領域で障害をエミュレーションする、
NVMe SSDへのfault injection機能について気になった

fault injectionってなあに

意図的にエラーを発生させるための機能

通常、エラー処理は正常処理と比較するとあまり実行されない
fault injectionにより意図的にエラーを発生させ、コードのカバレッジを高めていきたいということらしい

公式のドキュメント*1 を読みながらもう少し詳細なところを確認していく
*1 Fault injection capabilities infrastructure — The Linux Kernel documentation

どこに対してfault injectionできるの

現在injection可能なのは以下

  • slab allocation failures
  • page allocation failures
  • failures in user memory access functions
  • futex deadlock and uaddr fault errors
  • kernel RPC client and server failures
  • disk IO errors
  • MMC data errors on devices permitted
  • return on specific functions
  • NVMe status code and retry flag
  • IO timeouts

この項目からはわからないが、 PMのPrepareの段階の失敗をinjectnionすることもできる様子、気になる
まだ棚ぼた的に使ってみたい箇所の発見もあるかもしれないという気がする
次回以降の記事で、いい感じのキーワードでソース上をgrepしていい感じに見ていきたい

動作のチューニング

injectnionする失敗について、以下の項目がチューニングが可能

  • likelihood of failure injection
  • interval between failure
  • how many times failures may happen at most
  • initial resource "budget"
  • verbosity of the messages
  • filtering by process
  • specifies the range of virtual addresses
  • stacktrace depth
  • inject failures into highmem/user allocations
  • inject failures into allocations that can sleep
  • minimum page allocation order
  • disconnect injection on the RPC client
  • disconnect injection on the RPC server
  • cache wait injection on the RPC
  • target function
  • shows error injectable functions(read only)
  • "error" return value

かなり沢山項目がある
使ってみながら見ていく感じかなあ

最後に

興味をくすぐるテーマに出会わせてくれたsatさん、
その機会を提供をしていただいたイベントの運営の皆さん、ありがとうございました

…もうすべての記事を書き終わったかのような口ぶりだが
どこかで力尽きたときのための念のための保険で、一応書ききるつもりはある

ちなみに、satさんがセッションと同等の内容を Youtubeにも挙げていらっしゃるのでそちらも要チェックだ
その63 device mapperによるディスクI/O障害のエミュレーション 既存ターゲット編 - YouTube
その64 分散ストレージCephのデータ破壊検知修復機能は本当に動作するのか - YouTube
その65 device mapperによるディスクI/O障害のエミュレーション カーネルモジュール自作編 - YouTube