ちょっとCI環境でも作るか〜ということで。
CI
だいたいこんな構成です。
いまのところすべてローカル環境で閉じてます。うちはマンションタイプのBフレッツでそんなに速いわけでもないからローカルで閉じてるほうがテスト時間が短くて済むというとこです。AWSとかVPSを使っても良いんですけどね。
とりあえず動くようにはなったので、ブラッシュアップするところはたくさんあります😨
CIのシステムで使用するソフトウェア
次の2つを使います。
Buildbotは有名ですよね。LAVAはkernelci.orgで使われています。実ボードとかqemuなどの環境でLinuxをテストするのに向いているツールです。
インストール
buildbot
pythonのvirtualenvを使って専用のpython環境作ってインストールして使ってます。
LAVA
LAVAは一回スクラッチから設定したことあるんですが面倒なのでdockerを使います。githubにkernelciのプロジェクトのgitリポジトリがあるのでcloneして使いました。
Webサーバ
Webサーバも使うんですがこれはLAVAからカーネルなどのファイルをダウンロードするのに使うだけなのでpythonのワンライナーで済ませてます。
流れ
ファイルを編集してコミットしたらbuildbotがリポジトリをポーリングしているので、変更を検知したらビルド開始します。make bzImageとmake modules、make modules_installまで成功したらdracutでinitramfsを作り、bzImageとinitramfsをwebサーバのディレクトリに置きます。そしたらbuildbotのジョブからLAVAのAPIを叩いてboot testを実行します。buildbotはLAVAのテストが終わるのを待って、LAVAのテスト結果を最終的なビルドの結果とします。
画面
画面はこんな感じですね。
buildbot
lavaテスト実行画面
lavaテスト実行画面の最下部
設定
buildbot
buildbotでのテストステップはmaster.cfgで完結してますが、テストステップの記述はファイルを分けたほうが綺麗でしょうね。
pollingの設定はこんな感じでローカルのディレクトリを直に設定。このディレクトリはgit --bare initしたリモートリポジトリではなく、単純にcloneしてきたディレクトリです。ポーリング間隔はもっと短くて良いかな。
c['change_source'].append(changes.GitPoller( '/home/masami/projects/linux-kernel', workdir='linux-workdir', branch='ktest', pollInterval=300))
スケジューリングの設定はこんな感じ。forceビルドもしますよねということで。
c['schedulers'].append(schedulers.SingleBranchScheduler( name="ktest branch test", change_filter=util.ChangeFilter(branch='ktest'), treeStableTimer=None, builderNames=["linux-master-builder"])) c['schedulers'].append(schedulers.ForceScheduler( name="force", builderNames=["linux-master-builder"]))
テストステップはこうです。
check out the source factory.addStep(steps.Git(repourl='/home/masami/projects/linux-kernel', mode='incremental')) # start build factory.addStep(steps.ShellCommand(command=["make", "mrproper"])) factory.addStep(steps.ShellCommand(command=["cp", "/home/masami/projects/build-test/vm-config", ".config"])) config_path = "%s/.config" % tmpdir_path factory.addStep(steps.ShellCommand(command=["./scripts/config", "--set-str", "CONFIG_LOCALVERSION", "-build-test"])) import multiprocessing parallel_opt = '-j%s' % multiprocessing.cpu_count() # build kernel and modules factory.addStep(steps.ShellCommand(command=["make", parallel_opt, "bzImage"])) factory.addStep(steps.ShellCommand(command=["make", parallel_opt, "modules"])) # install modules factory.addStep(steps.ShellCommand(command=["make", "INSTALL_MOD_PATH=/tmp/linux-build-test", "modules_install"])) factory.addStep(steps.ShellCommand(command=["cp", "-f", "/home/masami/projects/builder/worker/linux-master-builder/build/arch/x86/boot/bzImage", "/home/masami/projects/web/bzImage"])) factory.addStep(steps.ShellCommand(command=["dracut", "--force", "-k", "/tmp/linux-build-test/lib/modules/5.2.0-build-test+", "--kernel-image", "/home/masami/projects/web/bzImage", "--kver", "5.2.0-build-test+", "/home/masami/projects/web/initramfs-build-test.img"])) factory.addStep(steps.ShellCommand(command=["/home/masami/projects/build-test/boot-test.py", "/home/masami/projects/build-test/boot-test.yaml"])) factory.addStep(steps.ShellCommand(command=["rm", "-fr", "/tmp/linux-build-test"]))
やってることはこんな感じです。
- git cloneする
- make mrproper
- 別のディレクトリからカーネルのコンフィグをコピーする
- CONFIG_LOCALVERSIONをセット
- 使えるcpu数を調べる
- make bzImage
- make modules
- make modules_install 9 bzImageをwebサーバのディレクトリにコピー
- dracutでinitramfsを作る
- 作ったinitramfsをwebサーバのディレクトリにコピー
- LAVAのAPIを実行するスクリプトを実行してLAVAでテスト
- 後始末
カーネルのバージョンを直書きしてるのは直さないとねとか色々。
LAVA
LAVAはdockerイメージをそのまま使っていて特に設定の変更はしてません。テストはAPIから実行するのでAPI用のトークンを作ったくらいですね。APIを実行するスクリプトは↓です。テストはjobという形で登録します。jobを記述するときのフォーマットはyaml形式で、APIから登録するときはyaml形式の文字列を送ります。
#!/usr/bin/env python3 import xmlrpc.client import sys import time import yaml def create_server_instance(): username = 'user name' token = 'access token' hostname = 'localhost:10080' return xmlrpc.client.ServerProxy('http://%s:%s@%s/RPC2' % (username, token, hostname), allow_none=True) def submit_job(server, job): return server.scheduler.submit_job(job) def read_job_yaml(filepath): with open(filepath, 'r') as f: return f.read() def wait_until_job_finish(server, job_id): while True: ret = server.scheduler.job_health(job_id) health = ret['job_health'] if not health == 'Unknown': return time.sleep(5) def check_job_result(server, job_id): results_raw = server.results.get_testjob_results_yaml(job_id) try: from yaml import CLoader as Loader except ImportError: from yaml import Loader results = yaml.load(results_raw, Loader=Loader) for ret in results: r = ret['result'] if r == 'fail': return False return True if __name__ == '__main__': if not len(sys.argv) == 2: print('[-]usage: %s <path to job definition yaml file>' % sys.argv[0]) exit(1) server = create_server_instance() job = read_job_yaml(sys.argv[1]) job_id = submit_job(server, job) print('job: %d created' % job_id) wait_until_job_finish(server, job_id) ret = check_job_result(server, job_id) if ret: print('Test: success') exit(0) print('Test: fail') exit(1)
上記のスクリプトはscheduler.submit_job()でjobを登録し、scheduler.job_health()で終わるのを待っています。その後、get_testjob_results_yaml()でテスト結果をステップごとに見ていってるんですが、これは実際不要ですね。scheduler.job_health()の結果としては以下の4つで、Unkownはテスト実行中に返ってくるので結果のステータスは3個。なのでComplete以外ならエラーとしても大丈夫だと思います。
- UNKONW(実行中はこれが返ってくる)
- Complete(成功時)
- Incomplete(失敗時)
- Cancel
LAVA test case
APIでjob登録するとして、どんなyamlを書いてるのかというとこんなのです。
# simple boot test device_type: qemu job_name: kernel build test x86-64 , boot test timeouts: job: minutes: 15 action: minutes: 10 connection: minutes: 10 priority: medium visibility: public context: arch: amd64 memory: 4096 extra_options: - --append "root=/dev/sda1 audit=0 console=tty0 console=ttyS0,115200" - -serial stdio actions: - deploy: timeout: minutes: 3 to: tmpfs images: kernel: image_arg: -kernel {kernel} url: http://192.168.11.18:12080/bzImage initrd: url: http://192.168.11.18:12080/initramfs-build-test.img image_arg: -initrd {initrd} rootfs: image_arg: -drive file={rootfs} url: http://192.168.11.18:12080/ktest.qcow2 - boot: timeout: minutes: 2 method: qemu media: tmpfs prompts: - "Last login:" - "root@ktest:" auto_login: login_prompt: "ktest login:" username: "root"
contextのところでqemuに渡すパラーメータを設定しています。actionsはテスト対象のカーネル、initrdなどの取得先とqemuに渡すパラメータの設定です。rootfsはその名の通りroot filesystemです。これはfedora30のserver版を使って作りました。ディスクに3GB割り当てたけど1GBでも充分だったな。 bootのところがbootテストの内容です。auto_loginのところで指定したログインプロンプトが表示されたらusernameを送ってます。rootfsのほうでrootのパスワードは無しに設定しているのでパスワード無しでログインしてます。もちろんパスワードプロンプトとパスワードを設定することできます。promptsは期待値になる部分で、ログインに成功したときに表示されるものをリストしてます。
lavaの出力のこの辺です。
まとめ
BuildbotとLAVAを使い、ここまでの設定でカーネルを弄ってコミットするとビルド・テストが自動で実行できるようになりました🎉
[試して理解]Linuxのしくみ ?実験と図解で学ぶOSとハードウェアの基礎知識
- 作者: 武内覚
- 出版社/メーカー: 技術評論社
- 発売日: 2018/02/23
- メディア: Kindle版
- この商品を含むブログを見る