Serverspec に小さな機能追加のプルリクエストを送ったらマージして頂いてとても嬉しかったので、自分なりに機能追加のポイント等を整理したことをメモる。内容に誤り等あれば適宜アップデートしていく。
プルリクエスト
github.com
以前にプルリクエストを送った際には色々と不勉強な点がありマージまでは至らなかったが、今回はなんとかマージして頂くところまで辿りつけて感動もひとしお。これもひとえに作者の mizzy さんやコントリビューターの方々の努力があって拡張し易い実装になっていること大きいと考えている。本当に感謝、有難うございます。
経緯
ギョームでネットワーク・インターフェースの MTU を変更する Ansible Playbook を書いて、当然テストは Serverspec でやるでしょって思っていたら MTU 値が interface リソースタイプのオプションとして定義されていなかった。当初は以下のように command リソースを利用して /sys/class/net/eth0/mtu
を cat
して値を取ればいいかなと思って見て見ぬふりをしていたが、今年の目標の一つである 「OSS への貢献」にうってつけの案件だと思い夕飯を食べた後、すぐに実装に取り掛かった。
describe command('cat /sys/class/net/eth1/mtu') do its(:stdout) { should match /1500/ } end
ちなみに、実際の実行結果は以下のようになる。
# # Interface を確認 # $ ifconfig eth0 eth0 Link encap:Ethernet HWaddr 08:00:27:7d:bc:c9 inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:fe7d:bcc9/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:4661315 errors:0 dropped:0 overruns:0 frame:0 TX packets:3849991 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1943853709 (1.9 GB) TX bytes:1154680993 (1.1 GB) # # example # $ cat spec/localhost/sample_spec.rb require 'spec_helper' describe interface('eth0') do its(:mtu) { should eq 1500 } end # # テスト実行 # $ rake (snip) Interface "eth0" mtu should eq 1500 Finished in 0.07793 seconds (files took 0.4603 seconds to load) 1 example, 0 failures
実装
Serverspc と Specinfra
Serverspec にリソースタイプの追加を実装するにあたって、Specinfra の存在を抜きにして語ることは出来ないということを今回の実装で感じた。尚 Serverspec と Specinfra についての詳細はそれぞれ以下のリンクが詳しいので割愛。
serverspec.org
github.com
ということで、Serverspec と Specinfra はざっくりと以下のような関係となる。
(出典:書籍 Serverspec 第四章 4.1 Serverspecのアーキテクチャ 図 4-2 を模写)
今回は Linux を対象にネットワーク・インターフェースの MTU 値を取得してテストを行う為、Specinfa 及び Serverspec の両方に対して以下のような追加実装を行った。尚、リソースタイプの追加に関しては書籍 Serverspec の第四章 4.5 Serverspec のリソースタイプの拡張が参考になる。
Specinfra への追加実装
specinfra/lib/specinfra/command/linux/base/interface.rb
に以下を追加。
(snip) def get_mtu_of(name) "cat /sys/class/net/#{name}/mtu" end (snip)
元々、ネットワークインターフェースの speed
の値を取得するメソッドが存在したので、それを参考にさせて頂いて MTU 値を取得するようにコマンドを実装、メソッド名を修正した。
Serverspec への追加実装
serverspec/lib/serverspec/type/interface.rb
に以下を追加。
(snip) def mtu ret = @runner.get_interface_mtu_of(@name) val_to_integer(ret) end (snip)
クラス変数の@runner
自体は serverspec/lib/serverspec/type/base.rb
にて以下のように Specinfra の Runner クラスが定義されていることが解る。
(snip) def initialize(name=nil, options = {}) @name = name @options = options @runner = Specinfra::Runner end (snip)
また、Serverspec 側から Specinfra のコマンドを呼び出す場合のメソッド名は以下のような規則で呼び出しているようなので…
アクション_リソースタイプ_サブアクション
今回の場合には Specinfra 側で定義したメソッド名は get_mtu_of
となり、リソースタイプはinterface
となるので以下のようなメソッド名で Specinfra の command インターフェースを呼び出す。
get_interface_mtu_of
尚、上記は書籍 Serverspec の第四章 4.3.3 コマンド取得の仕組みに記載されている。
ということで、今回の実装を先ほどの図に重ねると以下のようになる。
デバッグ方法 tips
テスト→実装→テストのサイクル
実装にあたってデバッグは以下のように行った。
- 実装してみる
rake build
でそれぞれの gem をビルドrake install:local
でそれぞれの gem をインストール- テストを流す
- テストを確認
尚、書籍 Serverspec の 4.5.1 Serverspec 側の拡張には「最初にテストコードを書きます」とあるので、今後はテストコードを書くことから始めたいと思う…。
テスト
Serverspec と Specinfra それぞれのテストは以下のように書いた。これも、既存のテストコードの模倣。
- Specinfra のテスト
describe get_command(:get_interface_mtu_of, 'eth0') do it { should eq "cat /sys/class/net/eth0/mtu" } end
上記の get_command
というメソッドは spec_helper.rb に以下のように定義されている。
module GetCommand def get_command(method, *args) Specinfra.command.get(method, *args) end end
- Serverspec のテスト
describe interface('eth0') do let(:stdout) { '1500' } its(:mtu) { should eq 1500 } end describe interface('invalid-interface') do let(:stdout) { '9001' } its(:mtu) { should_not eq 1500 } end
最後に
拡張し易い
まだまだ序の口であることは否めないが Serverspec と Specinfra は拡張し易い作りになっていることを実感出来た気がする。
次は
- Serverspec と Specinfra の内部実装を引き続き勉強する
- 他の OS で mtu をチェックする実装を追加
- リソースタイプ追加
ということで
マージして頂いて本当にうれしかったです。基本的には書籍 Serverspec 第四章を熟読することで Serverspec の詳細を理解して拡張することも出来るようになると思うので、是非、手にとって頂いて読むことをお薦めしたい。