• このエントリーをはてなブックマークに追加


はじめに

新しく Web アプリケーション的なものを作る際のデータベースを経由するテストの方法あたりで(以前からもそうだけど,特に)昨日 1日悩んだり試行錯誤で書いたものなどを.

要件

やりたいこと

  • 「このテーブルのデータはこのパタン,あのテーブルのデータはあのパタン」な感じの組み合わせで fixture を構成したい
  • fixture はそれなりに可読に,場合によっては直接修正可能に
  • prove 実行の際に読み込み!とかではなく,テストファイル単位とかでも実行できたい
  • データベース上のデータをクリアしたい(AUTO_INCREMENT もリセット)
  • DBIx::FixtureLoader が便利そうなので利用する(fixture のファイルフォーマットで悩まずに済むし)

fixture ファイルの構成

  • テーブルごと
  • テーブルごとに複数のデータパタンに相当するファイルがある

で, 次のような感じに:

/path/to/myapp/t/fixture
├── event
│   └── 0.yml
└── user
    ├── 0.yml
    └── 1.yml

インタフェイス

とりあえず名前を Test::FixtureManager とかにして,次のような感じで使いたい:

# set env name
local $Test::FixtureManager::ENV_NAME = 'MYAPP_ENV';  # default: PLACK_ENV
 
# new ( dies when $ENV{MYAPP_ENV} is not "test" )
my $dbh = DBI->connect( ... );
my $fm = Test::FixtureManager->new(
    dbh      => $dbh,
    data_dir => '/path/to/myapp/t/fixture',
);
 
# テーブル "user" の,パタン "0" を読み込む
$fm->load( data => 'user:0' );
 
# データ全消し
$fm->clear_all();
# or テーブル "user" のデータを消す
$fm->clear( table => 'user' );
 
# テーブル "user" のパタン "1", テーブル "event" のパタン "0" を読み込む
$fm->load( data => [qw( user:1 event:0 )] );
 
# テーブル "user" "event" のデータを消す
$fm->clear( table => [qw( user event )] );
 
# データを消した後,テーブル "user" のパタン "1", テーブル "event" のパタン "0" を読み込む
$fm->reload( data => [qw( user:1 event:0 )] );

fixture をどうこうしたいテストの冒頭とかでこういったことができればよさげ.

モジュールのプロトタイプ

上記要件とかインタフェイスとかを基にプロトタイプ.

今のところ MySQL のみしか考えていないので SQL 直書きです><

package Test::FixtureManager::Types;
use 5.12.0;
use warnings;
use MouseX::Types -declare => [qw( TableVersionToken )];
use MouseX::Types::Mouse qw( Str );
 
subtype TableVersionToken, as Str, where { /^[^:]+:[^:]+$/ };
 
1;
 
package Test::FixtureManager;
use strict;
use warnings;
use Mouse;
use DBIx::FixtureLoader;
use Data::Validator;
 
use MouseX::Types::Mouse qw(
    Bool Str ArrayRef
    is_ArrayRef
);
 
use constant +{
    TableVersionToken => Test::FixtureManager::Types::TableVersionToken(),
};
 
our $ENV_NAME = 'PLACK_ENV';
 
has 'dbh' => ( is => 'ro', isa => 'DBI::db' );
has 'loader' => ( is => 'ro', isa => 'DBIx::FixtureLoader' );
has 'data_dir' => ( is => 'ro', isa => Str );
 
sub BUILDARGS {
    my ($class, %args) = @_;
    my $env = $ENV{$ENV_NAME} || '';
    unless ( $env eq 'test' ) {
        die qq{available only in environment "${ENV_NAME}=test"};
    }
 
    my $v = Data::Validator->new(
        dbh      => { isa => 'DBI::db' },
        data_dir => { isa => Str },
    );
    %args = %{ $v->validate( \%args ) };
 
    my $loader = DBIx::FixtureLoader->new( dbh => $args{dbh} );
 
    %args = (
        %args,
        loader => $loader,
    );
 
    return \%args;
}
 
