shizzard / unicorn

Configuration server

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

unicorn

Unicorn is a basic config server that gives you ability to subscribe your application processes to exact parts of config file (or files) and read events when config is changed.

Usage

Unicorn app consists of a root supervisor and a number of workers. When you load config file another worker starts and serves your requests.

Interface:

  • unicorn:load/3 to load config file (e.g. start worker);
  • unicorn:subscribe/1,2 to subscribe process for config changes;
  • unicorn:unsubscribe/1,2 to unsubscribe process;
  • unicorn:unload/1 to unload config file (e.g. stop worker);
  • unicorn:reload/1 to reload config file and notify subscribers.

Loader and document

Loader is function that is responsible for parsing of your config file format. Loader is second argument to unicorn:load/3 function and is very important.

The one agrument of loader function is binary(), contents of config file. Function should return {ok, Document} | {error, Reason}. Document is unicorn:document/1 term, that in fact is a term in jiffy-like json notation. This notation is a proplist with tupled "objects". For example, proplist

[
    {<<"foo">>, 1},
    {<<"bar">>, [1,2,3,4]},
    {<<"baz">>, [
        {<<"bux">>, <<"buz">>}
    ]}
]

will be following term in jiffy-like json notation:

{[
    {<<"foo">>, 1},
    {<<"bar">>, [1,2,3,4]},
    {<<"baz">>, {[
        {<<"bux">>, <<"buz">>}
    ]}}
]}

If your parser returns exact proplist, you can use unicorn:to_document/1 function to transform proplist to document.

Validator

Validator is another function, that is passed to unicorn:load/3 function. Validator is responsible for config file validation stuff.

The one argument of validator function is unicorn:document/0 term. Function should return {ok, Document} | {error, ErrorList}. As validation routines are optional, you can use fun(Document) -> {ok, Document} end. fun to stub validation.

ErrorList is obviously a list of errors. This list will be returned on erroneous unicorn:load/3 and unicorn:reload/1 calls.

Events

Events are described in include/unicorn_client.hrl file. Those two events are obvious and will be sent to corresponding process in config reload and config unload respectively.

Test usage (file based)

Test unicorn launch uses TOML as config format (usin etoml as parser) and jiffy_v as validator.

Clone repository, run make dev and use standalone app.

Load priv/test.toml file and subscribe for changes:

$ make dev
rebar -Crebar_dev.config get-deps
==> etoml (get-deps)
==> jiffy_v (get-deps)
==> unicorn (get-deps)
rebar -Crebar_dev.config -DUNICORN_DEVEL compile
==> etoml (compile)
==> jiffy_v (compile)==> unicorn (compile)erl -sname unicorn -cookie unicorn -pa ebin -pa deps/etoml/ebin -pa deps/jiffy_v/ebin -s unicorn dev_start

