woshidan's blog

そんなことよりコードにダイブ。

RSpecでクラスの挙動や定数をテスト用のダミーにする、見慣れなかったマッチャ、普段は実行しないテストのスキップ、などの話

参加しているプロジェクトのRSpecを見ていたら見慣れないことがいろいろあったので、復習しておきます。

RSpecでクラスの挙動をダミー用のものにする

target_obj = double("target_obj") # initializeの文字のほうに深い意味はないらしい
target_obj.stub(:string) { "Stub test" }
# 以上は上と同じ target_obj = double("target_obj", :string => "Stub test")

allow(target_obj)to receive(:post) # ダミーオブジェクトである target_obj に postメソッドを生やす

# テストしたいメソッドの中で使われているクラスを初期化したらテスト用に用意したダミー実装が使われるようにする
allow(Real::Target).to receive(:new).and_return(target_obj)

# def testing_method
#   Real::Target.new.copy.string
# end

expect(testing_method).to eq "Stub test"

# 所定のspecでだけ定数を置き換えたい
stub_const("TARGET::CONST", 100)

参考

http://web-k.github.io/blog/2012/10/02/rspec-mock/ https://qiita.com/jnchito/items/640f17e124ab263a54dd

http://codenote.net/ruby/rspec/1800.html https://relishapp.com/rspec/rspec-mocks/docs/mutating-constants

match_array マッチャ便利かも

ActiveRecordから要素を取り出してくるけど、テスト用にベタがきする値と順序が一致していないので汚いコードを書いて... という記憶があったのでよさそう。

参考

https://qiita.com/jnchito/items/a4a51852c2c678b57868

RSpecで普段は実行しないテストをスキップする

describe Hoge do
  it "heavy test", type: :model, very_heavy_tests: true do # スキップ用のオプションはRails5で追加された type オプションの後に書きます
    hoge.should eq(fuga)
  end
end

describe Hoge do
  it "normal test", type: :model do # 書いてない場合は very_heavy_tests: false 扱いでこのテストは普段から実行される
    hoge.should eq(fuga)
  end
end
RSpec.configure do |c|
  c.filter_run_excluding :very_heavy_tests => true
end

参考

https://qiita.com/semind/items/cffd5c9e7ef9a108c871

現場からは以上です。

インフラエンジニアの教科書2を読みました

DockerとかAWSとか色々勉強しなければいけない感じなのですが、DockerやAWSの説明に出てくる用語がわからないということで、その辺りの基本的なことが載ってそうだし、薄いし、ということで「インフラエンジニアの教科書2」を読みました。

さらっとわかりやすい文体で書いてあるのですが、内容は

  • TCP/IP入門
  • WebオペレーションのMySQLの章
  • UNIX OSのコードの本
  • セキュリティ入門

あたりから、使う順に引っ張ってきました、という感じで、やさしそうな顔して濃いです。濃さの割にすらすら読めるので、わかった気になってないだろうか、と1ページ1ページなかなか読み進められず2~3週間くらいかかってました。

個人的に、特にありがたかったのが2章のOSのところで、自分はこの本を読んで学んだのですが、短めにまとめたそれでさえかなり重たくて読み終わった後感覚としては結構手ごたえがあったんですが、自分が何に対して手ごたえを得たのかちょっと要領を得ない感じでもやもやしていたのが結構すっきりした気がします。

この本読んで、コード的にもうちょっとどうなってるの... って思ったらこの本をめくってみると楽しいかもです。

逆に4章は、ちょっとシャーディングがレプリケーションと同じノリで書かれていたことに対してやや驚いていて、検索で出てきてほしい結果がユーザやユーザの属する組織、みたいな単位を超えるかどうか、とか、11章の拠点間のデータ保存のところみたいな部分を検討してからでないとなんとも言えないのでは。。と思いました。