sub clear {
    my ($self, %args) = @_;
    my $v = Data::Validator->new(
        table => { isa => Str|ArrayRef[Str] },
    );
    %args = %{ $v->validate( \%args ) };
 
    my @table = is_ArrayRef( $args{table} ) ? @{$args{table}} : ( $args{table} );
    my $dbh = $self->dbh;
    for my $table ( @table ) {
        my $sth = $dbh->prepare( "SHOW CREATE TABLE ${table}" );
        $sth->execute();
        my (undef, $sql_create) = $sth->fetchrow_array();
        $dbh->do( "DROP TABLE ${table}" );
        $dbh->do( $sql_create );
    }
 
    return @table;
}
 
sub clear_all {
    my ($self, %args) = @_;
    my $v = Data::Validator->new();
    %args = %{ $v->validate( \%args ) };
 
    my @table;
 
    my $sth = $self->dbh->prepare( "SHOW TABLES" );
    $sth->execute();
    while ( my @row = $sth->fetchrow_array() ) {
        push @table, $row[0];
    }
 
    return $self->clear( table => \@table );
}
 
sub load {
    my ($self, %args) = @_;
    my $v = Data::Validator->new(
        data   => { isa => TableVersionToken|ArrayRef[TableVersionToken] },
        _clear => { isa => Bool, default => 0 },  # for internal use
    );
    %args = %{ $v->validate( \%args ) };
 
    my @table;
    my @data = is_ArrayRef( $args{data} ) ? @{$args{data}} : ( $args{data} );
    @data = map {
        my ($table, $ver) = split /:/, $_;
        my $g = sprintf '%s/%s/%s.*', $self->data_dir, $table, $ver;
        my ($f) = glob $g;
        unless ( defined $f ) {
            die "$f: fixture data file does not exist";
        }
        +{ file => $f, table => $table, version => $ver };
    } @data;
 
    if ( $args{_clear} ) {
        $self->clear_all();
    }
 
    for my $data ( @data ) {
        my ($file, $table) = @$data{qw(file table)};
        $self->loader->load_fixture( $file, table => $table );
        push @table, $table;
    }
 
    return @table;
}
 
sub reload {
    my ($self, %args) = @_;
    return $self->load( _clear => 1, %args );
}
 
__PACKAGE__->meta->make_immutable();

おわりに

とりあえず GitHub に上げるところからですかね.

このコードがもうちょっとプロジェクトローカルな感じに書き換わったものは,そのテスト用データベースを利用することで最低限テストできているけど,モジュールとして切り出した場合,データベース test を利用するにしても ->clear_all() とかやばいな,どうしようw

そのテスト独自かつ他で使いまわすこともないようなデータパタンであれば,そのテストファイルの __DATA__ セクションに記述するっていうのもよいかもしれない.

  • このエントリーをはてなブックマークに追加


2014-04-26 11.54.50

とりあえずコードレスなものを使ってみたかったので,その入門的なものを.アイロン台はおもしろそうと思ったものをw

あと,ついでに Docker についても勉強を進めておきたいのでこっちも買った.さっそく読む.

  • このエントリーをはてなブックマークに追加


IMGP1138

はじめに

以前に自転車用サングラスを作ってもらった メガネのサカイさん で,かれこれ 7年くらいぶりになるだろうか,めがねを新調しました.

めがね

pls.pls. の PCB-38 というモデル:

IMGP1124

テンプルの一部に竹が使われていて,ここに一目惚れしましたw:

IMGP1126

こちらで紹介されているのはフロントが黒色なものですが,次にめがねを買うなら黒系・暗色系でないものを,などと思っていたので,店頭に並んでいたラベンダーなものを選んでみました.

ケース

店員さんの「ケースは pls.pls. のオリジナルになりますので」ということば,申し込む段階では特にどうと思っていませんでした.

が,できあがっためがねを受け取る際に,何事か?というようなものが出てきましたw

IMGP1128
IMGP1129
IMGP1135

さすがにバッグに入れて持ち運ぶようなものではないでしょうから,外用のケースが必要な場合は,サングラスのやつを使おうと思います(それでもそこそこ大きい).

おわりに

IMGP1136

以上,めがねをかなり久しぶりに新しくしてみたよ,というお話(写真)でした.

めがねとともに,頭の中も切り替えていきたいものです.

  • このエントリーをはてなブックマークに追加


お手本

を基に,ベースを ubuntu:13.04 としてビルドしてみる練習.イメージ名は test/nodejs_app

ファイル構成を準備する

構成

$ cd ~/tmp/nodejs_app
$ tree
.
├── Dockerfile
└── src
    ├── index.js
    └── package.json
 
