18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

serverspec-runnerにて独自リソースを使う

Last updated at Posted at 2015-02-12

foa6fq.png

serverspec-runnerとは?

インフラの状態をテストするフレームワークserverspecを複数のホストに対し気軽に使えて、わかりやすい結果を表示するツールです。
詳しくは↓こちら

serverspec-runnerを使って複数のホストのテストレポートを作る

serverspec-runner@github.com

独自リソースを使う理由

serverspec-runnerはserverspec.orgのリソースに準拠しています。
serverspecを使い込んでくると、どうしても組み込みのリソースでは足りず、その場合はcommandリソースによるコマンドラインやシェルスクリプトの実行になってしまい、コードの再利用性や可読性が下がってしまうため、独自のリソースを使った方が良いケースが出てくるかと思います。

serverspecのリソース拡張について

serverspecの拡張方法は作者のmizzy氏自らO'Reilly Japanの「Serverspec」にて1章を使って丁寧に詳しく書かれております。
serverspec-runnerでリソース拡張する際もここで書かれている内容の通りコードを記述します。
尚、この技術書にて詳細が書かれておりますのでserverspec自体の拡張に関してはここではあまり触れません。ご興味がある方は一読することをお勧めします!

事前準備

rubyが使用できる環境にてserverspec-runnerをインストールし、テストコードのスケルトンを生成します。

gem install serverspec-runner

テストで使用するディレクトリ/tmp/testroot を指定します。
以降「spec_root」と呼ぶ事にします。

serverspec-runner -r /tmp/testroot
want to create spec-tree to /tmp/testroot? (y/n): y
cd /tmp/testroot

spec_rootディレクトリ

lsコマンドで見ると以下のようなファイル・ディレクトリが生成されています。

サンプル1: 既存リソースを拡張

実用性やテスト内容の妥当性はとりあえず置いといて、ファイルがテキストファイルかを判別するリソース拡張をしてみます。

$spec_root/lib/extension/serverspec/type/file.rb
module Serverspec::Type
  class File < Base
    def text?
      @runner.check_file_is_text(@name)
    end
  end
end

テキストファイル判別はfileコマンドの結果をgrepすることにしてみます。

$spec_root/lib/extension/specinfra/command/base/file.rb
class Specinfra::Command::Base::File < Specinfra::Command::Base
  class << self
    def check_is_text(file)
      "file #{escape(file)} | egrep ' text$'"
    end
  end
end

拡張部分は以上です。
今度はテストコードのほうから、本家のfileリソースには無いbe_textを使用してみます。

$spec_root/spec/example/default.rb
require 'spec_helper'

describe file('/tmp/anyfile') do
  it { should be_text }
end

テストを成功させるために/tmp/anyfile を作ります。

echo 'あいうえお' > /tmp/anyfile

それでは結果を試してみましょう。
尚、今回は生成時デフォルトのscenario.ymlによるローカルホストのテストですが、リモートホストに実行するにはこちらを参照して下さい。

serverspec-runner
+---------------------------------------+
|description                   | result |
+---------------------------------------+
|example@anyhost-01(127.0.0.1) |        |
|  File "/tmp/anyfile"         |        |
|                              |   OK   |
+---------------------------------------+

今度は失敗するケースを試してみます。

dd if=/dev/zero of=/tmp/anyfile count=10 bs=1
serverspec-runner
+---------------------------------------+
|description                   | result |
+---------------------------------------+
|example@anyhost-01(127.0.0.1) |        |
|  File "/tmp/anyfile"         |        |
|                              |   NG   |
+---------------------------------------+

サンプル2: 新たにリソースを定義

今度はmysqlという全く新しいリソースを定義し、レプリケーションが正常にできているかチェックできるようにしてみます。構文は以下のようにしようと思います。

be_replicated.from(<レプリケーションマスターサーバのIPアドレス>)
  .with_user(<mysqlログイン用ユーザ>)
  .with_password(<mysql用ログインパスワード>)
  .with_port(<mysqlログイン用ポート>)

Serverspec::Type及びSpecinfra::Command クラスを定義します。

$spec_root/lib/extension/serverspec/type/mysql.rb
module Serverspec::Type
  class Mysql < Base
    def replicated?(master=nil, user=nil, password=nil, port=nil)
      @runner.check_mysql_is_replicated(master, user, password, port)
    end
  end
end
$spec_root/lib/extension/specinfra/command/linux/base/mysql.rb
class Specinfra::Command::Linux::Base::Mysql < Specinfra::Command::Base
  class << self
    def check_is_replicated(master=nil, user=nil, password=nil, port=nil)
      opt_user     = "--user=#{user} " || ''
      opt_password = "--password=#{password} " || ''
      opt_port     = "--port=#{port} " || ''

      cmd = ''
      cmd += "echo 'show slave status \\G;' | mysql #{opt_user} #{opt_password} #{opt_port} | "
      cmd += "grep -e 'Slave_IO_Running: Yes' -e 'Slave_SQL_Running: Yes' -e 'Master_Host: #{master}' | "
      cmd += "wc -l | grep -w 3"
      cmd
    end
  end
end

be_replicatedという新しい構文はRSpecのマッチャに処理されないので、自前で定義します。

$spec_root/lib/extension/serverspec/matcher/be_replicated.rb
RSpec::Matchers.define :be_replicated do
  match do |host|
    host.replicated?(@master, @user, @password, @port)
  end

  chain :from do |master|
    @master = master
  end

  chain :with_user do |user|
    @user = user
  end

  chain :with_password  do |password|
    @password = password
  end

  chain :with_port do |port|
    @port = port
  end
end

最後に、serverspecのフレームワークがServerspec::Helper::Typeにてやっていることと同じように、新しいリソースタイプを認識させるようにします。

$spec_root/lib/extension/serverspec/helper/type.rb
module Serverspec
  module Helper
    module Type
      types = %w(
        mysql
      )

      types.each {|type| require "extension/serverspec/type/#{type}" }

      types.each do |type|
        define_method type do |*args|
          name = args.first
          eval "Serverspec::Type::#{type.to_camel_case}.new(name)"
        end
      end
    end
  end
end

以上が拡張部分です。次にテストコードを書きます。

$spec_root/spec/example/default.rb
require 'spec_helper'

describe mysql("replication test") do
  it { should be_replicated.from('192.168.10.10')
         .with_user('repluser')
         .with_password('replpasswd')
         .with_port('3306') }
end

それでは試してみましょう。
(予め下記ホスト間にてリプリケーションは正常にされているものとします)

serverspec-runner
+---------------------------------------+
|description                   | result |
+---------------------------------------+
|example@anyhost-01(127.0.0.1) |        |
|  Mysql "replication test"    |        |
|                              |   OK   |
+---------------------------------------+

最後に

O'Reilly Japanの「Serverspec」github.comのserverspec/specinfraページを見てserverspec-runner側からリソースを拡張する方法を実装してみました。
serverspecそのものの拡張に対しての間違いやもっとスマートな実装がありましたらコメントにでもこっそり教えて頂けると嬉しいです。

18
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?