他には、6章のSSL通信の基本的なところの説明や8章のセキュリティ攻撃のところがわかりやすくて、SSL通信は長いことひっそり知ったかぶりしていたので、ほっとしたのでした。

以下、読書メモの中で印象的なところを置いておいて、現場からは以上です。

  • 経路情報
    • ルータがパケットを受け取るたびにそのルータが次の転送先を自動的に選んで送り出す
    • ルータには次の経路を決めるためのルールが登録されている必要がある
    • 各ルータが経路を決める手段
      • スタティックルーティング
      • ダイナミックルーティング
        • 周りのルータと経路情報を交換し合うことで動的にルーティングが更新される
        • IGPとEGPの2種類
  • IGP(Interier Gateway Protocol)
    • 自分が管理するLAN内部のルータ同士で経路情報を交換し合うプロトコル
    • 規模別: 小規模 RIPv2, IGRP 中規模以上 OSPF, IS-IS, EIGRP
    • RIPv2
      • 各ルータが30秒間隔でルーティングテーブルをUDPマルチキャストパケットを送信し合うことで経路情報を更新
      • RIPでは最適な経路情報の選択にホップ数(間のルータ数)を使う
      • RIPが到達できるホップ数は15ホップまで
    • OSPF(Open Shortest Path First)
      • RIPv2で弱点だった冗長化経路の制定やトポロジーを意識した経路選択などを解消
      • 隣接するルータとのリンク状態を他のルータとLSA(link-state advertisement)と呼ばれるリンクステート広告パケットをやり取り
      • 上からトポロジーマップを作り、それを元に最適な経路を判断
  • EGP(Exterior Gateway Protocol)
    • EGPは外部ASのルータと経路情報を交換しあうプロトコルのことを指す
    • EGPとして用いられるプロトコルにはBGP-4やEGPなどがありますが、現在のインターネットではBGP-4が標準的に使われる
    • BGP-4ではAS(Autonomous System)という用語が使われる
      • ASとは、インターネット内の各管理ネットワーク単位のことを指し、AS単位にAS番号が割り当てられる
      • 通常ISPや大規模コンテンツプロバイダーなどでAS番号を保有している
  • VLAN
    • 「 VLAN(Virtual LAN)とは、スイッチ内を論理的に分割する機能」
    • 上の前提として、L2スイッチにはデフォルトではVLAN1しかなく、すべてのポートがVLAN1のポートになっている(なので、すべてのポート間でイーサネットフレームの転送ができる)
      • VLAN1しかなく = 1つのネットワークアドレスしかなく
      • VLAN1つにローカルネットワークのネットワークアドレスを1つ割りふれる
    • 1つの(物理的な)スイッチの中で複数のVLANをもてたりすると何が嬉しい?
      • 物理的に機器を用意する制約が緩和
  • ポートVLAN
    • 1台のL2スイッチ中のポートをいくつかのVLANに割りふれる
    • 複数台のネットワークアドレスのために複数台のスイッチがいる ... でもスイッチ買う予算なんて ... う...
      • (1つ1つのVLANにつなぐぽーと数が少ない小規模なVLANなら)ポートVLANを使えば物理的なスイッチは1台でもいけるかも...?
    • タグVLAN
      • 1つのVLANがめっちゃでかくて、レイヤー2スイッチ1台だけでは扱えず、複数のレイヤー2スイッチを使いたい ... タグVLAN
  • ダーティリード、ファジーリード、ファントムリードとは、トランザクションの隔離が不十分な場合に発生する可能性のある三つの異常な読み込み現象のことを指す
    • ダーティリード
      • 別のトランザクションコミットされていない データが読み取れる
      • コミットされてない確定前のデータ = きれいでないデータ => ダーティリード
    • ファジーリード
      • 一つのトランザクションの中で、複数回読み込んだ同じはずのデータの結果が異なり、一貫性があるデータを得られない現象
      • ダーティリードは コミットされていないデータ が読み取れる現象だが、ファジーリードはコミットされているかいないかは気にしていない
        • ただ、ダーティリードの場合はファジーリードと言わないから、実質 同じタイミングで並行して走っている他のトランザクションでコミットされた更新結果が見える 、くらいか
    • ファントムリード
      • 別のトランザクションで挿入されたデータが見える
      • ファジーリードはカラムの更新だが、ファントムリードは行単位で増えたり減ったりが見える、ということ
  • SSL通信
    • WEBブラウザからWEBサーバに対して安全に共通鍵を送付するために公開暗号方式を使う
      • 公開鍵は通信開始時にクライアントがサーバからDLするSSL証明書の中にある
      • クライアントはDLしたSSL証明書が有効かCAに問いあわせる、と書いたが、SSLではCAのルート証明書という体裁の鍵を使ってSSLサーバ証明書を復号
      • SSLサーバ証明書を復号した中身がWEBサーバの公開鍵
      • その公開鍵で通信用のクライアント側が用意した共通鍵を送る
      • 以降はWEBサーバ <=> クライアント間でクライアント側が用意した共通鍵でやりとり
      • SSLサーバ証明書が復号できなかった時はサーバが間にあわせの公開鍵を送るが、その公開鍵はサーバ以外の誰かでも復号できるかも(警告の意味)
  • Java SEとJava EE
    • Java SE
    • Java EE
      • エンタープライズエディションという名前の通り、企業向けのシステムを構築する際、有用なサーバ関係のライブラリを中心に機能がまとめられている
  • ポートスキャン
    • ポートスキャンとは、クライアントからサーバ(もしくはロードバランサーが待ち受けている場合もある)に対してTCPもしくはUDPの広い範囲のポートにリクエストを送り、どのポートでどんなサービス用デーモンが待ち受けている(Listen)かを探し出す手法
    • 攻撃者がポートスキャンを使う主な目的は、セキュリティ脆弱性のあるサーバソフトウェアが使われているポートを見つけること
  • DoS攻撃DDoS攻撃
    • DoS攻撃(Denial of Services attack)とは、ネットワーク上のコンピュータから特定のサーバに接続要求を行うことで、サーバの応答を遅くしたりダウンさせたりする攻撃手法
      • コマンドの引数によって受信した相手を停止させたり、大量のファイルを送って停止させたり、プロトコルの仕様のバグっぽいところをついたり。(感想: たくさんコンピュータ動いてるわけじゃない時点で迷惑なことしてんだな...)
  • オープンリレー
    • MTAが外部からアクセスできる状態だと、スパムメールの転送のために利用される(ふつうは自社ネットワーク内など信用できる送信元のみ転送するように設定)