1 directory, 3 files

アプリケーション関連

index.jspackage.json はチュートリアルのとおり.(express のバージョンだけ 4.0.0 に変更しています.)

Dockerfile

Dockerfile はとりあえずこんな感じ:

# DOCKER-VERSION 0.10.0
FROM ubuntu:13.04
RUN  apt-get update
RUN  apt-get install -y curl
RUN  mkdir /tmp/node; cd /tmp/node; curl -L "http://nodejs.org/dist/v0.10.26/node-v0.10.26-linux-x64.tar.gz" | tar zxf -
RUN  if [ ! -d /usr/local/bin ]; then mkdir /usr/local/bin; fi; cp -v /tmp/node/node-v0.10.26-linux-x64/bin/node /usr/local/bin/node
RUN  curl -L "https://www.npmjs.org/install.sh" | clean=no sh
RUN  ldconfig
 
ADD  ./src /src
RUN  cd /src; npm install
 
EXPOSE  8080
CMD [ "node", "/src/index.js" ]

RUN する内容がふくれてきた場合は,シェルスクリプトとかに落としこんで,それを ADD して RUN bash /script/foo.sh みたいにすればすっきりするでしょうか.

ビルドする!

$ sudo docker build -t test/nodejs_app:bar .
Uploading context  72.7 kB
Uploading context
Step 0 : FROM    ubuntu:13.04
 ---> ab4344e23e3a
Step 1 : RUN     apt-get update
 ---> Running in 211a196ce52b
Hit http://archive.ubuntu.com raring Release.gpg
...
Hit http://archive.ubuntu.com raring-security/universe amd64 Packages
Reading package lists...
 ---> 8a247da1250e
Step 2 : RUN     apt-get install -y curl
 ---> Running in 033260120f50
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
...
Running hooks in /etc/ca-certificates/update.d....done.
 ---> a13ba87694ea
Step 3 : RUN     mkdir /tmp/node; cd /tmp/node; curl -L "http://nodejs.org/dist/v0.10.26/node-v0.10.26-linux-x64.tar.gz" | tar zxf -
 ---> Running in 77f96b1eeef2
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 5190k  100 5190k    0     0  1549k      0  0:00:03  0:00:03 --:--:-- 1560k
 ---> 794382b45dac
Step 4 : RUN     if [ ! -d /usr/local/bin ]; then mkdir /usr/local/bin; fi; cp -v /tmp/node/node-v0.10.26-linux-x64/bin/node /usr/local/bin/node
 ---> Running in 7e3febdb7802
'/tmp/node/node-v0.10.26-linux-x64/bin/node' -> '/usr/local/bin/node'
 ---> 8e195c75dd5e
Step 5 : RUN     curl -L "https://www.npmjs.org/install.sh" | clean=no sh
 ---> Running in 42bddedda16c
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  6711  100  6711    0     0  19219      0 --:--:-- --:--:-- --:--:-- 21648
tar=/bin/tar
...
install npm@latest
fetching: http://registry.npmjs.org/npm/-/npm-1.4.6.tgz
0.10.26
1.4.6
Skipping 0.x cruft clean
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
npm@1.4.6 /usr/local/lib/node_modules/npm
It worked
 ---> 6df4401438df
Step 6 : RUN     ldconfig
 ---> Running in d3b1d42030e9
 ---> b11e4f73888f
Step 7 : ADD  ./src /src
 ---> fd41aca555a4
Step 8 : RUN  cd /src; npm install
 ---> Running in 74048f492be8
npm http GET https://registry.npmjs.org/express/4.0.0
npm http 200 https://registry.npmjs.org/express/4.0.0
...
express@4.0.0 node_modules/express
├── methods@0.1.0
├── parseurl@1.0.1
...
├── accepts@1.0.0 (mime@1.2.11, negotiator@0.3.0)
└── serve-static@1.0.1 (send@0.1.4)
 ---> 9367e551873d
Step 9 : EXPOSE  8080
 ---> Running in 490f24363506
 ---> 71870cdca344
Step 10 : CMD [ "node", "/src/index.js" ]
 ---> Running in 2549cebac77c
 ---> c465062641f5
