├── .gitignore ├── Makefile ├── README.md ├── rebar.config ├── src ├── nominator.erl ├── notifier_handler.erl ├── redirect_handler.erl ├── spawnpool.app.src ├── spawnpool_app.erl └── spawnpool_sup.erl ├── start.sh └── templates ├── error.dtl └── zergling_config.dtl /.gitignore: -------------------------------------------------------------------------------- 1 | deps 2 | ebin 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: default 3 | 4 | default: 5 | rebar compile 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # The spawn pool for Zerg demo 3 | 4 | This application is an integral part of Zerg demo. It gets initial connection 5 | from nginx, spawns a new 'zergling' instance (see 6 | https://github.com/maximk/zergling), and instructs nginx to proxy the client 7 | connection to the newly spawned instance. 8 | 9 | # How to setup nginx 10 | 11 | nginx configuration file must have a block similar to this: 12 | 13 | server { 14 | listen 80; 15 | server_name zerg.yourdomain.com; 16 | 17 | # Suppress spawning a second instance just to return 404 18 | # 19 | location /favicon.ico { 20 | return 404; 21 | } 22 | 23 | # A catapult location to bypass X-Accel-Redirect limitation 24 | # 25 | location ~* ^/internal_redirect/(.*?)/(.*?)/(.*) { 26 | internal; 27 | set $redirect_host $1; 28 | set $redirect_port $2; 29 | set $redirect_uri $3; 30 | set $redirect_to http://$redirect_host:$redirect_port/$redirect_uri; 31 | proxy_pass $redirect_to; 32 | } 33 | 34 | location / { 35 | proxy_set_header X-Real-IP $remote_addr; 36 | proxy_set_header Host $http_host; 37 | 38 | # Put location of your 'spawningpool' application here 39 | # 40 | proxy_pass http://localhost:9001; 41 | } 42 | } 43 | 44 | # How to run the application 45 | 46 | The application recorgizes the following command-line flags: 47 | 48 | * _-gator_ the IP address (and port) of the Dom0 running gatord. Defaults to 49 | * "127.0.0.1:4287". 50 | 51 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | 2 | {deps,[ 3 | {cowboy,".*", 4 | {git,"git://github.com/extend/cowboy.git","master"}}, 5 | {erlydtl,".*", 6 | {git,"git://github.com/evanmiller/erlydtl.git","master"}}, 7 | {egator,".*", 8 | {git,"git@bitbucket.org:mkharch/egator.git","master"}} 9 | ]}. 10 | 11 | %%EOF 12 | -------------------------------------------------------------------------------- /src/nominator.erl: -------------------------------------------------------------------------------- 1 | -module(nominator). 2 | -behaviour(gen_server). 3 | -define(SERVER, ?MODULE). 4 | 5 | %% ------------------------------------------------------------------ 6 | %% API Function Exports 7 | %% ------------------------------------------------------------------ 8 | 9 | -export([start_link/0]). 10 | 11 | -export([fresh/0]). 12 | -export([wait_until_ready/1]). 13 | 14 | -record(st, {taken =[], 15 | pending =[], 16 | ready = []}). 17 | 18 | %% ------------------------------------------------------------------ 19 | %% gen_server Function Exports 20 | %% ------------------------------------------------------------------ 21 | 22 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 23 | terminate/2, code_change/3]). 24 | 25 | %% ------------------------------------------------------------------ 26 | %% API Function Definitions 27 | %% ------------------------------------------------------------------ 28 | 29 | start_link() -> 30 | gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). 31 | 32 | fresh() -> 33 | gen_server:call(?SERVER, fresh). 34 | 35 | wait_until_ready(DomName) -> 36 | gen_server:call(?SERVER, {wait_until_ready,DomName}). 37 | 38 | %% ------------------------------------------------------------------ 39 | %% gen_server Function Definitions 40 | %% ------------------------------------------------------------------ 41 | 42 | init(_Args) -> 43 | random:seed(now()), 44 | {ok,Domains} = egator:list([]), 45 | Taken = domain_addresses([D || {D,_} <- Domains]), 46 | {ok,#st{taken =Taken}}. 47 | 48 | handle_call(fresh, _From, #st{taken =Taken} =St) -> 49 | {ok,IpAddr,Taken1} = next_free(Taken), 50 | DomName = encode_name("zergling", IpAddr), 51 | {reply,{ok,DomName,IpAddr},St#st{taken =Taken1}}; 52 | 53 | handle_call({wait_until_ready,DomName}, From, #st{pending =Pending,ready =Ready} =St) -> 54 | case lists:keytake(DomName, 1, Ready) of 55 | {value,IpAddr,Ready1} -> 56 | {reply,IpAddr,St#st{ready =Ready1}}; 57 | false -> 58 | {noreply,St#st{pending =[{DomName,From}|Pending]}} 59 | end. 60 | 61 | handle_cast({ready,DomName,IpAddr}, #st{pending =Pending,ready =Ready} =St) -> 62 | case lists:keytake(DomName, 1, Pending) of 63 | {value,{_,From},Pending1} -> 64 | gen_server:reply(From, {ok,IpAddr}), 65 | {noreply,St#st{pending =Pending1}}; 66 | false -> 67 | {noreply,St#st{ready =[{DomName,IpAddr}|Ready]}} 68 | end. 69 | 70 | handle_info(_Msg, State) -> 71 | {noreply, State}. 72 | 73 | terminate(_Reason, _State) -> 74 | ok. 75 | 76 | code_change(_OldVsn, State, _Extra) -> 77 | {ok, State}. 78 | 79 | %% ------------------------------------------------------------------ 80 | %% Internal Function Definitions 81 | %% ------------------------------------------------------------------ 82 | 83 | domain_addresses(Domains) -> domain_addresses(Domains, []). 84 | 85 | domain_addresses([], Acc) -> Acc; 86 | domain_addresses([Dom|Domains], Acc) -> 87 | case re:run(Dom, "[a-z]_([0-9]+)_([0-9]+)_([0-9]+)", 88 | [{capture,all_but_first,list}]) of 89 | {match,[B,C,D]} -> 90 | IpAddr = {10,list_to_integer(B),list_to_integer(C),list_to_integer(D)}, 91 | domain_addresses(Domains, [IpAddr|Acc]); 92 | _ -> 93 | domain_addresses(Domains, Acc) 94 | end. 95 | 96 | next_free(Taken) -> 97 | B = random:uniform(256) -1, 98 | C = random:uniform(256) -1, 99 | D = random:uniform(256) -1, 100 | IpAddr = {10,B,C,D}, 101 | case lists:member(IpAddr, Taken) of 102 | true -> next_free(Taken); 103 | false -> {ok,IpAddr,[IpAddr|Taken]} end. 104 | 105 | encode_name(Prefix, {10,B,C,D}) -> 106 | list_to_binary(io_lib:format("~s_~w_~w_~w", [Prefix,B,C,D])). 107 | 108 | %%EOF 109 | -------------------------------------------------------------------------------- /src/notifier_handler.erl: -------------------------------------------------------------------------------- 1 | -module(notifier_handler). 2 | -export([init/3]). 3 | -export([handle/2,terminate/3]). 4 | 5 | init({tcp,http}, Req, []) -> 6 | {ok,Req,[]}. 7 | 8 | handle(Req, St) -> 9 | {<<"POST">>,_} = cowboy_req:method(Req), 10 | {[DomName],_} = cowboy_req:path_info(Req), 11 | {{IpAddr,_Port},_} = cowboy_req:peer(Req), 12 | gen_server:cast(nominator, {ready,DomName,IpAddr}), 13 | {ok,Req,St}. 14 | 15 | terminate(_What, _Req, _St) -> 16 | ok. 17 | 18 | %%EOF 19 | -------------------------------------------------------------------------------- /src/redirect_handler.erl: -------------------------------------------------------------------------------- 1 | -module(redirect_handler). 2 | -export([init/3]). 3 | -export([handle/2,terminate/3]). 4 | 5 | -define(ZERGLING_IMAGE, <<"/home/mk/zergling/vmling">>). 6 | -define(BRIDGE, <<"xenbr0">>). 7 | -define(BASIC_EXTRA, 8 | "-notify 10.0.0.1:8909 -shutdown_after 15000 " 9 | " -home /zergling " 10 | "-pz /zergling/ebin " 11 | "-pz /zergling/deps/cowboy/ebin " 12 | "-pz /zergling/deps/cowlib/ebin " 13 | "-pz /zergling/deps/ranch/ebin " 14 | "-pz /zergling/deps/erlydtl/ebin " 15 | "-s zergling_app"). 16 | 17 | init({tcp,http}, Req, []) -> 18 | {ok,Req,[]}. 19 | 20 | handle(Req, St) -> 21 | TsReqReceived = timestamp(), 22 | {ok,DomName,{A0,B0,C0,D0}} = nominator:fresh(), 23 | {RealIp,_} = cowboy_req:header(<<"x-real-ip">>, Req, undefined), 24 | io:format("demo:~s:~s:", [real_host_name(RealIp),DomName]), 25 | 26 | Extra = io_lib:format("-ipaddr ~w.~w.~w.~w " 27 | "-netmask 255.0.0.0 " 28 | "-gateway 10.0.0.1 " 29 | "~s -ts_req_received ~w", [A0,B0,C0,D0,?BASIC_EXTRA,TsReqReceived]), 30 | DomOpts = [{extra,list_to_binary(Extra)}, 31 | {memory,32}, 32 | {bridge,?BRIDGE}], 33 | case egator:create(DomName, ?ZERGLING_IMAGE, DomOpts, []) of 34 | ok -> 35 | {ok,{A,B,C,D}} = nominator:wait_until_ready(DomName), 36 | io:format("~w.~w.~w.~w\n", [A,B,C,D]), 37 | {Path,_} = cowboy_req:path(Req), 38 | RedirectTo = io_lib:format("/internal_redirect/~w.~w.~w.~w/8000~s", 39 | [A,B,C,D,Path]), 40 | Headers = [{<<"X-Accel-Redirect">>,list_to_binary(RedirectTo)}], 41 | {ok,Reply} = cowboy_req:reply(200, Headers, Req), 42 | {ok,Reply,St}; 43 | 44 | {error,Error} -> 45 | {ok,Body} = error_page(Error), 46 | {ok,Reply} = cowboy_req:reply(200, [], Body, Req), 47 | {ok,Reply,St} 48 | end. 49 | 50 | terminate(_What, _Req, _St) -> 51 | ok. 52 | 53 | %%------------------------------------------------------------------------------ 54 | 55 | real_host_name(undefined) -> <<"not-set">>; 56 | real_host_name(IpAddr) when is_binary(IpAddr) -> 57 | case inet:gethostbyaddr(binary_to_list(IpAddr)) of 58 | {error,_} -> IpAddr; 59 | {ok,{hostent,undefined,_,_,_,_}} -> IpAddr; 60 | {ok,{hostent,HostName,_,_,_,_}} -> list_to_binary(HostName) end. 61 | 62 | error_page(Error) -> 63 | Vars = [{error,io_lib:format("~p~n", [Error])}], 64 | error_dtl:render(Vars). 65 | 66 | timestamp() -> 67 | {Mega,Secs,Micro} = now(), 68 | Mega *1000000.0 + Secs + Micro / 1000000.0. 69 | 70 | %%EOF 71 | -------------------------------------------------------------------------------- /src/spawnpool.app.src: -------------------------------------------------------------------------------- 1 | {application, spawnpool, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { spawnpool_app, []}}, 11 | {env, []} 12 | ]}. 13 | -------------------------------------------------------------------------------- /src/spawnpool_app.erl: -------------------------------------------------------------------------------- 1 | -module(spawnpool_app). 2 | 3 | -behaviour(application). 4 | 5 | -export([start/0]). 6 | 7 | %% Application callbacks 8 | -export([start/2, stop/1]). 9 | 10 | start() -> 11 | application:start(crypto), 12 | application:start(ranch), 13 | application:start(cowlib), 14 | application:start(cowboy), 15 | application:start(spawnpool). 16 | 17 | %% =================================================================== 18 | %% Application callbacks 19 | %% =================================================================== 20 | 21 | start(_StartType, _StartArgs) -> 22 | 23 | Dispatch = cowboy_router:compile([ 24 | {'_',[ 25 | {'_',redirect_handler,[]} 26 | ]} 27 | ]), 28 | 29 | {ok,_} = cowboy:start_http(spawner, 100, 30 | [{port,8900},{ip,{127,0,0,1}}], 31 | [{env,[{dispatch,Dispatch}]}]), 32 | 33 | %% a listener to collect 'ready' messages from zerglings 34 | 35 | Notify = cowboy_router:compile([ 36 | {'_',[ 37 | {"/ready/[...]",notifier_handler,[]} 38 | ]} 39 | ]), 40 | 41 | {ok,_} = cowboy:start_http(notifier, 100, 42 | [{port,8909},{ip,{10,0,0,1}}], 43 | [{env,[{dispatch,Notify}]}]), 44 | 45 | spawnpool_sup:start_link(). 46 | 47 | stop(_State) -> 48 | ok. 49 | 50 | %%EOF 51 | -------------------------------------------------------------------------------- /src/spawnpool_sup.erl: -------------------------------------------------------------------------------- 1 | 2 | -module(spawnpool_sup). 3 | 4 | -behaviour(supervisor). 5 | 6 | %% API 7 | -export([start_link/0]). 8 | 9 | %% Supervisor callbacks 10 | -export([init/1]). 11 | 12 | %% Helper macro for declaring children of supervisor 13 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 14 | 15 | %% =================================================================== 16 | %% API functions 17 | %% =================================================================== 18 | 19 | start_link() -> 20 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 21 | 22 | %% =================================================================== 23 | %% Supervisor callbacks 24 | %% =================================================================== 25 | 26 | init([]) -> 27 | Nominator = ?CHILD(nominator, worker), 28 | {ok, { {one_for_one, 5, 10}, [Nominator]} }. 29 | 30 | %%EOF 31 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | erl -pz ebin -pz deps/*/ebin -s spawnpool_app 4 | 5 | -------------------------------------------------------------------------------- /templates/error.dtl: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |The demo is limited to 16 concurrent instances and 2 libvirt connections. Due 7 | to these limitations we were unable to spawn a new instance to service your 8 | request. Please try again later.
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/zergling_config.dtl: -------------------------------------------------------------------------------- 1 |