mysql2のgemのインストール時にchecking for mysql_query() in -lmysqlclient... no が出ていたらC言語版のMySQLのクライアントライブラリがない

mysql2のgemをインストールするときに、

checking for mysql_query() in -lmysqlclient... no
...

のエラーが出て失敗する場合、C言語版のMySQLのクライアントライブラリがないです。

なので、たとえばMac OS X(10.11.6)の場合、

$ brew mysql

C言語版のMySQLのクライアントライブラリ(コマンドでmysqlって入れたら動きだすやつ)を入れたらインストールを進めることができます。

Macの場合は、上記のようにHomebrewを使ったり、MacPorts, MySQLインストーラを使ってインストールした場合パスの指定は不要となりますが、他のパスにすでにMySQLのクライアントが入っていてそちらを利用したい場合はオプションでそのパスを指定する必要があります。

  • --with-mysql-dir[=/path/to/mysqldir] MySQLのインストールされたパス*1
  • --with-mysql-config[=/path/to/mysql_config] MySQLにはMySQLコンパイル方法やMySQLのクライアントへの接続方法が入っている mysql_conf という設定ファイルがあって、そのパスを指定する

なお、上記の2つのオプションは一度にどちらかひとつしか使えません。

native extensionのgem苦手意識あって毎回この辺どたどたしてたんですが、めずらしくすっきりしたのでメモ!