Successfully built c465062641f5
Removing intermediate container 77f96b1eeef2
Removing intermediate container 7e3febdb7802
Removing intermediate container 3b6ec29995a9
Removing intermediate container 211a196ce52b
Removing intermediate container 033260120f50
Removing intermediate container 42bddedda16c
Removing intermediate container d3b1d42030e9
Removing intermediate container 74048f492be8
Removing intermediate container 490f24363506
Removing intermediate container 2549cebac77c

ビルド完了.

$ sudo docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
test/nodejs_app     bar                 c465062641f5        19 minutes ago      230.9 MB
...

イメージができあがりました.

ステップごとのキャッシュ?

そういえば,「成功したステップはキャッシュされる」って,clairvy さんが このあたりのエントリ 書いてたときに言ってた気がします.

確かに,apt-get update のステップとか再ビルドの時とか発動しなかった感じです.(あとで調べたら,--no-cache ってオプションがあり,まぁその名のとおりですね.)

ビルドしたイメージからコンテナを起動する

コンテナを起動する

このあたりはチュートリアルに従って:

$ sudo docker run -p 49160:8080 -d test/nodejs_app
5202d212e7b73b73ede8ff8297b1386448525f0a74c634acf49dfa510daf185b
$ sudo docker ps
CONTAINER ID        IMAGE                    COMMAND              CREATED             STATUS              PORTS                     NAMES
5202d212e7b7        test/nodejs_app:latest   node /src/index.js   14 seconds ago      Up 14 seconds       0.0.0.0:49160->8080/tcp   lonely_wozniak

HTTP リクエストする

$ curl http://localhost:8080/
curl: (7) couldn't connect to host

これは間違いw

$ curl http://localhost:49160/
Hello World

きました.

コンテナに IP アドレスを割り当てて HTTP リクエストしてみる

前エントリ で練習した Pipework を,ここで使ってみます.192.168.10.10 として割り当てた後,http://192.168.10.10:8080/ へリクエストしてみます:

$ sudo pipework br1 5202d212e7b7  192.168.10.10/24
$ curl http://192.168.10.10:8080/
Hello World

期待どおりの結果が得られました.

  • このエントリーをはてなブックマークに追加


参考

ポイントは,次の 2点になるでしょうか:

  • Pipework でブリッジを追加する & ゲストへの IP アドレスを割り当てる
  • ホストのブリッジに IP アドレスを割り当てる

ホスト OS の環境

さくらの VPS,Ubuntu 12.04.

インタフェイス eth0 はこんな感じ:

$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether **:**:**:**:**:** brd ff:ff:ff:ff:ff:ff
    inet ***.***.***.***/23 brd ***.***.***.255 scope global eth0
    inet6 ****::****:***:***1:****/64 scope link
       valid_lft forever preferred_lft forever

参考サイトのとおりに進めてみる

Pipework を入れる

$ cd /usr/local/src
$ sudo git clone https://github.com/jpetazzo/pipework.git
Cloning into 'pipework'...
remote: Reusing existing pack: 134, done.
remote: Total 134 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (134/134), 36.66 KiB, done.
Resolving deltas: 100% (68/68), done.
$ sduo ln -sv /usr/local/src/pipework/pipework /usr/local/bin/
`/usr/local/bin/pipework' -> `/usr/local/src/pipework/pipework'
$ sudo pipework
Syntax:
pipework <hostinterface> [-i containerinterface] <guest> <ipaddr>/<subnet>[@default_gateway] [macaddr]
pipework <hostinterface> [-i containerinterface] <guest> dhcp [macaddr]
pipework --wait

Pipework 実行時にエラー?

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
0c563183d391        base:latest         /bin/bash           7 minutes ago       Up 7 minutes                            backstabbing_kowalevski
$ sudo pipework br1 0c563183d391 192.168.1.10/24
/usr/local/bin/pipework: line 152: brctl: command not found

brctl というものがないらしい.

bridge-utils パッケージを入れればよいらしい.

$ sudo apt-get -y install bridge-utils
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  python-central libvncserver0 libgsoap1
Use 'apt-get autoremove' to remove them.
The following NEW packages will be installed:
  bridge-utils
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 32.0 kB of archives.
After this operation, 142 kB of additional disk space will be used.
Get:1 http://jp.archive.ubuntu.com/ubuntu/ precise-updates/main bridge-utils amd64 1.5-2ubuntu7 [32.0 kB]
Fetched 32.0 kB in 0s (141 kB/s)
Selecting previously unselected package bridge-utils.
(Reading database ... 106135 files and directories currently installed.)
Unpacking bridge-utils (from .../bridge-utils_1.5-2ubuntu7_amd64.deb) ...
Processing triggers for man-db ...
Setting up bridge-utils (1.5-2ubuntu7) ...
$ sudo pipework br1 0c563183d391 192.168.1.10/24
Warning: arping not found; interface may not be immediately reachable

