%% proxy behaviour with round robin backend choice %% and backends failover tracking -module(gproxy3). -export([start_be/1,start_gw/1,stop/1,rpc/2, change_code/2]). %% backends management -export([rrobin/1, change_elect/2, add_be/2, del_be/2, list_be/1]). %% using functions, defined in reverl_util.erl -define(UTILMOD, reverl_util). -define(INFO(Format, Args), ?UTILMOD:info(?MODULE, ?LINE, self(), Format, Args)). -define(WARN(Format, Args), ?UTILMOD:warn(?MODULE, ?LINE, self(), Format, Args)). -define(ERR(Format, Args), ?UTILMOD:error(?MODULE, ?LINE, self(), Format, Args)). -define(IS_ALIVE(Pid), ?UTILMOD:is_pid_alive(Pid)). %% simple round robin rotation rrobin(Pool) -> if Pool /= [] -> [First|TheRest] = Pool, case ?IS_ALIVE(First) of true -> {First,lists:flatten(TheRest,[First])}; false -> ?WARN("backend ~p is down~n",[First]), rrobin(TheRest) end; true -> {empty,[]} end. %% start the server (backend) start_be(Fun) -> spawn(fun() -> loop(Fun) end). %% start the gateway (frontend) %% [Pool] - a list of possible backends start_gw(Pool) when not is_list(Pool) -> start_gw([Pool]); start_gw(Pool) -> spawn(fun() -> gloop(Pool,fun(X)->gproxy3:rrobin(X) end) end). %% stop gateway or backend stop(Pid) -> Pid ! stop. %% Pid can be gateway or server rpc(Pid, Query) -> Pid ! {self(), Query}, receive {Pid, crash} -> exit(rpc); {Pid, mustwait} -> ?WARN("~p not ready. please wait...~n",[Pid]), exit(rpc); {Pid, Why, mustwait} -> ?WARN("~p not ready, reason: ~p~n",[Pid,Why]), exit(rpc); {Pid, Reply} -> Reply end. %% function code change %% send to the SERVER (backend) change_code(Server, Fun) -> Server ! {swap_code,Fun}. change_elect(Gw, Elect) -> Gw ! {swap_elect,Elect}. add_be(Gw, Server) -> Gw ! {be_add, Server}. del_be(Gw, Server) -> Gw ! {be_del, Server}. list_be(Gw) -> Gw ! {self(), list_be}, receive {ok,Reply} -> Reply end. %% server loop loop(Fun) -> receive stop -> void; %% replace the service code {swap_code,Fun1} -> loop(Fun1); %% direct request {Client, Data} -> case catch Fun(Data) of {'EXIT', Why} -> ?ERR("Server: ~p, Query: ~p, Reason: ~p~n", [Client, Data, Why]), Client ! {self(), crash}, loop(Fun); Reply -> Client ! {self(), Reply}, loop(Fun) end; %% request via gateway {Gw, Client, Data} -> case catch Fun(Data) of {'EXIT', Why} -> ?ERR("Server: ~p, Query: ~p, Reason: ~p~n", [Client, Data, Why]), Gw ! {Client, crash}, loop(Fun); Reply -> Gw ! {Client, ok, Reply}, loop(Fun) end end. %% gateway loop gloop(Pool,Elect) -> receive stop -> void; %% replace BE election function {swap_elect,Elect1} -> gloop(Pool, Elect1); %% add new backend {be_add,Server} -> gloop([Server|Pool], Elect); %% remove some backend {be_del,Server} -> case lists:member(Server, Pool) of true -> gloop(lists:delete(Server, Pool),Elect); false -> gloop(Pool, Elect) end; %% list all backends {Client,list_be} -> Client ! {ok, Pool}, gloop(Pool, Elect); %% resend error from the backend to the client %% hide the server Pid, return Gw Pid {Client, crash} -> Client ! {self(), crash}, gloop(Pool, Elect); %% send the data to the elected backend % check for empty BE pool % the election function must check the backends %% for availability {Client, Data} -> %% not empty pool if Pool /= [] -> case catch Elect(Pool) of {empty,[]} -> Client ! {self(), mustwait}, gloop(Pool, Elect); {'EXIT', Why} -> Client ! {self(), Why, mustwait}, gloop(Pool, Elect); {Backend,Pool1} -> Backend ! {self(), Client, Data}, gloop(Pool1,Elect) end; %% empty pool true -> Client ! {self(), mustwait}, gloop(Pool, Elect) end; %% resend the response to the client %% replace the backend Pid with the Gw one {Client, ok, Data} -> Client ! {self(), Data}, gloop(Pool, Elect) end.