参考

*1:ちなみに、HomebrewでインストールしたMySQLのパスは $(brew --prefix mysql) で調べられる

RailsのFormクラスについて

Railsでバリデーションを書くケースといえばビジネスロジックで場所はModel(ActiveRecord)が多いのではないかと思います。

これは、たとえばRails Tutorialで扱うような、フォームで扱うModelが一つかつ単純な場合には特に迷う必要はありません。RailsのフォームでsubmitされたパラメータをModelに渡してそのままActiveRecordのバリデーションを使うことができます。

しかし、実際のアプリケーションでは、たとえば住所+配送方法など複数のModelをユーザーの利便性を考えたりすると一つのフォームで扱うことはありうるでしょう。

こういう場合、submitされたパラメータを処理するController側がModelに対応するようにパラメータを編集するか、Model側がフォームの中身を知ってパラメータの形式とModelの対応のコードを引き受けるか、ということになりがちです。

個人的には(Webブラウザという)クライアントからくるリクエストパラメータの形を見てビジネスロジックが扱うクラスが処理できる形にして渡すのはどちらかというとコントローラ側の仕事であって、ModelがいちいちViewのことを知るな、と思うんですが、どちらにせよ見通しが悪くなりがちです。

そこで、Railsでは具体的なテーブルと対応しないForm用のモデルクラスを用意して、Modelの属性とフォームで入力するパラメータの構成が綺麗にマッチしない部分を処理させる実装のパターンがあり、それに利用するクラスをform objectというようです。

form objectで嬉しいのは

  • ActiveRecordと同じバリデーションの作法が使える
  • formオブジェクトにActiveRecordのモデルと同様に渡すことができ、バリデーションエラーのメッセージを渡す処理が書きやすい
  • これまでActiveRecordのModelに書いたりControllerに書いたりで置き場所が落ち着かなかったロジックをまとめやすい

あたりだと思います。

具体的なコードは参考元を見るとして、コード上の要点としては

  • ActiveRecordと同じバリデーションを使うために ActiveModel::Model をinclude
  • ActiveRecordと違って対応するスキーマがないので扱うパラメータを明示するために attr_accessor を書く
  • form object は form_for に渡せる(バリデーションエラーを渡したりなど便利)

参考

短いですが、今週の現場からは以上です。

fluentdについてのメモ

fluentdとは

参考: https://docs.fluentd.org/v0.12/articles/quickstart

基本Cで書かれていてRubyの薄いラッパーがあるログ収集用のライブラリ。 Fluentdは入ってきたログをJSONとして扱う。

インプットプラグインとアウトプットプラグイン

参考: https://docs.fluentd.org/v0.12/articles/quickstart 参考: https://docs.fluentd.org/v0.12/articles/life-of-a-fluentd-event

Fluentdには

  • 入力を受け付け、入力されたデータをFluentd上で共通して扱える形式であるJSON + αに加工するインプットプラグインと、
  • インプットプラグインから出力されたデータを他の場所へ時刻などをつけて整形したりフィルタリングしたりして出力するアウトプットプラグイン