さらに 「arping がない」と言われるので,これも入れる.

$ sudo apt-get -y install arping
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  python-central libvncserver0 libgsoap1
Use 'apt-get autoremove' to remove them.
The following extra packages will be installed:
  libnet1
The following NEW packages will be installed:
  arping libnet1
0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded.
Need to get 75.3 kB of archives.
After this operation, 267 kB of additional disk space will be used.
Get:1 http://jp.archive.ubuntu.com/ubuntu/ precise/main libnet1 amd64 1.1.4-2.1 [49.4 kB]
Get:2 http://jp.archive.ubuntu.com/ubuntu/ precise/universe arping amd64 2.09-2 [25.9 kB]
Fetched 75.3 kB in 0s (164 kB/s)
Selecting previously unselected package libnet1.
(Reading database ... 106160 files and directories currently installed.)
Unpacking libnet1 (from .../libnet1_1.1.4-2.1_amd64.deb) ...
Selecting previously unselected package arping.
Unpacking arping (from .../arping_2.09-2_amd64.deb) ...
Processing triggers for man-db ...
Setting up libnet1 (1.1.4-2.1) ...
Setting up arping (2.09-2) ...
Processing triggers for libc-bin ...
ldconfig deferred processing now taking place

気を取り直して

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
2aa3698efed5        base:latest         /bin/bash           7 seconds ago       Up 6 seconds                            silly_fermat
$ sudo pipework br1 2aa3698efed5 192.168.1.21/24

エラー・警告がなくなりました.

割り当て前後におけるコンテナの ifconfig を比較してみる

before:

root@2aa3698efed5:/# ifconfig -a
eth0      Link encap:Ethernet  HWaddr 96:6e:8c:76:0e:ef
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::946e:8cff:fe76:eef/64 Scope:Link
          UP BROADCAST RUNNING  MTU:1500  Metric:1
          RX packets:5 errors:0 dropped:2 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:378 (378.0 B)  TX bytes:468 (468.0 B)
 
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

after:

root@2aa3698efed5:/# ifconfig -a
eth0      Link encap:Ethernet  HWaddr 96:6e:8c:76:0e:ef
          inet addr:172.17.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::946e:8cff:fe76:eef/64 Scope:Link
          UP BROADCAST RUNNING  MTU:1500  Metric:1
          RX packets:5 errors:0 dropped:2 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:378 (378.0 B)  TX bytes:468 (468.0 B)
 
eth1      Link encap:Ethernet  HWaddr 7a:46:04:45:de:a9
          inet addr:192.168.1.21  Bcast:0.0.0.0  Mask:255.255.255.0
          inet6 addr: fe80::7846:4ff:fe45:dea9/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:468 (468.0 B)  TX bytes:510 (510.0 B)
 
lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

インタフェイス eth1 が追加され,その IP アドレスが指定したものになりました.

ホスト側のブリッジに IP アドレスを割りてる

$ sudo ip addr add 192.168.1.254/24 dev br1

これは特に問題なく.

しかし,ping 192.168.1.21 してもうまくいかず,原因を調べられるほどネットワークの知識もなく,いったん中断.

とりあえずつながった

いくらか試行錯誤?して,割り当てる IP アドレスを 192.168.1.* でないものにしたところ,とりあえず ping が通ったので,そのあたりを記録しておきます.

先の 192.168.1.21 に代わり,192.168.10.21 を割り当ててみます:

$ sudo pipework br1 ... 192.168.10.21/24
$ sudo ip addr add 192.168.10.1/24 dev br1

こうしたら ping が通りました...

コンテナをもうひとつ立ち上げて,192.168.10.22 を割り当ててみます:

$ sudo pipework br1 ... 192.168.10.22/24

ホスト → 2台目ゲスト,1台目ゲスト・2台目ゲスト間,いずれも ping が通りました.

5 of 1221...34567...102030...122