Erlang R16B03-1 (erts-5.10.4) [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

Eshell V5.10.4  (abort with ^G)
(unicorn@shizz-worktop)1> unicorn:load(<<"priv/test.toml">>, fun unicorn:dev_loader/1, fun unicorn:dev_validator/1).
[unicorn debug] 'priv/test.toml' started for '<<"priv/test.toml">>' file
ok
(unicorn@shizz-worktop)2> unicorn:subscribe(<<"priv/test.toml">>, [<<"database">>]).
[unicorn debug] <0.44.0> subscribed for 'priv/test.toml':[<<"database">>]
ok
(unicorn@shizz-worktop)3> unicorn:subscribe(<<"priv/test.toml">>, [<<"worker">>]).
[unicorn debug] <0.44.0> subscribed for 'priv/test.toml':[<<"worker">>]
ok

After that make some changes in the file:

$ cp priv/test3.toml priv/test.toml

Reload config and get some incoming messages:

(unicorn@shizz-worktop)4> unicorn:reload(<<"priv/test.toml">>).
[unicorn debug] 'priv/test.toml' received reload signal
[unicorn debug] Diff for file <<"priv/test.toml">>: [{<<"database">>,
                                                      [{<<"max_overflow">>,0},
                                                       {<<"pool_size">>,200}]},
                                                     {<<"binding">>,
                                                      [{<<"proto1">>,
                                                        [{<<"port">>,8713}]}]},
                                                     {<<"worker">>,
                                                      [{<<"proto2">>,
                                                        [{<<"transaction_loglevel">>,
                                                          4}]}]}]
[unicorn debug] 'priv/test.toml' notified 2 subscribers
{ok,2}
(unicorn@shizz-worktop)5> flush().
Shell got {unicorn_notify,<<"priv/test.toml">>,
                          [<<"database">>],
                          [{<<"max_overflow">>,0},{<<"pool_size">>,200}],
                          [{<<"max_overflow">>,0},
                           {<<"pool_size">>,200},
                           {<<"port">>,1234},
                           {<<"server">>,<<"1.2.3.4">>}]}
Shell got {unicorn_notify,<<"priv/test.toml">>,
                          [<<"worker">>],
                          [{<<"proto2">>,[{<<"transaction_loglevel">>,4}]}],
                          [{<<"proto2">>,
                            [{<<"transaction_loglevel">>,4},
                             {<<"max_overflow">>,200},
                             {<<"pool_size">>,400}]},
                           {<<"proto1">>,
                            [{<<"transaction_loglevel">>,2},
                             {<<"max_overflow">>,200},
                             {<<"pool_size">>,300}]}]}
ok

Unload the file and get termination messages:

(unicorn@shizz-worktop)6> unicorn:unload(<<"priv/test.toml">>).
ok
(unicorn@shizz-worktop)7> flush().
Shell got {unicorn_terminate,<<"priv/test.toml">>,[<<"database">>]}
Shell got {unicorn_terminate,<<"priv/test.toml">>,[<<"worker">>]}
ok
(unicorn@shizz-worktop)8>
(unicorn@shizz-worktop)8>

Test usage (document based)

(unicorn@shizz-worktop)8> {ok, Raw} = file:read_file(<<"priv/test.toml">>).
{ok,<<"version = \"2.7.13\"\n\n[database]\n    server = \"1.2.3.4\"\n    port = 1234\n    pool_size = 100\n    max_overflow ="...>>}
(unicorn@shizz-worktop)10> {ok, Doc} = unicorn:dev_loader(Raw).
{ok,{[{<<"version">>,<<"2.7.13">>},
      {<<"database">>,
       {[{<<"max_overflow">>,200},
         {<<"pool_size">>,100},
         {<<"port">>,1234},
         {<<"server">>,<<"1.2.3.4">>}]}},
      {<<"binding">>,
       {[{<<"proto2">>,
          {[{<<"conn_timeout">>,450},{<<"ports">>,[8713,8714,8715]}]}},
         {<<"proto1">>,
          {[{<<"conn_timeout">>,500},
            {<<"ports">>,[7713,7714,7715]}]}}]}},
      {<<"worker">>,
       {[{<<"proto2">>,
          {[{<<"transaction_loglevel">>,7},
            {<<"max_overflow">>,200},
            {<<"pool_size">>,400}]}},
         {<<"proto1">>,
          {[{<<"transaction_loglevel">>,2},
            {<<"max_overflow">>,200},
            {<<"pool_size">>,300}]}}]}}]}}
(unicorn@shizz-worktop)11> unicorn:load_document(foo, Doc, fun unicorn:dev_validator/1).
[unicorn debug] foo started
ok
(unicorn@shizz-worktop)12> unicorn:get(foo, [<<"database">>, <<"server">>]).
{ok,{1,2,3,4}}

Test usage can be found in unicorn.erl file in -ifdef(UNICORN_DEVEL). section.

ToDo

  • Write tests

About

Configuration server


Languages

Language:Erlang 98.9%Language:Makefile 1.1%