Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active September 12, 2017 23:56
Show Gist options
  • Save voluntas/5002689 to your computer and use it in GitHub Desktop.
Save voluntas/5002689 to your computer and use it in GitHub Desktop.

Webmachine を使ったホットスワップ

更新:2013-02-22
バージョン:0.0.4
作者:@voluntas
URL:http://voluntas.github.com/

一度もサーバは落としてません

今回のベース

https://github.com/voluntas/snowflake/tree/feature/webmachine

ホットスワップ

Erlang は動いたままで無停止でコードを変更する事が出来ると言われますが、実際に見たことがある人はほとんどいない気がしたので、ウェブの API をホットスワップして切り替えるというサンプルを書いてみることにしました。

流れは以下の通りです。

  1. 普通にデプロイして起動
  2. 戻している JSON の一部の名前が間違っていた
  3. ホットスワップでアップデートする。

アプリを起動

rebar generate で rel 以下にリリースデータを生成します。

$ ./rebar compile
==> mochiweb (compile)
==> webmachine (compile)
==> rel (compile)
==> snowflake (compile)
Compiled src/snowflake_wm_user.erl
$ ./rebar generate
==> rel (generate)
$ mv rel/snowflake rel/snowflake_1
$ ./rebar clean
==> mochiweb (clean)
==> webmachine (clean)
==> rel (clean)
==> snowflake (clean)
$ ./rel/snowflake_1/bin/snowflake console
Exec: /tmp/snowflake/rel/snowflake_1/erts-5.9.3/bin/erlexec -env ERL_LIBS /tmp/snowflake/rel/snowflake_1/lib -boot /tmp/snowflake/rel/snowflake_1/releases/1/snowflake -config /tmp/snowflake/rel/snowflake_1/releases/1/sys.config -args_file /tmp/snowflake/rel/snowflake_1/releases/1/vm.args -- console
Root: /tmp/snowflake/rel/snowflake_1
Erlang R15B03 (erts-5.9.3) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false] [dtrace]

(snowflake@127.0.0.1)1>

結果

httpie で API を確認します

https://github.com/jkbr/httpie

ユーザが見つからない:

$ http GET localhost:8080/users/1234
HTTP/1.1 404 Object Not Found
Content-Length: 193
Content-Type: text/html
Date: Thu, 21 Feb 2013 06:10:20 GMT
Server: MochiWeb/1.1 WebMachine/1.9.2 (someone had painted it blue)

<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY><H1>Not Found</H1>The requested document was not found on this server.<P><HR><ADDRESS>mochiweb+webmachine web server</ADDRESS></BODY></HTML>

ユーザ情報の追加:

$ http PUT localhost:8080/users/1234 password=pass -j
HTTP/1.1 201 Created
Content-Length: 36
Content-Type: application/json
Date: Thu, 21 Feb 2013 06:10:25 GMT
Location: /users/1234
Server: MochiWeb/1.1 WebMachine/1.9.2 (someone had painted it blue)

{
    "password": "pass",
    "user_id": "1234"
}

ユーザ情報の確認:

$ http GET localhost:8080/users/1234
HTTP/1.1 200 OK
Content-Length: 36
Content-Type: application/json
Date: Thu, 21 Feb 2013 06:10:27 GMT
Server: MochiWeb/1.1 WebMachine/1.9.2 (someone had painted it blue)

{
    "password": "pass",
    "user_id": "1234"
}

JSON(というか JS 標準) の命名規則は userId のため、間違っています。 サーバを止めずにホットスワップで切り替えます。

アップデータの作成

変更点は以下の通りです

  • バージョンを 1 から 2 へ上げる
  • JSON 出力の user_id を userId にする

変更差分

$ git diff
diff --git a/rel/reltool.config b/rel/reltool.config
index b0b833f..a35c90b 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -2,7 +2,7 @@
        {lib_dirs, ["../deps"]},
        {erts, [{mod_cond, derived}, {app_file, strip}]},
        {app_file, strip},