がある。どのプラグインを使用するかは .conf の拡張子を持った設定ファイル(デフォルトでは fluent.conf. 詳しいパスは https://docs.fluentd.org/v0.12/articles/config-file )にxmlのような形で書いていく。

インプットプラグインは、source 要素を使い、種類(@type)と種類に対応したパラメータを指定することで、IP, ポート, 流れてきたデータの加工方法を指定される。

たとえば、 https://docs.fluentd.org/v0.12/articles/life-of-a-fluentd-event の例の

<source>
  @type http
  port 8888
  bind 0.0.0.0
</source>

では、 localhostTCPポート 8888 番から json=<JSON文字列> のボディを持つHTTPリクエストがきたら、Fluentdで扱えるJSONのデータが内部で取れる、ということになる。

アウトプットプラグインは、match 要素を使い、インプットプラグインが出してきたJSONのデータをフィルタリングしたり、特定のキーの値を加工したり、別のファイルや標準出力へ出力したりする。

match tag のように書くことで、特定のタグを持ったログのみを出力に渡すことができて、むしろフィルタリングしない場合も ** など、なにかしら書かないといけない。

また、 https://docs.fluentd.org/v0.12/articles/life-of-a-fluentd-event の例を眺めてみると

<match test.cycle>
  @type stdout
</match>

となっていて、 test.cycle のタグがついたログだけを標準出力((これは @type stdout の指定による。標準出力へログを出すアウトプットプラグインを使う、という意味))に出す、という意味になる。

インプットプラグインにより、ログがJSONとして扱えるよう加工されている

参考: https://docs.fluentd.org/v0.12/articles/life-of-a-fluentd-event

Fluentdは入ってきたログをJSONとして扱う というのを読んだ時、一瞬JSONの形でFluentdに渡さなければいけないのかと思ったのですが、そんなことはない。

たとえば、Apache のログが /var/log/httpd-access.log のパスで以下のような形式になっているとして

192.168.0.1 - - [28/Feb/2013:12:00:00 +0900] "GET / HTTP/1.1" 200 777

fluent.conf で入力元を Apache のログとするように

<source>
  @type tail
  format apache
  path /var/log/httpd-access.log
  tag apache.access
</source>

のように適切にプラグインの指定をすれば、このsourceから出てきたログを受け付けるアウトプットプラグインを書く時は、JSONのキーを加工して... みたいな感じでパラメータの指定ができる。

便宜上、Fluentdの上ではJSONとして扱う、と言ってますが、sourceで指定したインプットプラグインから出力されるデータは Event と呼ばれていて、JSON形式のデータ以外に、時刻とタグがつく。

Eventを複数の出力へ送る

参考: https://qiita.com/nagais/items/ca96af840b8061102551 参考: https://docs.fluentd.org/v1.0/articles/out_copy

基本的にログを加工して送る先は1つだと思うんですが、タグAがついたログとタグBがついたログは別のS3のバケットに送りたい、とかそういう場合は out_copy プラグインを使う。

<match pattern>
  @type copy
  <store>
    @type file
    path /var/log/fluent/myapp1
    ...
  </store>
  <store>
    ...
  </store>
  <store>
    ...
  </store>
</match>

一旦 out_copy プラグインを使うmatch要素でsourceから来た入力を受けて、store 要素で指定した別のmatch要素に送り直す形となる。

Eventをフィルタリングしたり、加工する

参考: https://docs.fluentd.org/v1.0/articles/life-of-a-fluentd-event#filters 参考: https://docs.fluentd.org/v1.0/articles/filter_record_transformer

全部のログを取ると多すぎるので、注目したいイベントのログだけ保管用のストレージへ転送、といったこともなくはないと思うけれど、そういう場合は filter 要素を使って書くフィルタープラグインを使う。

filter 要素は source 要素と match 要素の間に書くみたい。

また、Fluentdはログの転送だけじゃなくて加工も行うのだが、加工を行う場合もフィルタープラグインを使い、代表的なものが record_transformer プラグイン

たとえば、

<filter foo.bar>
  @type record_transformer
  <record>
    adding_key "Sample"
  </record>
</filter>

のようにすれば、EventのcontentsのJSON"Sample" という値の adding_key というキーが追加される。

現場からは以上です。

ウィンドウ関数に触ってみる

下準備

http://sqlfiddle.com/ の Postgress SQL 9.6 でテスト。

CREATE TABLE books (
  id int,
  name varchar,
  author varchar,
  price int
);

INSERT INTO books
VALUES
(
  1,
  'Introduciton For Dog',
  'Pochi',
  200
);

INSERT INTO books
VALUES
(
  2,
  'High Performance For Dog',
  'Pochi',
  350
);

INSERT INTO books
VALUES
(
  3,
  'Master Of Dog',
  'Pochi',
  1000
);

INSERT INTO books
VALUES
(
  4,
  'Introduciton For Cat',
  'Tama',
  500
);

INSERT INTO books
VALUES
(
  5,
  'High Performance For Cat',
  'Tama',
  700
);

まずは集約関数

SELECT author, COUNT(*) 
FROM books 
GROUP BY(author);
// 結果
author  count
Tama    2
Pochi   3

はじめてのウィンドウ関数

// 関数をかける列を指定するイメージ(関数で使う列の値で行を並び替えて...みたいなイメージ)はGROUP BYに似ているが
// WINDOW関数の基本的な構文の場合、SELECT句にOVER(PARTITION BY 列) と書く
SELECT author, COUNT(*) OVER(PARTITION BY author) 
FROM books;
author   count
Pochi   3 // countで出ている数はGROUP BYの場合と同じだが、
Pochi   3 // ウィンドウ関数の場合は集約処理がなされていないので
Pochi   3 // 行がたくさんでてくる
Tama    2
Tama    2

ウィンドウ関数は

  • 集約関数と同じく特定の列でグループ化された行の中で処理をするけど
  • 集約のように1行にまとめるのではなく、1行ごとに結果が欲しい

という場合に使うと便利で、代表的なものとしてRANKがある。ウィンドウ関数が使用可能なのはSELECT句とORDER BY句。

SELECT author, name, RANK() OVER(ORDER BY price DESC) as rank 
FROM books;
author  name                        rank // ウィンドウ関数は処理を行う列以外もSELECTできる
Pochi   Master Of Dog               1    // 集約する際に集約に使わない行を消すが、その際、集約に使った列以外はどこを残すか、
Tama    High Performance For Cat    2    // といった問題が発生しないので表示できる状態にしてても困らないとか?
Tama    Introduciton For Cat        3
Pochi   High Performance For Dog    4
Pochi   Introduciton For Dog        5

一つのSQLの中に複数のウィンドウ関数を書くことができ、それぞれのウィンドウ関数に異なるOVER句を記述することが可能。

複数のウィンドウ関数で同じOVER句を使う場合は OVER(ORDER BY price DESC)ORDER BY price DESC の部分にWindow句で名前をつけることができて、OVER句内で参照することができる。

SELECT author, name, RANK() OVER price_order as rank 
FROM books
WINDOW price_order AS (ORDER BY price DESC);

参考: https://www.postgresql.jp/document/9.4/html/tutorial-window.html

「パスを通す」とは何をしているのか

ここ3年くらい恐る恐る export PATH="$PATH:additional_path" のような行を .bash_profile に追加する日々を生きていたのですが、ようやくなんとなく意味がわかったのが嬉しかったのでメモ。

  • 「(お目当てのコマンドへの)パスを通す」というときは、お目当ての「コマンド」があるディレクトリのパスを「実行ファイル検索パス」に追加している
  • 「コマンド」とはUnix関連の用語でコンパイルが済んでおり、ファイル名を打ち込めば実行可能な状態のプログラムのファイル*1
  • 「実行ファイル検索パス」とは環境変数 PATH: 区切りで記述されているディレクトリ群のこと
  • コマンド名(というよりファイル名)を入れた時、 PATH に記述されたディレクトリのどれかの中にその名前のコマンドがないか探す*2
  • which コマンドでは PATHディレクトリ群の中に引数で与えたコマンド(という種類のファイル)を含むディレクトリがないか検索してその結果を表示している

参考

現場からは以上です。

*1:C言語のプログラムでいうと a.out 的な.

*2:探した結果、ディレクトリやファイルしかないと bash: your_file: command not found のようなエラーが出る