share facebook facebook2 twitter menu hatena pocket slack

2016.03.14 MON

Serverspec に送ったプルリクエストをマージして頂けたのがとても嬉しかったのでメモ

川原 洋平

WRITTEN BY川原 洋平

Serverspec に小さな機能追加のプルリクエストを送ったらマージして頂いてとても嬉しかったので、自分なりに機能追加のポイント等を整理したことをメモる。内容に誤り等あれば適宜アップデートしていく。

プルリクエスト

Added mtu option to interface resouce type by inokappa · Pull Request #551 · mizzy/serverspec
Hi, mizzy sanI added mtu option to interface resouce type becase I wanted to test NIC's MTU value.I want to write MTU check as the following.require 'spec_helper'describe interface('eth0') d...

github.com

以前にプルリクエストを送った際には色々と不勉強な点がありマージまでは至らなかったが、今回はなんとかマージして頂くところまで辿りつけて感動もひとしお。これもひとえに作者の mizzy さんやコントリビューターの方々の努力があって拡張し易い実装になっていること大きいと考えている。本当に感謝、有難うございます。

経緯

ギョームでネットワーク・インターフェースの MTU を変更する Ansible Playbook を書いて、当然テストは Serverspec でやるでしょって思っていたら MTU 値が interface リソースタイプのオプションとして定義されていなかった。当初は以下のように command リソースを利用して /sys/class/net/eth0/mtucat して値を取ればいいかなと思って見て見ぬふりをしていたが、今年の目標の一つである 「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

mizzy/specinfra
specinfra - Command Execution Framework for serverspec, itamae and so on

github.com

ということで、Serverspec と Specinfra はざっくりと以下のような関係となる。

20160110230128

(出典:書籍 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 コマンド取得の仕組みに記載されている。

ということで、今回の実装を先ほどの図に重ねると以下のようになる。

20160110231104

デバッグ方法 tips

テスト→実装→テストのサイクル

実装にあたってデバッグは以下のように行った。

  1. 実装してみる
  2. rake build でそれぞれの gem をビルド
  3. rake install:local でそれぞれの gem をインストール
  4. テストを流す
  5. テストを確認

尚、書籍 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 の詳細を理解して拡張することも出来るようになると思うので、是非、手にとって頂いて読むことをお薦めしたい。

元記事はこちら

Serverspec に送ったプルリクエストをマージして頂けたのがとても嬉しかったのでメモ