-       {rel, "snowflake", "1",
+       {rel, "snowflake", "2",
         [
          kernel,
          stdlib,
diff --git a/src/snowflake.app.src b/src/snowflake.app.src
index 08c788c..5294402 100644
--- a/src/snowflake.app.src
+++ b/src/snowflake.app.src
@@ -1,7 +1,7 @@
 {application, snowflake,
  [
   {description, ""},
-  {vsn, "1"},
+  {vsn, "2"},
   {registered, []},
   {applications, [
                   kernel,
diff --git a/src/snowflake_wm_user.erl b/src/snowflake_wm_user.erl
index ff8c8ec..6352940 100644
--- a/src/snowflake_wm_user.erl
+++ b/src/snowflake_wm_user.erl
@@ -54,7 +54,7 @@ delete_resource(RD, #ctx{user = User} = Ctx) ->


 to_json(RD, #ctx{user = User} = Ctx) ->
-    Result = mochijson2:encode({struct, [{<<"user_id">>, User#user.id},
+    Result = mochijson2:encode({struct, [{<<"userId">>, User#user.id},
                                          {<<"password">>, User#user.password}]}),
     {Result, RD, Ctx}.

@@ -65,7 +65,7 @@ from_json(RD, #ctx{user_id = undefined, user = User} = Ctx) ->
     {struct, Json} = mochijson2:decode(RawJson),
     Password = proplists:get_value(<<"password">>, Json),
     ok = snowflake_user:assign(User#user.id, Password),
-    NewJson = mochijson2:encode({struct, [{<<"user_id">>, User#user.id},
+    NewJson = mochijson2:encode({struct, [{<<"userId">>, User#user.id},
                                           {<<"password">>, Password}]}),
     RD2 = wrq:set_resp_body(NewJson, RD),
     {true, RD2, Ctx};
@@ -75,7 +75,7 @@ from_json(RD, Ctx) ->
     {struct, Json} = mochijson2:decode(RawJson),
     Password = proplists:get_value(<<"password">>, Json),
     ok = snowflake_user:assign(list_to_binary(Ctx#ctx.user_id), Password),
-    NewJson = mochijson2:encode({struct, [{<<"user_id">>, list_to_binary(Ctx#ctx.user_id)},
+    NewJson = mochijson2:encode({struct, [{<<"userId">>, list_to_binary(Ctx#ctx.user_id)},
                                           {<<"password">>, Password}]}),
     RD2 = wrq:set_resp_body(NewJson, RD),
     RD3 = wrq:set_resp_header("Location", "/users/" ++ Ctx#ctx.user_id, RD2),

アップデート用のパッケージを作成します:

$ ./rebar generate
==> rel (generate)
$ ./rebar generate-appups previous_release=snowflake_1
==> rel (generate-appups)
Generated appup for snowflake
Appup generation complete
$ ./rebar generate -f
==> rel (generate)
$ ./rebar generate-upgrade previous_release=snowflake_1
==> rel (generate-upgrade)
snowflake_2 upgrade package created
$ cp rel/snowflake_2.tar.gz rel/snowflake_1/releases

ポイントは rel/snowflake_1 の releases の下に作成したアップデートパッケージを置くことです。

アップデータの適用

起動しているサーバで以下のコマンドを実行します:

Eshell V5.9.3  (abort with ^G)
(snowflake@127.0.0.1)1> release_handler:which_releases().
[{"snowflake","1",
  ["kernel-2.15.3","stdlib-1.18.3","sasl-2.2.1","crypto-2.2",
   "inets-5.9.2","mochiweb-1.5.1p3","webmachine-1.9.3",
   "snowflake-1","asn1-1.8","compiler-4.8.2","mnesia-4.7.1",
   "public_key-0.17","runtime_tools-1.8.9","ssl-5.1.1",
   "syntax_tools-1.6.9","tools-2.6.8","xmerl-1.3.2"],
  permanent}]
(snowflake@127.0.0.1)2> release_handler:unpack_release("snowflake_2").
{ok,"2"}
(snowflake@127.0.0.1)3> release_handler:install_release("2").
{ok,"1",[]}
(snowflake@127.0.0.1)4> release_handler:which_releases().
[{"snowflake","2",
  ["kernel-2.15.3","stdlib-1.18.3","sasl-2.2.1","crypto-2.2",
   "inets-5.9.2","mochiweb-1.5.1p3","webmachine-1.9.3",
   "snowflake-2","asn1-1.8","compiler-4.8.2","mnesia-4.7.1",
   "public_key-0.17","runtime_tools-1.8.9","ssl-5.1.1",
   "syntax_tools-1.6.9","tools-2.6.8","xmerl-1.3.2"],
  current},
 {"snowflake","1",
  ["kernel-2.15.3","stdlib-1.18.3","sasl-2.2.1","crypto-2.2",
   "inets-5.9.2","mochiweb-1.5.1p3","webmachine-1.9.3",
   "snowflake-1","asn1-1.8","compiler-4.8.2","mnesia-4.7.1",
   "public_key-0.17","runtime_tools-1.8.9","ssl-5.1.1",
   "syntax_tools-1.6.9","tools-2.6.8","xmerl-1.3.2"],
  permanent}]
(snowflake@127.0.0.1)5>

release_handler:unpack_relase/1 で解凍して release_handler:install_release/1 でバージョン番号を指定してインストールします。

ホットスワップ後に確認

user_id が userId になっていることを確認します

userId になってる:

$ http GET localhost:8080/users/1234
HTTP/1.1 200 OK
Content-Length: 35
Content-Type: application/json
Date: Thu, 21 Feb 2013 06:13:51 GMT
Server: MochiWeb/1.1 WebMachine/1.9.2 (someone had painted it blue)

{
    "password": "pass",
    "userId": "1234"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment