diff --git a/ecallmgr/src/ecallmgr.erl b/ecallmgr/src/ecallmgr.erl index 83c661e8a78..7d4afdf6fa8 100644 --- a/ecallmgr/src/ecallmgr.erl +++ b/ecallmgr/src/ecallmgr.erl @@ -47,5 +47,5 @@ stop() -> -spec start_deps/0 :: () -> 'ok'. start_deps() -> lager:start(), - _ = [wh_util:ensure_started(App) || App <- [sasl, crypto, gproc, whistle_amqp, ibrowse]], + _ = [wh_util:ensure_started(App) || App <- [sasl, crypto, whistle_amqp, gproc, ibrowse]], ok. diff --git a/ecallmgr/src/ecallmgr.hrl b/ecallmgr/src/ecallmgr.hrl index 04e2a947254..8307aa1ee64 100644 --- a/ecallmgr/src/ecallmgr.hrl +++ b/ecallmgr/src/ecallmgr.hrl @@ -6,7 +6,7 @@ -include_lib("whistle/include/freeswitch_xml.hrl"). -include_lib("whistle/src/wh_api.hrl"). --define(AMQP_POOL_MGR, ecallmgr_amqp_pool). +-define(ECALLMGR_AMQP_POOL, ecallmgr_amqp_pool). -type fs_api_ret() :: {'ok', binary()} | {'error', binary()} | 'timeout'. -type fs_sendmsg_ret() :: 'ok' | {'error', binary()} | 'timeout'. diff --git a/ecallmgr/src/ecallmgr_amqp_pool.erl b/ecallmgr/src/ecallmgr_amqp_pool.erl deleted file mode 100644 index 60ccff63b92..00000000000 --- a/ecallmgr/src/ecallmgr_amqp_pool.erl +++ /dev/null @@ -1,126 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @copyright (C) 2012, VoIP INC -%%% @doc -%%% Dispatches AMQP workers on behalf of client requests -%%% @end -%%% @contributors -%%% James Aimonetti -%%%------------------------------------------------------------------- --module(ecallmgr_amqp_pool). - --export([authn_req/1, authn_req/2 - ,authz_req/1, authz_req/2 - ,route_req/1, route_req/2 - ,reg_query/1, reg_query/2 - ,media_req/1, media_req/2 - ,get_req/1, get_req/2 - ,set_req/1, set_req/2 - ,originate_ready/2, originate_ready/3 - ]). - --export([true/1]). - --include("ecallmgr.hrl"). - --define(DEFAULT_TIMEOUT, 1500). - --spec true/1 :: (any()) -> 'true'. -true(_) -> true. - --spec authn_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec authn_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -authn_req(AuthnReq) -> - authn_req(AuthnReq, ?DEFAULT_TIMEOUT). -authn_req(AuthnReq, Timeout) -> - send_request(AuthnReq, Timeout, fun wapi_authn:publish_req/1, fun wapi_authn:resp_v/1). - --spec authz_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec authz_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -authz_req(AuthzReq) -> - authz_req(AuthzReq, ?DEFAULT_TIMEOUT). -authz_req(AuthzReq, Timeout) -> - send_request(AuthzReq, Timeout, fun wapi_authz:publish_req/1, fun wapi_authz:is_authorized/1). - --spec route_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec route_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -route_req(RouteReq) -> - route_req(RouteReq, ?DEFAULT_TIMEOUT). -route_req(RouteReq, Timeout) -> - send_request(RouteReq, Timeout, fun wapi_route:publish_req/1, fun wapi_route:is_actionable_resp/1). - --spec reg_query/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec reg_query/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -reg_query(RegReq) -> - reg_query(RegReq, ?DEFAULT_TIMEOUT). -reg_query(RegReq, Timeout) -> - send_request(RegReq, Timeout, fun wapi_registration:publish_query_req/1, fun wapi_registration:query_resp_v/1). - --spec media_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec media_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -media_req(MediaReq) -> - media_req(MediaReq, ?DEFAULT_TIMEOUT). -media_req(MediaReq, Timeout) -> - send_request(MediaReq, Timeout, fun wapi_media:publish_req/1, fun wapi_media:resp_v/1). - --spec get_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec get_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -get_req(GetReq) -> - get_req(GetReq, ?DEFAULT_TIMEOUT). -get_req(GetReq, Timeout) -> - send_request(GetReq, Timeout, fun wapi_sysconf:publish_get_req/1, fun ecallmgr_amqp_pool:true/1). - --spec set_req/1 :: (api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec set_req/2 :: (api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -set_req(GetReq) -> - set_req(GetReq, ?DEFAULT_TIMEOUT). -set_req(GetReq, Timeout) -> - send_request(GetReq, Timeout, fun wapi_sysconf:publish_set_req/1, fun ecallmgr_amqp_pool:true/1). - --spec originate_ready/2 :: (ne_binary(), api_terms()) -> {'ok', wh_json:json_object()} | - {'error', _}. --spec originate_ready/3 :: (ne_binary(), api_terms(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -originate_ready(ServerId, Req) -> - originate_ready(ServerId, Req, ?DEFAULT_TIMEOUT). -originate_ready(ServerId, Req, Timeout) -> - send_request(Req, Timeout - ,fun(Api) -> wapi_dialplan:publish_originate_ready(ServerId, Api) end - ,fun wapi_dialplan:originate_execute_v/1 - ). - -%%-------------------------------------------------------------------- -%% @doc -%% -%% @end -%%-------------------------------------------------------------------- --spec send_request/4 :: (api_terms(), pos_integer(), wh_amqp_worker:publish_fun(), wh_amqp_worker:validate_fun()) -> {'ok', wh_json:json_object()} | - {'error', _}. -send_request(Req, Timeout, PubFun, VFun) -> - case poolboy:checkout(?AMQP_POOL_MGR, false, ?DEFAULT_TIMEOUT) of - W when is_pid(W) -> - Prop = case wh_json:is_json_object(Req) of - true -> wh_json:to_proplist(Req); - false -> Req - end, - - Reply = wh_amqp_worker:new_request(W, Prop, PubFun, VFun, Timeout), - poolboy:checkin(?AMQP_POOL_MGR, W), - Reply; - full -> - lager:debug("failed to checkout worker: full"), - {error, pool_full} - end. diff --git a/ecallmgr/src/ecallmgr_authz.erl b/ecallmgr/src/ecallmgr_authz.erl index 020bb25ef50..680d44190ba 100644 --- a/ecallmgr/src/ecallmgr_authz.erl +++ b/ecallmgr/src/ecallmgr_authz.erl @@ -62,23 +62,18 @@ authz_win(Pid) when is_pid(Pid) -> init_authorize(Parent, FSID, CallID, FSData) -> proc_lib:init_ack(Parent, {ok, self()}), put(callid, CallID), - lager:debug("authorize started"), - - ReqProp = request(FSID, CallID, FSData), - - JObj = try - {ok, RespJObj} = ecallmgr_amqp_pool:authz_req(ReqProp, 2000), - true = wapi_authz:resp_v(RespJObj), - RespJObj - catch - _T:_R -> - lager:debug("authz request un-answered or improper"), - lager:debug("authz ~p: ~p", [_T, _R]), - default() - end, - - authorize_loop(JObj). + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,request(FSID, CallID, FSData) + ,fun wapi_authz:publish_req/1 + ,fun wapi_authz:is_authorized/1), + case ReqResp of + {error, _R} -> + lager:debug("authz request lookup failed: ~p", [_R]), + default(); + {ok, RespJObj} -> + authorize_loop(RespJObj) + end. -spec authorize_loop/1 :: (wh_json:json_object()) -> no_return(). authorize_loop(JObj) -> diff --git a/ecallmgr/src/ecallmgr_call_events.erl b/ecallmgr/src/ecallmgr_call_events.erl index 1994a8c9e61..9f680b254fb 100644 --- a/ecallmgr/src/ecallmgr_call_events.erl +++ b/ecallmgr/src/ecallmgr_call_events.erl @@ -356,7 +356,7 @@ publish_event(Props) -> ApplicationData = props:get_value(<<"Raw-Application-Data">>, Props, <<>>), lager:debug("publishing call event ~s '~s(~s)'", [EventName, ApplicationName, ApplicationData]) end, - wapi_call:publish_event(CallId, Props). + wh_amqp_worker:cast(?ECALLMGR_AMQP_POOL, Props, fun(P) -> wapi_call:publish_event(CallId, P) end). -spec is_masquerade/1 :: (proplist()) -> boolean(). is_masquerade(Props) -> diff --git a/ecallmgr/src/ecallmgr_config.erl b/ecallmgr/src/ecallmgr_config.erl index e15ce418785..dc6f9b00d70 100644 --- a/ecallmgr/src/ecallmgr_config.erl +++ b/ecallmgr/src/ecallmgr_config.erl @@ -48,10 +48,15 @@ get(Key0, Default, Node0) -> V =/= undefined], lager:debug("looking up ~s from sysconf", [Key]), - - case ecallmgr_amqp_pool:get_req(Req) of + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,Req + ,fun wapi_sysconf:publish_get_req/1 + ,fun wapi_sysconf:get_resp_v/1), + case ReqResp of + {error, _R} -> + lager:debug("unable to get config for key '~s' failed: ~p", [Key0, _R]), + Default; {ok, RespJObj} -> - true = wapi_sysconf:get_resp_v(RespJObj), V = case wh_json:get_value(<<"Value">>, RespJObj) of undefined -> Default; null -> Default; @@ -61,8 +66,7 @@ get(Key0, Default, Node0) -> wh_cache:store_local(Cache, cache_key(Key, Node), Value), Value end, - V; - {error, timeout} -> Default + V end end. @@ -73,10 +77,8 @@ set(Key, Value) -> set(Key0, Value, Node0) -> Key = wh_util:to_binary(Key0), Node = wh_util:to_binary(Node0), - {ok, Cache} = ecallmgr_util_sup:cache_proc(), wh_cache:store_local(Cache, cache_key(Key, Node), Value), - Req = [KV || {_, V} = KV <- [{<<"Category">>, <<"ecallmgr">>} ,{<<"Key">>, Key} @@ -85,12 +87,16 @@ set(Key0, Value, Node0) -> | wh_api:default_headers(?APP_NAME, ?APP_VERSION) ], V =/= undefined], - case catch ecallmgr_amqp_pool:set_req(Req) of - {'EXIT', _} -> - lager:debug("failed to recv resp for setting ~s to ~p", [Key, Value]); - _ -> - lager:debug("recv resp for setting ~s to ~p", [Key, Value]) + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,Req + ,fun wapi_sysconf:publish_set_req/1 + ,fun wh_amqp_worker:any_resp/1), + case ReqResp of + {error, _R} -> + lager:debug("set config for key '~s' failed: ~p", [Key0, _R]); + {ok, _} -> + lager:debug("set config for key '~s' to new value: ~p", [Key0, Value]) end. - + cache_key(K, Node) -> {?MODULE, K, Node}. diff --git a/ecallmgr/src/ecallmgr_fs_auth.erl b/ecallmgr/src/ecallmgr_fs_auth.erl index 72d5dca53cf..a7f5d918c64 100644 --- a/ecallmgr/src/ecallmgr_fs_auth.erl +++ b/ecallmgr/src/ecallmgr_fs_auth.erl @@ -128,7 +128,7 @@ handle_info({fetch, directory, <<"domain">>, <<"name">>, _Value, ID, [undefined case {props:get_value(<<"Event-Name">>, Data), props:get_value(<<"action">>, Data)} of {<<"REQUEST_PARAMS">>, <<"sip_auth">>} -> %% TODO: move this to a supervisor somewhere.... - lookup_user(Node, ID, Data), + proc_lib:spawn(?MODULE, lookup_user, [Node, ID, Data]), {noreply, State, hibernate}; _Other -> _ = freeswitch:fetch_reply(Node, ID, ?EMPTYRESPONSE), @@ -176,33 +176,31 @@ code_change(_OldVsn, State, _Extra) -> lookup_user(Node, ID, Data) -> spawn(fun() -> put(callid, ID), - %% build req for rabbit AuthRealm = props:get_value(<<"domain">>, Data, props:get_value(<<"Auth-Realm">>, Data)), AuthUser = props:get_value(<<"user">>, Data, props:get_value(<<"Auth-User">>, Data)), - - AuthReq = [{<<"Msg-ID">>, ID} - ,{<<"To">>, ecallmgr_util:get_sip_to(Data)} - ,{<<"From">>, ecallmgr_util:get_sip_from(Data)} - ,{<<"Orig-IP">>, ecallmgr_util:get_orig_ip(Data)} - ,{<<"Method">>, props:get_value(<<"sip_auth_method">>, Data)} - ,{<<"Auth-User">>, AuthUser} - ,{<<"Auth-Realm">>, AuthRealm} - | wh_api:default_headers(?APP_NAME, ?APP_VERSION) - ], - - lager:debug("looking up credentials of ~s@~s for a ~s", [AuthUser, AuthRealm, props:get_value(<<"Method">>, AuthReq)]), - - case ecallmgr_amqp_pool:authn_req(AuthReq) of + Method = props:get_value(<<"sip_auth_method">>, Data), + lager:debug("looking up credentials of ~s@~s for a ~s", [AuthUser, AuthRealm, Method]), + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,[{<<"Msg-ID">>, ID} + ,{<<"To">>, ecallmgr_util:get_sip_to(Data)} + ,{<<"From">>, ecallmgr_util:get_sip_from(Data)} + ,{<<"Orig-IP">>, ecallmgr_util:get_orig_ip(Data)} + ,{<<"Method">>, Method} + ,{<<"Auth-User">>, AuthUser} + ,{<<"Auth-Realm">>, AuthRealm} + | wh_api:default_headers(?APP_NAME, ?APP_VERSION) + ] + ,fun wapi_authn:publish_req/1 + ,fun wapi_authn:resp_v/1), + case ReqResp of {error, _R} -> lager:debug("authn request lookup failed: ~p", [_R]), freeswitch:fetch_reply(Node, ID, ?ROUTE_NOT_FOUND_RESPONSE); - {ok, AuthResp} -> - true = wapi_authn:resp_v(AuthResp), - lager:debug("received authn_resp"), + {ok, RespJObj} -> {ok, Xml} = ecallmgr_fs_xml:authn_resp_xml( wh_json:set_value(<<"Auth-Realm">>, AuthRealm - ,wh_json:set_value(<<"Auth-User">>, AuthUser, AuthResp)) + ,wh_json:set_value(<<"Auth-User">>, AuthUser, RespJObj)) ), lager:debug("sending XML to ~w: ~s", [Node, Xml]), freeswitch:fetch_reply(Node, ID, Xml) diff --git a/ecallmgr/src/ecallmgr_fs_node.erl b/ecallmgr/src/ecallmgr_fs_node.erl index 783de64488b..9fe17f905d0 100644 --- a/ecallmgr/src/ecallmgr_fs_node.erl +++ b/ecallmgr/src/ecallmgr_fs_node.erl @@ -168,7 +168,6 @@ init([Node, Options]) -> %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- --spec handle_call/3 :: ('node', {pid(), reference()}, #state{}) -> {'reply', atom(), #state{}}. handle_call(node, _From, #state{node=Node}=State) -> {reply, Node, State}. diff --git a/ecallmgr/src/ecallmgr_fs_route.erl b/ecallmgr/src/ecallmgr_fs_route.erl index 070bd285867..4d0077756e3 100644 --- a/ecallmgr/src/ecallmgr_fs_route.erl +++ b/ecallmgr/src/ecallmgr_fs_route.erl @@ -194,12 +194,15 @@ authorize_and_route(Node, FSID, CallID, FSData, DefProp) -> -spec route/5 :: (atom(), ne_binary(), ne_binary(), proplist(), pid() | 'undefined') -> 'ok'. route(Node, FSID, CallID, DefProp, AuthZPid) -> lager:debug("starting route request from node ~s", [Node]), - case ecallmgr_amqp_pool:route_req(DefProp) of + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,DefProp + ,fun wapi_route:publish_req/1 + ,fun wapi_route:is_actionable_resp/1), + case ReqResp of + {error, _R} -> lager:debug("did not receive route response: ~p", [_R]); {ok, RespJObj} -> RouteCCV = wh_json:get_value(<<"Custom-Channel-Vars">>, RespJObj, wh_json:new()), - authorize(Node, FSID, CallID, RespJObj, AuthZPid, RouteCCV); - _Else -> - lager:debug("did not receive route response: ~p", [_Else]) + authorize(Node, FSID, CallID, RespJObj, AuthZPid, RouteCCV) end. -spec authorize/6 :: (atom(), ne_binary(), ne_binary(), wh_json:json_object(), pid() | 'undefined', wh_json:json_object()) -> 'ok'. diff --git a/ecallmgr/src/ecallmgr_media_registry.erl b/ecallmgr/src/ecallmgr_media_registry.erl index c0dd0477c4c..5a8711be9e1 100644 --- a/ecallmgr/src/ecallmgr_media_registry.erl +++ b/ecallmgr/src/ecallmgr_media_registry.erl @@ -255,15 +255,17 @@ lookup_remote(MediaName, new, CallID) -> -spec lookup_remote/2 :: (ne_binary(), proplist()) -> {'ok', ne_binary()} | {'error', 'not_local'}. lookup_remote(MediaName, Request) -> - try - {ok, MediaResp} = ecallmgr_amqp_pool:media_req(Request, 2000), - true = wapi_media:resp_v(MediaResp), - MediaName = wh_json:get_value(<<"Media-Name">>, MediaResp), - - {ok, wh_json:get_value(<<"Stream-URL">>, MediaResp, <<>>)} - catch - _:B -> - {error, B} + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,Request + ,fun wapi_media:publish_req/1 + ,fun wapi_media:resp_v/1), + case ReqResp of + {error, _R}=E -> + lager:debug("media lookup for '~s' failed: ~p", [MediaName, _R]), + E; + {ok, MediaResp} -> + MediaName = wh_json:get_value(<<"Media-Name">>, MediaResp), + {ok, wh_json:get_value(<<"Stream-URL">>, MediaResp, <<>>)} end. wait_for_fs_media({Path,_}, RecvSrv, FromPid) -> diff --git a/ecallmgr/src/ecallmgr_registrar.erl b/ecallmgr/src/ecallmgr_registrar.erl index acdbad52369..78e3ceefa69 100644 --- a/ecallmgr/src/ecallmgr_registrar.erl +++ b/ecallmgr/src/ecallmgr_registrar.erl @@ -121,7 +121,6 @@ handle_cast(_Msg, State) -> handle_info({cache_registrations, Realm, User, RegFields}, State) -> lager:debug("storing registration information for ~s@~s", [User, Realm]), {ok, Cache} = ecallmgr_util_sup:cache_proc(), - wh_cache:store_local(Cache, cache_key(Realm, User), RegFields ,wh_util:to_integer(props:get_value(<<"Expires">>, RegFields, 300)) %% 5 minute default ), @@ -180,37 +179,25 @@ lookup_reg(Realm, User, Fields) -> false -> Acc end end, - case wh_cache:fetch_local(Cache, cache_key(Realm, User)) of {error, not_found} -> lager:debug("valid cached registration not found, querying whapps"), - RegProp = [{<<"Username">>, User} - ,{<<"Realm">>, Realm} - ,{<<"Fields">>, []} - | wh_api:default_headers(?APP_NAME, ?APP_VERSION) ], - try - case ecallmgr_amqp_pool:reg_query(RegProp) of - {ok, RegJObj} -> - true = wapi_registration:query_resp_v(RegJObj), - - RegFields = wh_json:to_proplist(wh_json:get_value(<<"Fields">>, RegJObj, wh_json:new())), - - {ok, Srv} = ecallmgr_util_sup:registrar_proc(), - Srv ! {cache_registrations, Realm, User, RegFields}, - - lager:debug("received registration information"), - lists:foldr(FilterFun, [], RegFields); - {error, timeout}=E -> - lager:debug("Looking up registration timed out"), - E - end - catch - _:{timeout, _} -> - lager:debug("looking up registration threw exception: timeout", []), - {error, timeout}; - _:R -> - lager:debug("looking up registration threw exception: ~p", [R]), - {error, timeout} + ReqResp = wh_amqp_worker:call(?ECALLMGR_AMQP_POOL + ,[{<<"Username">>, User} + ,{<<"Realm">>, Realm} + ,{<<"Fields">>, []} + | wh_api:default_headers(?APP_NAME, ?APP_VERSION) + ] + ,fun wapi_registration:publish_query_req/1 + ,fun wapi_registration:query_resp_v/1), + case ReqResp of + {error, _R} -> lager:debug("did not receive registrar response: ~p", [_R]); + {ok, RespJObj} -> + RegFields = wh_json:to_proplist(wh_json:get_value(<<"Fields">>, RespJObj, wh_json:new())), + {ok, Srv} = ecallmgr_util_sup:registrar_proc(), + Srv ! {cache_registrations, Realm, User, RegFields}, + lager:debug("received registration information"), + lists:foldr(FilterFun, [], RegFields) end; {ok, RegFields} -> lager:debug("found cached registration information"), diff --git a/ecallmgr/src/ecallmgr_sup.erl b/ecallmgr/src/ecallmgr_sup.erl index 75bb6f24bc7..8ffd637b052 100644 --- a/ecallmgr/src/ecallmgr_sup.erl +++ b/ecallmgr/src/ecallmgr_sup.erl @@ -23,7 +23,7 @@ ,permanent, 5000, worker, [poolboy] }; (N, T) -> {N, {N, start_link, []}, permanent, 5000, T, [N]} end(Name, Type)). --define(CHILDREN, [{?AMQP_POOL_MGR, pool} +-define(CHILDREN, [{?ECALLMGR_AMQP_POOL, pool} ,{ecallmgr_util_sup, supervisor} ,{ecallmgr_call_sup, supervisor} ,{ecallmgr_fs_sup, supervisor} diff --git a/lib/whistle-1.0.0/include/wh_types.hrl b/lib/whistle-1.0.0/include/wh_types.hrl index 1ebb4b7ef0a..845a0c8552b 100644 --- a/lib/whistle-1.0.0/include/wh_types.hrl +++ b/lib/whistle-1.0.0/include/wh_types.hrl @@ -83,5 +83,7 @@ -type handle_info_ret() :: {'noreply', term()} | {'noreply', term(), gen_server_timeout()} | {'stop', term(), term()}. +-type server_ref() :: atom() | {atom(), atom()} | {global, term()} | {via, atom(), term()} | pid(). + -define(WHISTLE_TYPES_INCLUDED, true). -endif. diff --git a/lib/whistle-1.0.0/src/api/wapi_dialplan.erl b/lib/whistle-1.0.0/src/api/wapi_dialplan.erl index df62763fe84..d175c1c29ab 100644 --- a/lib/whistle-1.0.0/src/api/wapi_dialplan.erl +++ b/lib/whistle-1.0.0/src/api/wapi_dialplan.erl @@ -791,6 +791,7 @@ publish_originate_execute(ServerId, API, ContentType) -> amqp_util:targeted_publish(ServerId, Payload, ContentType). bind_q(Queue, _Prop) -> + _ = amqp_util:callctl_exchange(), _ = amqp_util:bind_q_to_callctl(Queue), 'ok'. diff --git a/lib/whistle-1.0.0/src/gen_listener.erl b/lib/whistle-1.0.0/src/gen_listener.erl index 93e2f6855b8..365f75c3bbb 100644 --- a/lib/whistle-1.0.0/src/gen_listener.erl +++ b/lib/whistle-1.0.0/src/gen_listener.erl @@ -100,6 +100,9 @@ behaviour_info(_) -> -define(TIMEOUT_RETRY_CONN, 1000). -define(CALLBACK_TIMEOUT_MSG, callback_timeout). +-define(START_TIMEOUT, 500). +-define(MAX_TIMEOUT, 5000). + %% API functions for requesting data from the gen_listener -spec queue_name/1 :: (pid()) -> ne_binary(). queue_name(Srv) -> @@ -198,9 +201,7 @@ rm_binding(Srv, Binding, Props) -> init([Module, Params, InitArgs]) -> process_flag(trap_exit, true), put(callid, ?LOG_SYSTEM_ID), - lager:debug("starting new gen_listener proc: ~s", [wh_util:to_binary(Module)]), - {ModState, TimeoutRef} = case erlang:function_exported(Module, init, 1) andalso Module:init(InitArgs) of {ok, MS} -> {MS, undefined}; @@ -211,22 +212,24 @@ init([Module, Params, InitArgs]) -> Err -> throw(Err) end, - Responders = props:get_value(responders, Params, []), Bindings = props:get_value(bindings, Params, []), - - {ok, Q} = start_amqp(Params), - - _ = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), is_consuming), - - _ = [add_responder(self(), Mod, Evts) || {Mod, Evts} <- Responders], - - _ = [create_binding(wh_util:to_binary(Type), BindProps, Q) || {Type, BindProps} <- Bindings], - - {ok, #state{queue=Q, module=Module, module_state=ModState, module_timeout_ref=TimeoutRef - ,responders=[], bindings=Bindings - ,params=lists:keydelete(responders, 1, lists:keydelete(bindings, 1, Params))} - ,hibernate}. + case start_amqp(Params) of + {error, _R} -> + lager:alert("lost our channel, but its back up; rebinding"), + _Ref = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), {amqp_channel_event, initial_conn_failed}), + {ok, #state{module=Module, module_state=ModState, module_timeout_ref=TimeoutRef + ,responders=[], bindings=Bindings + ,params=lists:keydelete(responders, 1, lists:keydelete(bindings, 1, Params))}}; + {ok, Q} -> + _ = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), is_consuming), + _ = [add_responder(self(), Mod, Evts) || {Mod, Evts} <- Responders], + _ = [create_binding(wh_util:to_binary(Type), BindProps, Q) || {Type, BindProps} <- Bindings], + {ok, #state{queue=Q, module=Module, module_state=ModState, module_timeout_ref=TimeoutRef + ,responders=[], bindings=Bindings + ,params=lists:keydelete(responders, 1, lists:keydelete(bindings, 1, Params))} + ,hibernate} + end. -type gen_l_handle_call_ret() :: {'reply', term(), #state{}, gen_server_timeout()} | {'noreply', #state{}, gen_server_timeout()} | @@ -369,47 +372,40 @@ handle_info({'EXIT', Pid, _Reason}=Message, #state{active_responders=ARs}=State) false -> handle_callback_info(Message, State) end; -handle_info({amqp_host_down, _H}=Down, #state{bindings=Bindings, params=Params}=State) -> - timer:sleep(random:uniform(150)+100), % wait a bit before reconnecting, so we don't slam amqp_mgr - lager:alert("amqp host down msg: ~p", [_H]), - - case amqp_util:is_host_available() of - true -> - lager:debug("host is available, let's try wiring up"), - case start_amqp(Params) of - {ok, Q} -> - Self = self(), - _ = erlang:send_after(?TIMEOUT_RETRY_CONN, Self, is_consuming), - proc_lib:spawn(fun() -> [ add_binding(Self, Type, BindProps) || {Type, BindProps} <- Bindings ] end), - {noreply, State#state{queue=Q, is_consuming=false, bindings=[]}, hibernate}; - {error, _} -> - lager:debug("failed to start amqp, waiting another second"), - _ = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), Down), - {noreply, State#state{queue = <<>>, is_consuming=false}, hibernate} - end; - false -> - lager:debug("no AMQP host ready, waiting another second"), - erlang:send_after(?TIMEOUT_RETRY_CONN, self(), Down), +handle_info({amqp_channel_event, initial_conn_failed}, State) -> + lager:alert("failed to create initial connection to AMQP, waiting for connection"), + _Ref = erlang:send_after(?START_TIMEOUT, self(), {'$maybe_connect_amqp', ?START_TIMEOUT}), + {noreply, State#state{queue = <<>>, is_consuming=false}, hibernate}; +handle_info({amqp_channel_event, restarted}, #state{params=Params, bindings=Bindings}=State) -> + case start_amqp(Params) of + {ok, Q} -> + Self = self(), + lager:debug("lost our channel, but its back up; rebinding"), + _ = erlang:send_after(?TIMEOUT_RETRY_CONN, Self, is_consuming), + proc_lib:spawn(fun() -> [add_binding(Self, Type, BindProps) || {Type, BindProps} <- Bindings] end), + {noreply, State#state{queue=Q, is_consuming=false, bindings=[]}, hibernate}; + {error, _R} -> + lager:alert("failed to rebind after channel restart: ~p", [_R]), + _Ref = erlang:send_after(?START_TIMEOUT, self(), {'$maybe_connect_amqp', ?START_TIMEOUT}), {noreply, State#state{queue = <<>>, is_consuming=false}, hibernate} end; - -handle_info({amqp_lost_channel, no_connection}, State) -> - lager:alert("lost our channel, checking every second for a host to come back up"), - _Ref = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), {amqp_host_down, ok}), +handle_info({amqp_channel_event, _Reason}, State) -> + lager:alert("notified AMQP channel died: ~p", [_Reason]), + _Ref = erlang:send_after(?START_TIMEOUT, self(), {'$maybe_connect_amqp', ?START_TIMEOUT}), {noreply, State#state{queue = <<>>, is_consuming=false}, hibernate}; -handle_info({amqp_lost_channel, connection_restored}, #state{params=Params, bindings=Bindings}=State) -> - lager:alert("lost our channel, but its back up; rebinding"), - +handle_info({'$maybe_connect_amqp', Timeout}, #state{bindings=Bindings, params=Params}=State) -> case start_amqp(Params) of - {ok, Q} -> + {ok, Q} -> Self = self(), + lager:info("reconnected to AMQP channel, rebinding"), _ = erlang:send_after(?TIMEOUT_RETRY_CONN, Self, is_consuming), - proc_lib:spawn(fun() -> [ add_binding(Self, Type, BindProps) || {Type, BindProps} <- Bindings ] end), + proc_lib:spawn(fun() -> + [add_binding(Self, Type, BindProps) || {Type, BindProps} <- Bindings] + end), {noreply, State#state{queue=Q, is_consuming=false, bindings=[]}, hibernate}; {error, _} -> - lager:debug("failed to start amqp, waiting another second"), - _ = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), {amqp_host_down, ok}), + _Ref = erlang:send_after(Timeout, self(), {'$maybe_connect_amqp', next_timeout(Timeout)}), {noreply, State#state{queue = <<>>, is_consuming=false}, hibernate} end; @@ -419,7 +415,7 @@ handle_info(#'basic.consume_ok'{}, S) -> handle_info(is_consuming, #state{is_consuming=false, queue=Q}=State) -> lager:debug("huh, we're not consuming. Queue: ~p", [Q]), - _Ref = erlang:send_after(?TIMEOUT_RETRY_CONN, self(), {amqp_host_down, ok}), + _Ref = erlang:send_after(?START_TIMEOUT, self(), {'$maybe_connect_amqp', ?START_TIMEOUT}), {noreply, State}; handle_info(is_consuming, State) -> @@ -532,16 +528,19 @@ wait_for_handlers([]) -> ok. -spec start_amqp/1 :: (wh_proplist()) -> {'ok', binary()} | {'error', 'amqp_error'}. start_amqp(Props) -> - QueueProps = props:get_value(queue_options, Props, []), - QueueName = props:get_value(queue_name, Props, <<>>), - case catch amqp_util:new_queue(QueueName, QueueProps) of - {error, amqp_error}=E -> lager:debug("failed to start new queue"), E; - {'EXIT', _Why} -> lager:alert("exit: ~p", [_Why]), {error, amqp_error}; - Queue -> - ConsumeProps = props:get_value(consume_options, Props, []), - set_qos(props:get_value(basic_qos, Props)), - ok = amqp_util:basic_consume(Queue, ConsumeProps), - {ok, Queue} + case wh_amqp_mgr:is_available() of + false -> {error, amqp_down}; + true -> + QueueProps = props:get_value(queue_options, Props, []), + QueueName = props:get_value(queue_name, Props, <<>>), + case amqp_util:new_queue(QueueName, QueueProps) of + {error, _}=E -> E; + Q -> + ConsumeProps = props:get_value(consume_options, Props, []), + set_qos(props:get_value(basic_qos, Props)), + ok = amqp_util:basic_consume(Q, ConsumeProps), + {ok, Q} + end end. -spec set_qos/1 :: ('undefined' | non_neg_integer()) -> 'ok'. @@ -604,3 +603,13 @@ stop_timer(Ref) when is_reference(Ref) -> start_timer(Timeout) when is_integer(Timeout) andalso Timeout >= 0 -> erlang:send_after(Timeout, self(), ?CALLBACK_TIMEOUT_MSG); start_timer(_) -> 'undefined'. + +-spec next_timeout/1 :: (pos_integer()) -> ?START_TIMEOUT..?MAX_TIMEOUT. +next_timeout(?MAX_TIMEOUT=Timeout) -> + Timeout; +next_timeout(Timeout) when Timeout*2 > ?MAX_TIMEOUT -> + ?MAX_TIMEOUT; +next_timeout(Timeout) when Timeout < ?START_TIMEOUT -> + ?START_TIMEOUT; +next_timeout(Timeout) -> + Timeout * 2. diff --git a/lib/whistle-1.0.0/src/party_line.erl b/lib/whistle-1.0.0/src/party_line.erl deleted file mode 100644 index 2511e0c07c4..00000000000 --- a/lib/whistle-1.0.0/src/party_line.erl +++ /dev/null @@ -1,235 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @copyright (C) 2010-2012, VoIP INC -%%% @doc -%%% -%%% -%%% @end -%%% @contributors -%%% Karl Anderson -%%%----------------------------------------------------------------------------- --module(party_line). - --behaviour(gen_server). - --compile({no_auto_import, [register/2]}). - --define(SERVER, ?MODULE). - --export([start_link/0, start_link/1]). --export([register/1, register/2]). --export([unregister/0, unregister/1, unregister/2]). --export([send/2, send/3]). --export([list/1, list/2]). --export([init/1 - ,handle_call/3 - ,handle_cast/2 - ,handle_info/2 - ,terminate/2 - ,code_change/3 - ]). - -%%%=================================================================== -%%% API -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @doc -%% Starts the server -%% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} -%% @end -%%-------------------------------------------------------------------- --spec start_link/0 :: () -> {'ok', pid()}. --spec start_link/1 :: (atom()) -> {'ok', pid()}. - -start_link() -> - start_link(?SERVER). - -start_link(Name) -> - gen_server:start_link({local, Name}, ?MODULE, [Name], []). - --spec register/1 :: (term()) -> 'ok'. -register(Key) -> - register(?SERVER, Key). - --spec register/2 :: (atom(), term()) -> 'ok'. -register(Srv, Key) -> - case get(Srv) of - undefined -> put(Srv, [Key]); - Keys -> put(Srv, [Key|Keys]) - end, - gen_server:cast(Srv, {register, Key, self()}). - --spec unregister/0 :: () -> 'ok'. -unregister() -> - unregister(?SERVER, undefined). - --spec unregister/1 :: (term()) -> 'ok'. -unregister(Key) -> - unregister(?SERVER, Key). - --spec unregister/2 :: (atom(), term()) -> 'ok'. -unregister(Srv, Key) -> - case get(party_line) of - undefined -> ok; - Keys -> put(Srv, lists:delete(Key, Keys)) - end, - gen_server:cast(Srv, {unregister, Key, self()}). - --spec send/2 :: (term(), term()) -> 'ok'. -send(Key, Msg) -> - send(?SERVER, Key, Msg). - --spec send/3 :: (atom(), term(), term()) -> 'ok'. -send(Srv, Key, Msg) -> - Pids = list(Srv, Key), - [P ! Msg || P <- Pids, P =/= self()], - ok. - --spec list/1 :: (term()) -> [pid(),...] | []. -list(Key) -> - list(?SERVER, Key). - --spec list/2 :: (atom(), term()) -> [pid(),...] | []. -list(Srv, Key) -> - gen_server:call(Srv, {list, Key}). - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Initializes the server -%% -%% @spec init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% @end -%%-------------------------------------------------------------------- -init([_Name]) -> - %% TODO: search for a key in every process dict and init the registrar?? maybe... - {ok, {dict:new(), dict:new()}}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling call messages -%% -%% @spec handle_call(Request, From, State) -> -%% {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_call({list, Key}, _From, {Registrar, _}=State) -> - case dict:find(Key, Registrar) of - error -> - {reply, [], State}; - {ok, Pids} -> - {reply, Pids, State} - end; -handle_call(_Request, _From, State) -> - {reply, {error, not_implemented}, State}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling cast messages -%% -%% @spec handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_cast({register, Key, Pid}, {Registrar, Monitors}) -> - case dict:find(Pid, Monitors) of - error -> - MRef = erlang:monitor(process, Pid), - {noreply, {dict:append(Key, Pid, Registrar) - ,dict:store(Pid, MRef, Monitors)}}; - {ok, _} -> - {noreply, {dict:append(Key, Pid, Registrar), Monitors}} - end; -handle_cast({unregister, undefined, Pid}, {Registrar, Monitors}) -> - NewRegistrar = dict:map(fun(_, Pids) -> - [P || P <- Pids, P =/= Pid] - end, Registrar), - {noreply, {NewRegistrar, maybe_demonitor(Pid, Monitors)}}; -handle_cast({unregister, Key, Pid}, {Registrar, Monitors}) -> - case dict:find(Key, Registrar) of - error -> {noreply, {Registrar, Monitors}}; - {ok, Pids} -> - NewRegistrar = dict:store(Key, [P || P <- Pids, P =/= Pid], Registrar), - case lists:any(fun({_, Ps}) -> - [P || P <- Ps, P =:= Pid] =/= [] - end, dict:to_list(NewRegistrar)) of - false -> - %% the pid has removed its last key - {noreply, {NewRegistrar - ,maybe_demonitor(Pid, Monitors)}}; - true -> - %% the pid still has other keys - {noreply, {NewRegistrar, Monitors}} - end - end; -handle_cast(_Msg, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling all non call/cast messages -%% -%% @spec handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_info({'DOWN', _, _, Pid, _}, State) -> - gen_server:cast(self(), {unregister, undefined, Pid}), - {noreply, State}; -handle_info(_Info, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_server terminates -%% with Reason. The return value is ignored. -%% -%% @spec terminate(Reason, State) -> void() -%% @end -%%-------------------------------------------------------------------- -terminate(_Reason, _State) -> - lager:debug("party line terminating: ~p", [_Reason]). - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Convert process state when code is changed -%% -%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} -%% @end -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== --spec maybe_demonitor/2 :: (pid(), dict()) -> dict(). -maybe_demonitor(Pid, Monitors) -> - case dict:find(Pid, Monitors) of - error -> Monitors; - {ok, MRef} -> - erlang:demonitor(MRef, [flush]), - dict:erase(Pid, Monitors) - end. diff --git a/lib/whistle_amqp-1.0.0/src/amqp_host.erl b/lib/whistle_amqp-1.0.0/src/amqp_host.erl deleted file mode 100644 index b9a1e5c77dd..00000000000 --- a/lib/whistle_amqp-1.0.0/src/amqp_host.erl +++ /dev/null @@ -1,745 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @copyright (C) 2011-2012, VoIP INC -%%% @doc -%%% Handle a host's connection/channels -%%% @end -%%% @contributions -%%% James Aimonetti -%%% Karl Anderson -%%%------------------------------------------------------------------- --module(amqp_host). - --behaviour(gen_server). - -%% API --export([start_link/3, publish/4, consume/3, misc_req/3, stop/1]). --export([publish_channel/2, misc_channel/2, my_channel/2 - ,update_my_tag/3, fetch_my_tag/2 - ]). - --export([register_return_handler/2]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --include("amqp_util.hrl"). - --define(SERVER, ?MODULE). --define(KNOWN_EXCHANGES, [{?EXCHANGE_TARGETED, ?TYPE_TARGETED} - ,{?EXCHANGE_CALLCTL, ?TYPE_CALLCTL} - ,{?EXCHANGE_CALLEVT, ?TYPE_CALLEVT} - ,{?EXCHANGE_CALLMGR, ?TYPE_CALLMGR} - ,{?EXCHANGE_MONITOR, ?TYPE_MONITOR} - ,{?EXCHANGE_RESOURCE, ?TYPE_RESOURCE} - ,{?EXCHANGE_CONFIGURATION, ?TYPE_CONFIGURATION} - ,{?EXCHANGE_CONFERENCE, ?TYPE_CONFERENCE} - ,{?EXCHANGE_WHAPPS, ?TYPE_WHAPPS} - ,{?EXCHANGE_SYSCONF, ?TYPE_SYSCONF} - ]). - -%% Channel, ChannelRef -%% Channel, ChannelRef, Tag, FromRef --type channel_data() :: {pid(), reference()}. --type consumer_data() :: {pid(), reference(), binary(), reference()}. - --type consume_records() :: #'queue.declare'{} | #'queue.bind'{} | #'queue.unbind'{} | #'queue.delete'{} | - #'basic.consume'{} | #'basic.cancel'{} | #'basic.ack'{} | #'basic.nack'{} | - #'basic.qos'{}. --type misc_records() :: #'exchange.declare'{}. - - --export_type([consume_records/0, misc_records/0]). - --record(state, { - connection = 'undefined' :: 'undefined' | {pid(), reference()} - ,publish_channel = 'undefined' :: 'undefined' | channel_data() - ,misc_channel = 'undefined' :: 'undefined' | channel_data() - ,consumers = dict:new() :: dict() - ,use_federation = 'true' :: boolean() - ,return_handlers = dict:new() :: dict() %% ref, pid() - list of PIDs that are interested in returned messages - ,manager = 'undefined' :: 'undefined' | pid() - ,amqp_h = 'undefined' :: 'undefined' | binary() - }). - -%%%=================================================================== -%%% API -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @doc -%% Starts the server -%% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} -%% @end -%%-------------------------------------------------------------------- -start_link(Host, Conn, UseFederation) -> - gen_server:start_link(?MODULE, [Host, Conn, UseFederation], []). - --spec publish/4 :: (pid(), call_from(), #'basic.publish'{}, ne_binary() | iolist()) -> any(). -publish(Srv, From, BasicPub, AmqpMsg) -> - {ok, C} = gen_server:call(Srv, publish_channel), - gen_server:reply(From, amqp_channel:call(C, BasicPub, AmqpMsg)). - --spec consume/3 :: (pid(), call_from(), consume_records()) -> 'ok'. -consume(Srv, From, Msg) -> - gen_server:cast(Srv, {consume, From, Msg}). - --spec misc_req/3 :: (pid(), call_from(), misc_records()) -> 'ok'. -misc_req(Srv, From, Req) -> - gen_server:cast(Srv, {misc_req, From, Req}). - --spec register_return_handler/2 :: (pid(), call_from()) -> 'ok'. -register_return_handler(Srv, From) -> - gen_server:cast(Srv, {register_return_handler, From}). - --spec publish_channel/2 :: (pid(), call_from()) -> any(). -publish_channel(Srv, From) -> - gen_server:reply(From, gen_server:call(Srv, publish_channel)). - --spec misc_channel/2 :: (pid(), call_from()) -> any(). -misc_channel(Srv, From) -> - gen_server:reply(From, gen_server:call(Srv, misc_channel)). - --spec my_channel/2 :: (pid(), call_from()) -> any(). -my_channel(Srv, {FromPid, _}=From) -> - gen_server:reply(From, gen_server:call(Srv, {my_channel, FromPid})). - --spec update_my_tag/3 :: (pid(), call_from(), ne_binary()) -> any(). -update_my_tag(Srv, {FromPid, _}=From, Tag) -> - gen_server:reply(From, gen_server:cast(Srv, {update_my_tag, FromPid, Tag})). - --spec fetch_my_tag/2 :: (pid(), call_from()) -> any(). -fetch_my_tag(Srv, {FromPid, _}=From) -> - gen_server:reply(From, gen_server:call(Srv, {fetch_my_tag, FromPid})). - --spec stop/1 :: (pid()) -> 'ok' | {'error', 'you_are_not_my_boss'}. -stop(Srv) -> - case erlang:is_process_alive(Srv) of - true -> gen_server:call(Srv, stop); - false -> ok - end. - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Initializes the server -%% -%% @spec init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% @end -%%-------------------------------------------------------------------- -init([Host, Conn, UseFederation]) when is_pid(Conn) -> - process_flag(trap_exit, true), - put(callid, ?LOG_SYSTEM_ID), - lager:debug("starting amqp host for broker ~s", [Host]), - - Ref = erlang:monitor(process, Conn), - case start_channel(Conn) of - {Channel, _} = PubChan when is_pid(Channel) -> - _ = load_exchanges(Channel, UseFederation), - amqp_channel:register_return_handler(Channel, self()), - {ok, #state{ - connection = {Conn, Ref} - ,publish_channel = PubChan - ,misc_channel = start_channel(Conn) - ,consumers = dict:new() - ,manager = whereis(amqp_mgr) - ,amqp_h = Host - ,use_federation = UseFederation - }}; - {error, E} -> - lager:debug("unable to initialize publish channel for amqp host ~s, ~p", [Host, E]), - erlang:demonitor(Ref, [flush]), - {stop, E, Conn} - end. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling call messages -%% -%% @spec handle_call(Request, From, State) -> -%% {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_call(publish_channel, _, #state{publish_channel={C,_}}=State) -> - {reply, {ok, C}, State}; -handle_call(misc_channel, _, #state{misc_channel={C,_}}=State) -> - {reply, {ok, C}, State}; -handle_call({my_channel, FromPid}, _, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> % channel, channel ref - lager:debug("started new ch: ~p for proc: ~p", [C, FromPid]), - FromRef = erlang:monitor(process, FromPid), - amqp_selective_consumer:register_default_consumer(C, self()), - - {reply - ,{ok, C} - ,State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)} - ,hibernate - }; - closing -> - lager:debug("failed to start channel: closing"), - {reply, {error, closing}, State}; - {error, _}=E -> - lager:debug("failed to start channel: ~p", [E]), - {reply, E, State} - end; - {ok, {C,_,_,_}} -> - lager:debug("channel ~p exists for proc ~p", [C, FromPid]), - {reply, {ok, C}, State} - end; - -handle_call({fetch_my_tag, FromPid}, _, #state{consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> {reply, {error, not_consuming}, State}; - {ok, {_C, _R, T, _FromRef}} -> {reply, {ok, T}, State} - end; - - -handle_call(stop, {From, _}, #state{manager=Mgr}=State) -> - case Mgr =:= From of - true -> {stop, normal, ok, State}; - false -> {reply, {error, you_are_not_my_boss}, State} - end. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling cast messages -%% -%% @spec handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_cast({publish, From, #'basic.publish'{exchange=_Exchange, routing_key=_RK}=BasicPub, AmqpMsg}, #state{publish_channel={C,_}}=State) -> - lager:debug("Pub on ch ~p x: ~s rt: ~s", [C, _Exchange, _RK]), - spawn(fun() -> gen_server:reply(From, amqp_channel:cast(C, BasicPub, AmqpMsg)) end), - {noreply, State}; - -handle_cast({register_return_handler, {FromPid, _}=From}, #state{return_handlers=RHDict}=State) -> - gen_server:reply(From, ok), - lager:debug("adding ~p as a return handler", [FromPid]), - {noreply, State#state{return_handlers=dict:store(erlang:monitor(process, FromPid), FromPid, RHDict)}, hibernate}; - -handle_cast({update_my_tag, FromPid, Tag}, #state{consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> {noreply, State}; - {ok, {C, R, _T, FromRef}} -> - lager:debug("updating tag for ~p from ~p to ~p", [FromPid, _T, Tag]), - {noreply, State#state{consumers=dict:store(FromPid, {C, R, Tag, FromRef}, Consumers)}, hibernate} - end; - -handle_cast({consume, {FromPid, _}=From, #'basic.consume'{consumer_tag=CTag}=BasicConsume}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> % channel, channel ref - lager:debug("Consuming on ch: ~p for proc: ~p", [C, FromPid]), - FromRef = erlang:monitor(process, FromPid), - amqp_selective_consumer:register_default_consumer(C, self()), - - case try_to_subscribe(C, BasicConsume, FromPid) of - {ok, Tag} -> - gen_server:reply(From, {ok, C}), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,Tag,FromRef}, Consumers)}, hibernate}; - {error, _E}=Err -> - gen_server:reply(From, Err), - {noreply, State} - end; - closing -> - lager:debug("Failed to start channel: closing"), - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - lager:debug("Failed to start channel: ~p", [E]), - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,R,<<>>,FromRef}} -> - amqp_selective_consumer:register_default_consumer(C, self()), - lager:debug("Channel ~p exists for proc ~p, but we aren't consuming yet", [C, FromPid]), - - case try_to_subscribe(C, BasicConsume, FromPid) of - {ok, Tag} -> - gen_server:reply(From, {ok, C}), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,Tag,FromRef}, Consumers)}, hibernate}; - {error, _E}=Err -> - gen_server:reply(From, Err), - {noreply, State} - end; - {ok, {_,_,CTag,_}} when CTag =/= <<>> -> - gen_server:reply(From, {error, sloppy_code_detected}), - {noreply, State, hibernate}; - {ok, {C,_,_,_}} -> - case try_to_subscribe(C, BasicConsume, FromPid) of - {ok, Tag} -> - lager:debug("started additional consumer on ch: ~p for proc: ~p but dropping tag ~p, they will not be able to cancel this consumer...", [C, FromPid, Tag]), - gen_server:reply(From, {ok, C}); - {error, _E}=Err -> - gen_server:reply(From, Err) - end, - {noreply, State, hibernate} - end; - -handle_cast({consume, {FromPid, _}=From, #'basic.cancel'{}=BasicCancel}, #state{consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - gen_server:reply(From, {error, not_consuming}), - {noreply, State}; - {ok, {C,_,Tag,_}} -> - gen_server:reply(From, amqp_channel:cast(C, BasicCancel#'basic.cancel'{consumer_tag=Tag}, FromPid)), - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'queue.bind'{}=QueueBind}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - case amqp_channel:call(C, QueueBind) of - #'queue.bind_ok'{} -> - gen_server:reply(From, ok), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - ok -> - gen_server:reply(From, ok), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - {error, _E}=Err -> - lager:debug("Error binding queue: ~p", [_E]), - gen_server:reply(From, {error, Err}), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - E -> - lager:debug("Unexpected ~p", [E]), - gen_server:reply(From, {error, E}), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate} - end; - closing -> - lager:debug("Failed to start channel: closing"), - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - case amqp_channel:call(C, QueueBind) of - #'queue.bind_ok'{} -> - gen_server:reply(From, ok); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'queue.unbind'{}=QueueUnbind}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - case amqp_channel:call(C, QueueUnbind) of - #'queue.unbind_ok'{} -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - case amqp_channel:call(C, QueueUnbind) of - #'queue.unbind_ok'{} -> - gen_server:reply(From, ok); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'queue.declare'{}=QueueDeclare}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - case amqp_channel:call(C, QueueDeclare) of - #'queue.declare_ok'{}=QD -> - gen_server:reply(From, {ok, QD}); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - case amqp_channel:call(C, QueueDeclare) of - #'queue.declare_ok'{}=QD -> - gen_server:reply(From, {ok, QD}); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'queue.delete'{}=QueueDelete}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - - case amqp_channel:call(C, QueueDelete) of - #'queue.delete_ok'{} -> - gen_server:reply(From, ok); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - case amqp_channel:call(C, QueueDelete) of - #'queue.delete_ok'{} -> - gen_server:reply(From, ok); - ok -> - gen_server:reply(From, ok); - {error, _E}=Err -> - gen_server:reply(From, Err); - Err -> - gen_server:reply(From, {error, Err}) - end, - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'basic.qos'{}=BasicQos}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - gen_server:reply(From, amqp_channel:call(C, BasicQos)), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - gen_server:reply(From, amqp_channel:call(C, BasicQos)), - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'basic.ack'{}=BasicAck}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - gen_server:reply(From, amqp_channel:cast(C, BasicAck)), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - gen_server:reply(From, amqp_channel:cast(C, BasicAck)), - {noreply, State} - end; - -handle_cast({consume, {FromPid, _}=From, #'basic.nack'{}=BasicNack}, #state{connection=Conn, consumers=Consumers}=State) -> - case dict:find(FromPid, Consumers) of - error -> - case start_channel(Conn, FromPid) of - {C,R} when is_pid(C) andalso is_reference(R) -> - FromRef = erlang:monitor(process, FromPid), - gen_server:reply(From, amqp_channel:cast(C, BasicNack)), - {noreply, State#state{consumers=dict:store(FromPid, {C,R,<<>>,FromRef}, Consumers)}, hibernate}; - closing -> - gen_server:reply(From, {error, closing}), - {noreply, State}; - {error, _}=E -> - gen_server:reply(From, E), - {noreply, State} - end; - {ok, {C,_,_,_}} -> - gen_server:reply(From, amqp_channel:cast(C, BasicNack)), - {noreply, State} - end; - -handle_cast({misc_req, From, #'exchange.declare'{}=ED} - ,#state{misc_channel={C,_}, use_federation=UseFederation}=State) -> - spawn(fun() -> - put(callid, ?LOG_SYSTEM_ID), - - lager:debug("sending exchange.declare to ~p (federated: ~s)", [C, UseFederation]), - case amqp_channel:call(C, exchange_declare(ED, UseFederation)) of - #'exchange.declare_ok'{} -> - lager:debug("exchange declared"), - gen_server:reply(From, ok); - {error, _E}=Err -> - lager:debug("error declaring exchange: ~p", [_E]), - gen_server:reply(From, Err); - E -> - lager:debug("error declaring exchange: ~p", [E]), - gen_server:reply(From, {error, E}) - end - end), - {noreply, State}; - -handle_cast(_Msg, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Handling all non call/cast messages -%% -%% @spec handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% @end -%%-------------------------------------------------------------------- -handle_info({'DOWN', Ref, process, ConnPid, Reason}, #state{connection={ConnPid, Ref}}=State) -> - lager:debug("recieved notification our connection to the amqp broker died: ~p", [Reason]), - {stop, normal, State}; - -handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{return_handlers=RHDict}=State) -> - lager:debug("recieved notification monitored process ~p died ~p, searching for reference", [_Pid, _Reason]), - erlang:demonitor(Ref, [flush]), - {noreply, remove_ref(Ref, State#state{return_handlers=dict:erase(Ref, RHDict)}), hibernate}; - -handle_info({#'basic.return'{}, #amqp_msg{}}=ReturnMsg, #state{return_handlers=RHDict}=State) -> - spawn(fun() -> - put(callid, ?LOG_SYSTEM_ID), - lager:debug("recieved notification a message couldnt be delivered, forwarding to registered return handlers"), - dict:map(fun(_, Pid) -> Pid ! ReturnMsg end, RHDict) - end), - {noreply, State}; - -handle_info(_Info, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_server terminates -%% with Reason. The return value is ignored. -%% -%% @spec terminate(Reason, State) -> void() -%% @end -%%-------------------------------------------------------------------- --spec terminate/2 :: (term(), #state{}) -> 'ok'. -terminate(_Reason, {_H, _Conn, _UseF}) -> - lager:debug("amqp host failed to startup: ~p", [_Reason]), - lager:debug("params: ~s on conn ~p, use federation: ~s", [_H, _Conn, _UseF]); -terminate(_Reason, #state{consumers=Consumers, amqp_h=Host}) -> - spawn(fun() -> - put(callid, ?LOG_SYSTEM_ID), - notify_consumers({amqp_host_down, Host}, Consumers) - end), - lager:debug("amqp host for ~s terminated ~p", [Host, _Reason]). - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Convert process state when code is changed -%% -%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} -%% @end -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== --spec start_channel/1 :: ('undefined' | {pid(), reference()} | pid()) -> channel_data() | {'error', 'no_connection'} | 'closing'. -start_channel(undefined) -> - {error, no_connection}; -start_channel({Connection, _}) -> - start_channel(Connection); -start_channel(Connection) when is_pid(Connection) -> - %% Open an AMQP channel to access our realm - case erlang:is_process_alive(Connection) andalso amqp_connection:open_channel(Connection) of - {ok, Channel} -> - lager:debug("opened channel ~p", [Channel]), - - ChanMRef = erlang:monitor(process, Channel), - {Channel, ChanMRef}; - false -> - {error, no_connection}; - E -> - lager:debug("error opening channel: ~p", [E]), - E - end. - --spec start_channel/2 :: ('undefined' | {pid(), reference()} | pid(), pid()) -> channel_data() | {'error', 'no_connection'} | 'closing'. -start_channel(Connection, Pid) -> - case start_channel(Connection) of - {C, _} = Channel when is_pid(C) -> - lager:debug("started channel ~p for caller ~p", [C, Pid]), - Channel; - {error, no_connection}=E -> - lager:debug("no connection available to start channel"), - E; - E -> - lager:debug("failed to start new channel for ~p: ~p", [Pid, E]), - E - end. - --spec load_exchanges/2 :: (pid(), boolean()) -> 'ok'. -load_exchanges(C, UseFederation) -> - lists:foreach(fun({Ex, Type}) -> - ED = #'exchange.declare'{ - exchange = Ex - ,type = Type - }, - amqp_channel:call(C, exchange_declare(ED, UseFederation)) - end, ?KNOWN_EXCHANGES). - --spec remove_ref/2 :: (reference(), #state{}) -> #state{}. -remove_ref(Ref, #state{connection={Conn, _}, publish_channel={C,Ref}}=State) -> - lager:debug("reference was for publish channel ~p, restarting", [C]), - State#state{publish_channel=start_channel(Conn)}; - -remove_ref(Ref, #state{connection={Conn, _}, misc_channel={C,Ref}}=State) -> - lager:debug("reference was for misc channel ~p, restarting", [C]), - State#state{misc_channel=start_channel(Conn)}; - -remove_ref(Ref, #state{connection={Conn, _}, consumers=Cs}=State) -> - State#state{consumers = - dict:fold(fun(K, V, Acc) -> clean_consumers(K, V, Acc, Ref, Conn) end, Cs, Cs) - }. - --spec notify_consumers/2 :: ({'amqp_host_down', binary()}, dict()) -> 'ok'. -notify_consumers(Msg, Dict) -> - lists:foreach(fun({Pid,_}) -> Pid ! Msg end, dict:to_list(Dict)). - -%% Channel died --spec clean_consumers/5 :: (pid(), consumer_data(), dict(), reference(), pid()) -> dict(). -clean_consumers(FromPid, {C,Ref1,_,FromRef}, AccDict, Ref, Conn) when Ref =:= Ref1 -> - lager:debug("reference was for channel ~p for ~p, restarting", [C, FromPid]), - - erlang:demonitor(Ref1, [flush]), - erlang:is_process_alive(C) andalso amqp_channel:close(C), - - case start_channel(Conn, FromPid) of - {CNew, RefNew} when is_pid(CNew) andalso is_reference(RefNew) -> - lager:debug("new channel started for ~p", [FromPid]), - FromPid ! {amqp_lost_channel, connection_restored}, - dict:store(FromPid, {CNew, RefNew, <<>>, FromRef}, AccDict); - {error, no_connection} -> - lager:debug("no connection available"), - FromPid ! {amqp_lost_channel, no_connection}, - dict:erase(FromPid, AccDict); - closing -> - lager:debug("closing, no connection"), - FromPid ! {amqp_lost_channel, no_connection}, - dict:erase(FromPid, AccDict) - end; - -%% Consumer died -clean_consumers(FromPid, {C,CRef,_,FromRef}, AccDict, Ref, _) when Ref =:= FromRef -> - lager:debug("reference was for consumer ~p, removing channel ~p", [FromPid, C]), - erlang:demonitor(CRef, [flush]), - erlang:demonitor(FromRef, [flush]), - erlang:is_process_alive(C) andalso amqp_channel:close(C), - dict:erase(FromPid, AccDict); - -%% Generic channel cleanup when FromPid isn't alive -clean_consumers(FromPid, {C,CRef,_,FromRef}, AccDict, _, _) -> - case erlang:is_process_alive(FromPid) of - true -> AccDict; - false -> - lager:debug("reference was a consumer ~p that shutdown, removing channel ~p", [FromPid, C]), - erlang:demonitor(FromRef, [flush]), - erlang:demonitor(CRef, [flush]), - erlang:is_process_alive(C) andalso amqp_channel:close(C), - dict:erase(FromPid, AccDict) - end; - -%% Simple catchall -clean_consumers(_, _, AccDict,_,_) -> - AccDict. - --spec try_to_subscribe/3 :: (pid(), #'basic.consume'{}, pid()) -> {'ok', non_neg_integer()} | {'error', term()}. -try_to_subscribe(C, BasicConsume, FromPid) -> - try amqp_channel:subscribe(C, BasicConsume, FromPid) of - #'basic.consume_ok'{consumer_tag=Tag} -> {ok, Tag}; - Other -> {error, Other} - catch - _E:R -> {error, R} - end. - -exchange_declare(ED, false) -> - ED; -exchange_declare(#'exchange.declare'{type=Type}=ED, true) -> -% -record('exchange.declare', {ticket = 0, exchange, type = <<"direct">>, passive = false, durable = false, auto_delete = false, internal = false, nowait = false, arguments = []}). - ED1 = ED#'exchange.declare'{ - type = <<"x-federation">> - ,arguments = [{<<"type">>, longstr, Type} - ,{<<"upstream-set">>, longstr, ?RABBITMQ_UPSTREAM_SET} - ] - }, - ED1. diff --git a/lib/whistle_amqp-1.0.0/src/amqp_host_sup.erl b/lib/whistle_amqp-1.0.0/src/amqp_host_sup.erl deleted file mode 100644 index 60d108db673..00000000000 --- a/lib/whistle_amqp-1.0.0/src/amqp_host_sup.erl +++ /dev/null @@ -1,73 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author James Aimonetti -%%% @copyright (C) 2011, VoIP INC -%%% @doc -%%% -%%% @end -%%% Created : 18 Mar 2011 by James Aimonetti -%%%------------------------------------------------------------------- --module(amqp_host_sup). - --behaviour(supervisor). - -%% API --export([start_link/0, start_host/3]). - -%% Supervisor callbacks --export([init/1]). - --define(SERVER, ?MODULE). - -%%%=================================================================== -%%% API functions -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @doc -%% Starts the supervisor -%% -%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} -%% @end -%%-------------------------------------------------------------------- -start_link() -> - supervisor:start_link({local, ?SERVER}, ?MODULE, []). - -start_host(Host, Conn, UseFederation) -> - supervisor:start_child(?SERVER, [Host, Conn, UseFederation]). - -%%%=================================================================== -%%% Supervisor callbacks -%%%=================================================================== - -%%-------------------------------------------------------------------- -%% @private -%% @doc -%% Whenever a supervisor is started using supervisor:start_link/[2,3], -%% this function is called by the new process to find out about -%% restart strategy, maximum restart frequency and child -%% specifications. -%% -%% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | -%% ignore | -%% {error, Reason} -%% @end -%%-------------------------------------------------------------------- -init([]) -> - RestartStrategy = simple_one_for_one, - MaxRestarts = 2, - MaxSecondsBetweenRestarts = 5, - - SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, - - Restart = temporary, - Shutdown = 2000, - Type = worker, - - AChild = {amqp_host, {amqp_host, start_link, []}, - Restart, Shutdown, Type, [amqp_host]}, - - {ok, {SupFlags, [AChild]}}. - -%%%=================================================================== -%%% Internal functions -%%%=================================================================== diff --git a/lib/whistle_amqp-1.0.0/src/amqp_mgr.erl b/lib/whistle_amqp-1.0.0/src/amqp_mgr.erl deleted file mode 100644 index fc4235b17eb..00000000000 --- a/lib/whistle_amqp-1.0.0/src/amqp_mgr.erl +++ /dev/null @@ -1,425 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @copyright (C) 2010-2012, VoIP INC -%%% @doc -%%% -%%% @end -%%% @contributors -%%% James Aimonetti -%%% Karl Anderson -%%%------------------------------------------------------------------- --module(amqp_mgr). - --behaviour(gen_server). - -%% API --export([start_conn/1, start_conn/2, get_host/0]). - --export([start_link/0, publish/2, consume/1, misc_req/1, register_return_handler/0]). - --export([publish_channel/0, misc_channel/0, my_channel/0 - ,update_my_tag/1, fetch_my_tag/0 - ]). - --export([is_available/0]). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --include("amqp_util.hrl"). - --define(SERVER, ?MODULE). --define(START_TIMEOUT, 500). --define(MAX_TIMEOUT, 2000). --define(STARTUP_FILE, [code:lib_dir(whistle_amqp, priv), "/startup.config"]). - --record(state, { - amqp_uri = "" :: string() - ,use_federation = false :: boolean() - ,handler_pid = 'undefined' :: 'undefined' | pid() - ,handler_ref = 'undefined' :: 'undefined' | reference() - ,conn_params = 'undefined' :: 'undefined' | #'amqp_params_direct'{} | #'amqp_params_network'{} - ,conn_ref = 'undefined' :: 'undefined' | reference() - ,timeout = ?START_TIMEOUT :: integer() - }). - -%%==================================================================== -%% API -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- -start_link() -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). - --spec start_conn/1 :: (nonempty_string() | ne_binary()) -> 'ok'. --spec start_conn/2 :: (nonempty_string() | ne_binary(), boolean()) -> 'ok'. -start_conn(AmqpUri) -> - gen_server:cast(?SERVER, {start_conn, wh_util:to_list(AmqpUri)}). -start_conn(AmqpUri, UseFederation) -> - gen_server:cast(?SERVER, {start_conn, wh_util:to_list(AmqpUri), wh_util:is_true(UseFederation)}). - --spec get_host/0 :: () -> nonempty_string(). -get_host() -> - gen_server:call(?SERVER, get_host). - --spec publish_channel/0 :: () -> {'ok', pid()} | - {'error', term()}. -publish_channel() -> - gen_server:call(?SERVER, {publish_channel}). - --spec misc_channel/0 :: () -> {'ok', pid()} | - {'error', term()}. -misc_channel() -> - gen_server:call(?SERVER, {misc_channel}). - --spec my_channel/0 :: () -> {'ok', pid()} | - {'error', term()}. -my_channel() -> - gen_server:call(?SERVER, {my_channel}). - --spec update_my_tag/1 :: (ne_binary()) -> 'ok'. -update_my_tag(Tag) -> - gen_server:call(?SERVER, {update_my_tag, Tag}). - --spec fetch_my_tag/0 :: () -> {'ok', binary()}. -fetch_my_tag() -> - gen_server:call(?SERVER, {fetch_my_tag}). - --spec publish/2 :: (#'basic.publish'{}, #'amqp_msg'{}) -> 'ok'. -publish(BP, AM) -> - gen_server:call(?SERVER, {publish, BP, AM}). - --spec consume/1 :: (amqp_host:consume_records()) -> 'ok' | - {'ok', pid() | ne_binary() | #'queue.declare_ok'{}} | - {'error', 'not_consuming' | atom()}. -consume(BC) -> - gen_server:call(?SERVER, {consume, BC}). - --spec misc_req/1 :: (amqp_host:misc_records()) -> 'ok' | {'error', atom()}. -misc_req(Req) -> - gen_server:call(?SERVER, {misc_req, Req}). - --spec is_available/0 :: () -> boolean(). -is_available() -> - gen_server:call(?SERVER, is_available). - --spec register_return_handler/0 :: () -> 'ok'. -register_return_handler() -> - gen_server:call(?SERVER, {register_return_handler}). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== - -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- --spec init/1 :: ([]) -> {'ok', #state{}}. -init([]) -> - put(callid, ?LOG_SYSTEM_ID), - Init = get_config(), - Uri = props:get_value(amqp_uri, Init, ?DEFAULT_AMQP_URI), - UseFederation = props:get_value(use_federation, Init, false), - - case start_amqp_host(Uri, UseFederation) of - {ok, State} -> - lager:debug("starting amqp manager server"), - lager:info("We've connected successfully to the broker at ~s", [Uri]), - process_flag(trap_exit, true), - {ok, State#state{amqp_uri=Uri, use_federation=UseFederation}}; - {error, E} -> - lager:info("We tried to connect to ~s, but were unsuccessful: ~p", [Uri, E]), - lager:info("We can't start without a working connection to an AMQP broker"), - {stop, E} - end. - -%%-------------------------------------------------------------------- -%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%% -%%-------------------------------------------------------------------- -handle_call(is_available, _, #state{handler_pid=undefined}=State) -> - {reply, false, State}; -handle_call(is_available, _, #state{handler_pid=HPid}=State) -> - case erlang:is_pid(HPid) andalso erlang:is_process_alive(HPid) of - true -> {reply, true, State}; - false -> - ok = stop_amqp_host(State), - self() ! {reconnect, ?START_TIMEOUT}, - {reply, false, State#state{handler_pid=undefined, handler_ref=undefined}} - end; - -handle_call(get_host, _, #state{conn_params=ConnP}=State) -> - {reply, wh_amqp_params:host(ConnP), State}; - -handle_call(_, _, #state{handler_pid = undefined}=State) -> - {reply, {error, amqp_down}, State}; - -handle_call({publish, BP, AM}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, fun() -> catch amqp_host:publish(HPid, From, BP, AM) end), - {noreply, State}; - -handle_call({consume, Msg}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, fun() -> catch amqp_host:consume(HPid, From, Msg) end), - {noreply, State}; - -handle_call({misc_req, Req}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, fun() -> catch amqp_host:misc_req(HPid, From, Req) end), - {noreply, State}; - -handle_call({register_return_handler}, From, #state{handler_pid=HPid}=State)-> - send_req(HPid, From, fun() -> catch amqp_host:register_return_handler(HPid, From) end), - {noreply, State}; - -handle_call({publish_channel}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, publish_channel), - {noreply, State}; - -handle_call({misc_channel}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, misc_channel), - {noreply, State}; - -handle_call({my_channel}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, my_channel), - {noreply, State}; - -handle_call({update_my_tag, Tag}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, update_my_tag, Tag), - {noreply, State}; - -handle_call({fetch_my_tag}, From, #state{handler_pid=HPid}=State) -> - send_req(HPid, From, fetch_my_tag), - {noreply, State}; - -handle_call(_, _, State) -> - {noreply, State}. - -send_req(HPid, From, Fun) when is_function(Fun, 0) -> - case erlang:is_process_alive(HPid) of - true -> spawn(Fun); - false -> gen_server:reply(From, {error, amqp_host_missing}) - end; -send_req(HPid, From, Fun) when is_atom(Fun) -> - case erlang:is_process_alive(HPid) of - true -> spawn(fun() -> amqp_host:Fun(HPid, From) end); - false -> gen_server:reply(From, {error, amqp_host_missing}) - end. - -send_req(HPid, From, Fun, Arg) when is_atom(Fun) -> - case erlang:is_process_alive(HPid) of - true -> spawn(fun() -> amqp_host:Fun(HPid, From, Arg) end); - false -> gen_server:reply(From, {error, amqp_host_missing}) - end. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({start_conn, ""}, State) -> - handle_cast({start_conn, ?DEFAULT_AMQP_URI}, State); - -handle_cast({start_conn, Uri}, #state{use_federation=UseFederation}) -> - lager:debug("Starting connection with uri: ~s: ~s", [Uri, UseFederation]), - - case start_amqp_host(Uri, UseFederation) of - {ok, State1} -> - lager:info("We've connected successfully to the broker at ~s", [Uri]), - {noreply, State1#state{amqp_uri=Uri, use_federation=UseFederation}}; - {error, E} -> - lager:info("We can't seem to start an AMQP connection using ~s: ~p", [Uri, E]), - {stop, E, normal} - end; -handle_cast({start_conn, Uri, UseFederation}, _State) -> - lager:debug("Starting connection with uri: ~s: ~s", [Uri, UseFederation]), - - case start_amqp_host(Uri, UseFederation) of - {ok, State1} -> - lager:info("We've connected successfully to the broker at ~s", [Uri]), - {noreply, State1#state{amqp_uri=Uri, use_federation=UseFederation}}; - {error, E} -> - lager:info("We can't seem to start an AMQP connection using ~s: ~p", [Uri, E]), - {stop, E, normal} - end. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info({reconnect, Timeout}, #state{conn_params=ConnP - ,use_federation=UseFederation - ,amqp_uri=_Uri - }=State) -> - case start_amqp_host(ConnP, State, UseFederation) of - {ok, State1} -> - lager:debug("reconnected AMQP"), - lager:info("we've reconnected to the broker at ~s", [_Uri]), - {noreply, State1}; - {error, _E} -> - lager:debug("failed to reconnect to AMQP(~p), waiting a bit more", [_E]), - lager:info("we failed to reconnect to the AMQP broker at ~s: ~p", [_Uri, _E]), - - NextTimeout = next_timeout(Timeout), - lager:info("we will try to recoonect in ~b milliseconds", [NextTimeout]), - - _Ref = erlang:send_after(NextTimeout, self(), {reconnect, NextTimeout}), - {noreply, State} - end; - -handle_info({'DOWN', ConnRef, process, _Pid, {shutdown, {server_initiated_close,Code,_}}} - ,#state{conn_params=ConnP, conn_ref=ConnRef}=State) -> - lager:debug("connection to ~s (process ~p) went down, error ~p", [wh_amqp_params:host(ConnP), _Pid, Code]), - erlang:demonitor(ConnRef, [flush]), - _Ref = erlang:send_after(0, self(), {reconnect, ?START_TIMEOUT}), - ok = stop_amqp_host(State), - {noreply, State#state{conn_ref=undefined, handler_pid=undefined - ,handler_ref=undefined, use_federation = false - }, hibernate}; - -handle_info({'DOWN', ConnRef, process, _Pid, {shutdown, {internal_error, Code,_}}} - ,#state{conn_params=ConnP, conn_ref=ConnRef}=State) -> - lager:debug("connection to ~s (process ~p) went down, error ~p", [wh_amqp_params:host(ConnP), _Pid, Code]), - erlang:demonitor(ConnRef, [flush]), - _Ref = erlang:send_after(0, self(), {reconnect, ?START_TIMEOUT}), - ok = stop_amqp_host(State), - {noreply, State#state{conn_ref=undefined, handler_pid=undefined - ,handler_ref=undefined, use_federation = false - }, hibernate}; - -handle_info({'DOWN', ConnRef, process, _Pid, _Reason}, #state{conn_params=ConnP, conn_ref=ConnRef}=State) -> - lager:debug("connection to ~s (process ~p) went down, ~w", [wh_amqp_params:host(ConnP), _Pid, _Reason]), - erlang:demonitor(ConnRef, [flush]), - _Ref = erlang:send_after(0, self(), {reconnect, ?START_TIMEOUT}), - ok = stop_amqp_host(State), - {noreply, State#state{conn_ref=undefined, handler_pid=undefined, handler_ref=undefined}, hibernate}; - -handle_info({'DOWN', Ref, process, _, normal}, #state{handler_ref=Ref}=State) -> - lager:debug("amqp host proc down normally"), - erlang:demonitor(Ref, [flush]), - {noreply, State#state{handler_ref=undefined, handler_pid=undefined}}; - -handle_info({'DOWN', Ref, process, _, _Reason}, #state{handler_ref=Ref}=State) -> - lager:debug("amqp host process went down, ~p", [_Reason]), - erlang:demonitor(Ref, [flush]), - _Ref = erlang:send_after(0, self(), {reconnect, ?START_TIMEOUT}), - - {noreply, State#state{handler_pid=undefined, handler_ref=undefined}, hibernate}; - -handle_info(timeout, State) -> - {noreply, State}; - -handle_info(_Info, State) -> - lager:debug("unhandled message: ~p", [_Info]), - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- --spec terminate/2 :: (term(), #state{}) -> no_return(). -terminate(_Reason, _) -> - lager:debug("amqp manager terminated: ~p", [_Reason]). - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%-------------------------------------------------------------------- -%%% Internal functions -%%-------------------------------------------------------------------- --spec get_new_connection/1 :: (#'amqp_params_direct'{} | #'amqp_params_network'{}) -> {'ok', pid()} | {'error', 'econnrefused' | 'broker_not_found_on_node'}. -get_new_connection(ConnP) -> - case amqp_connection:start(ConnP) of - {ok, Connection}=OK -> - lager:debug("established network connection (~p) to amqp broker at ~s", [Connection, wh_amqp_params:host(ConnP)]), - OK; - {error, econnrefused}=E -> - Host = wh_amqp_params:host(ConnP), - lager:debug("amqp connection to ~s refused", [Host]), - E; - {error, broker_not_found_on_node}=E -> - lager:debug("found node ~s but no amqp broker", [wh_amqp_params:host(ConnP)]), - E - end. - -stop_amqp_host(#state{handler_pid=undefined}) -> - ok; -stop_amqp_host(#state{handler_pid=HPid, handler_ref=HRef}) -> - erlang:demonitor(HRef, [flush]), - _ = net_kernel:monitor_nodes(false), - amqp_host:stop(HPid). - --spec start_amqp_host/2 :: (string(), boolean()) -> {'ok', #state{}} | {'error', 'econnrefused'}. --spec start_amqp_host/3 :: (#'amqp_params_direct'{} | #'amqp_params_network'{}, #state{}, boolean()) -> - {'ok', #state{}} | {'error', 'econnrefused'}. -start_amqp_host(Uri, UseFederation) -> - case amqp_uri:parse(Uri) of - {ok, Settings} -> - start_amqp_host(Settings, #state{}, UseFederation); - {error, {Info, _}} -> - {error, Info} - end. - -start_amqp_host(ConnP, State, UseFederation) -> - case get_new_connection(ConnP) of - {error,_}=E -> - E; - {ok, Conn} -> - case amqp_host_sup:start_host(wh_amqp_params:host(ConnP), Conn, UseFederation) of - {ok, HPid} -> - HRef = erlang:monitor(process, HPid), - ConnRef = erlang:monitor(process, Conn), - - lager:debug("Started amqp_host ~p", [HPid]), - - {ok, State#state{handler_pid=HPid, handler_ref=HRef, conn_ref=ConnRef - ,conn_params=ConnP, timeout=?START_TIMEOUT - }}; - E -> - lager:debug("Error starting amqp_host ~p", [E]), - case UseFederation of - true -> start_amqp_host(ConnP, State, false); - false -> E - end - end - end. - --spec get_config/0 :: () -> proplist(). -get_config() -> - case file:consult(?STARTUP_FILE) of - {ok, Prop} -> - lager:debug("loaded amqp manager configuration from ~s", [?STARTUP_FILE]), - lager:info("loaded amqp manager configuration from ~s", [?STARTUP_FILE]), - Prop; - E -> - lager:debug("unable to load amqp manager configuration ~p", [E]), - [] - end. - --spec next_timeout/1 :: (pos_integer()) -> ?START_TIMEOUT..?MAX_TIMEOUT. -next_timeout(?MAX_TIMEOUT=Timeout) -> Timeout; -next_timeout(Timeout) when Timeout*2 > ?MAX_TIMEOUT -> - ?MAX_TIMEOUT; -next_timeout(Timeout) when Timeout < ?START_TIMEOUT -> - ?START_TIMEOUT; -next_timeout(Timeout) -> - Timeout * 2. diff --git a/lib/whistle_amqp-1.0.0/src/amqp_util.erl b/lib/whistle_amqp-1.0.0/src/amqp_util.erl index 2fdc901efb4..243717d5ee1 100644 --- a/lib/whistle_amqp-1.0.0/src/amqp_util.erl +++ b/lib/whistle_amqp-1.0.0/src/amqp_util.erl @@ -92,7 +92,7 @@ -export([new_queue/0, new_queue/1, new_queue/2]). -export([basic_consume/1, basic_consume/2]). -export([basic_publish/3, basic_publish/4]). --export([basic_cancel/1]). +-export([basic_cancel/0]). -export([queue_delete/1, queue_delete/2]). -export([new_exchange/2, new_exchange/3]). @@ -361,10 +361,7 @@ basic_publish(Exchange, Queue, Payload, ContentType, Props) when is_binary(Paylo ,props = MsgProps }, - ?AMQP_DEBUG andalso lager:debug("publish ~s ~s (~p): ~s", [Exchange, Queue, Props, Payload]), - - {ok, C} = amqp_mgr:publish_channel(), - amqp_channel:cast(C, BP, AM). + wh_amqp_mgr:publish(BP, AM). %%------------------------------------------------------------------------------ %% @public @@ -432,10 +429,7 @@ new_exchange(Exchange, Type, Options) -> ,nowait = props:get_value(nowait, Options, false) ,arguments = props:get_value(arguments, Options, []) }, - ?AMQP_DEBUG andalso lager:debug("create new ~s exchange: ~s", [Type, Exchange]), - - {ok, C} = amqp_mgr:misc_channel(), - amqp_channel:call(C, ED). + wh_amqp_mgr:misc_req(ED). %%------------------------------------------------------------------------------ %% @public @@ -443,91 +437,91 @@ new_exchange(Exchange, Type, Options) -> %% Create AMQP queues %% @end %%------------------------------------------------------------------------------ --spec new_targeted_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_targeted_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_targeted_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_targeted_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_targeted_queue() -> new_targeted_queue(<<>>). new_targeted_queue(Queue) -> new_queue(Queue, [{nowait, false}]). --spec new_whapps_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_whapps_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_whapps_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_whapps_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_whapps_queue() -> new_whapps_queue(<<>>). new_whapps_queue(Queue) -> new_queue(Queue, [{nowait, false}]). --spec new_notifications_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_notifications_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_notifications_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_notifications_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_notifications_queue() -> new_notifications_queue(<<>>). new_notifications_queue(Queue) -> new_queue(Queue, [{nowait, false}]). --spec new_sysconf_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_sysconf_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_sysconf_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_sysconf_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_sysconf_queue() -> new_sysconf_queue(<<>>). new_sysconf_queue(Queue) -> new_queue(Queue, [{nowait, false}]). --spec new_callevt_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_callevt_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_callevt_queue(<<>>) -> new_queue(<<>>, [{exclusive, false}, {auto_delete, true}, {nowait, false}]); new_callevt_queue(CallID) -> new_queue(list_to_binary([?EXCHANGE_CALLEVT, ".", encode(CallID)]) ,[{exclusive, false}, {auto_delete, true}, {nowait, false}]). --spec new_callctl_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_callctl_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_callctl_queue(<<>>) -> new_queue(<<>>, [{exclusive, false}, {auto_delete, true}, {nowait, false}]); new_callctl_queue(CallID) -> new_queue(list_to_binary([?EXCHANGE_CALLCTL, ".", encode(CallID)]) ,[{exclusive, false}, {auto_delete, true}, {nowait, false}]). --spec new_resource_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_resource_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_resource_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_resource_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_resource_queue() -> new_resource_queue(?RESOURCE_QUEUE_NAME). new_resource_queue(Queue) -> new_queue(Queue, [{exclusive, false}, {auto_delete, true}, {nowait, false}]). --spec new_callmgr_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. --spec new_callmgr_queue/2 :: (binary(), proplist()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_callmgr_queue/1 :: (binary()) -> ne_binary() | {'error', _}. +-spec new_callmgr_queue/2 :: (binary(), proplist()) -> ne_binary() | {'error', _}. new_callmgr_queue(Queue) -> new_callmgr_queue(Queue, []). new_callmgr_queue(Queue, Opts) -> new_queue(Queue, Opts). --spec new_configuration_queue/1 :: (ne_binary()) -> ne_binary() | {'error', 'amqp_error'}. --spec new_configuration_queue/2 :: (ne_binary(), proplist()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_configuration_queue/1 :: (ne_binary()) -> ne_binary() | {'error', _}. +-spec new_configuration_queue/2 :: (ne_binary(), proplist()) -> ne_binary() | {'error', _}. new_configuration_queue(Queue) -> new_configuration_queue(Queue, []). new_configuration_queue(Queue, Options) -> new_queue(Queue, Options). --spec new_monitor_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_monitor_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_monitor_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_monitor_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_monitor_queue() -> new_monitor_queue(<<>>). new_monitor_queue(Queue) -> new_queue(Queue, [{exclusive, false}, {auto_delete, true}]). --spec new_conference_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_conference_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_conference_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_conference_queue/1 :: (binary()) -> ne_binary() | {'error', _}. new_conference_queue() -> new_conference_queue(<<>>). new_conference_queue(Queue) -> new_queue(Queue, [{exclusive, false}, {auto_delete, true}, {nowait, false}]). %% Declare a queue and returns the queue Name --spec new_queue/0 :: () -> ne_binary() | {'error', 'amqp_error'}. --spec new_queue/1 :: (binary()) -> ne_binary() | {'error', 'amqp_error'}. --spec new_queue/2 :: (binary(), proplist()) -> ne_binary() | {'error', 'amqp_error'}. +-spec new_queue/0 :: () -> ne_binary() | {'error', _}. +-spec new_queue/1 :: (binary()) -> ne_binary() | {'error', _}. +-spec new_queue/2 :: (binary(), proplist()) -> ne_binary() | {'error', _}. new_queue() -> new_queue(<<>>). % lets the client lib create a random queue name new_queue(Queue) -> @@ -542,17 +536,9 @@ new_queue(Queue, Options) when is_binary(Queue) -> ,nowait = props:get_value(nowait, Options, false) ,arguments = props:get_value(arguments, Options, []) }, - {ok, C} = amqp_mgr:my_channel(), - - lager:debug("trying to declare queue ~s on ~p", [Queue, C]), - - case amqp_channel:call(C, QD) of - #'queue.declare_ok'{queue=Q} -> - ?AMQP_DEBUG andalso lager:debug("create queue: ~s", [Q]), - Q; - {error, _Other}=E -> - ?AMQP_DEBUG andalso lager:debug("error creating queue(~p): ~p", [Options, _Other]), - E + case wh_amqp_mgr:consume(QD) of + {ok, Q} -> Q; + {error, _}=E -> E end. %%------------------------------------------------------------------------------ @@ -560,7 +546,7 @@ new_queue(Queue, Options) when is_binary(Queue) -> %% @doc %% Delete AMQP queue %% @end -%%------------------------------------------------------------------------------ + %%------------------------------------------------------------------------------ delete_targeted_queue(Queue) -> queue_delete(Queue, []). @@ -610,9 +596,7 @@ queue_delete(Queue, Prop) -> ,if_empty = props:get_value(if_empty, Prop, false) ,nowait = props:get_value(nowait, Prop, true) }, - ?AMQP_DEBUG andalso lager:debug("delete queue ~s", [Queue]), - {ok, C} = amqp_mgr:my_channel(), - amqp_channel:call(C, QD). + ok = wh_amqp_mgr:consume(QD). %%------------------------------------------------------------------------------ %% @public @@ -620,43 +604,43 @@ queue_delete(Queue, Prop) -> %% Bind a Queue to an Exchange (with optional Routing Key) %% @end %%------------------------------------------------------------------------------ --spec bind_q_to_targeted/1 :: (ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_targeted/1 :: (ne_binary()) -> 'ok'. bind_q_to_targeted(Queue) -> bind_q_to_exchange(Queue, Queue, ?EXCHANGE_TARGETED). bind_q_to_targeted(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_TARGETED). --spec bind_q_to_whapps/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_whapps/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_whapps/2 :: (ne_binary(), ne_binary()) -> 'ok'. +-spec bind_q_to_whapps/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok'. bind_q_to_whapps(Queue, Routing) -> bind_q_to_whapps(Queue, Routing, []). bind_q_to_whapps(Queue, Routing, Options) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_WHAPPS, Options). --spec bind_q_to_notifications/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_notifications/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_notifications/2 :: (ne_binary(), ne_binary()) -> 'ok'. +-spec bind_q_to_notifications/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok'. bind_q_to_notifications(Queue, Routing) -> bind_q_to_notifications(Queue, Routing, []). bind_q_to_notifications(Queue, Routing, Options) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_NOTIFICATIONS, Options). --spec bind_q_to_sysconf/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_sysconf/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_sysconf/2 :: (ne_binary(), ne_binary()) -> 'ok'. +-spec bind_q_to_sysconf/3 :: (ne_binary(), ne_binary(), proplist()) -> 'ok'. bind_q_to_sysconf(Queue, Routing) -> bind_q_to_sysconf(Queue, Routing, []). bind_q_to_sysconf(Queue, Routing, Options) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_SYSCONF, Options). --spec bind_q_to_callctl/1 :: (ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_callctl/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_callctl/1 :: (ne_binary()) -> 'ok'. +-spec bind_q_to_callctl/2 :: (ne_binary(), ne_binary()) -> 'ok'. bind_q_to_callctl(Queue) -> bind_q_to_callctl(Queue, Queue). bind_q_to_callctl(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_CALLCTL). %% to receive all call events or cdrs, regardless of callid, pass <<"*">> for CallID --spec bind_q_to_callevt/2 :: (ne_binary(), ne_binary() | 'media_req') -> 'ok' | {'error', atom()}. --spec bind_q_to_callevt/3 :: (ne_binary(), ne_binary(), Type) -> 'ok' | {'error', atom()} when +-spec bind_q_to_callevt/2 :: (ne_binary(), ne_binary() | 'media_req') -> 'ok'. +-spec bind_q_to_callevt/3 :: (ne_binary(), ne_binary(), Type) -> 'ok' when Type :: 'events' | 'status_req' | 'cdr' | 'other'. bind_q_to_callevt(Queue, media_req) -> bind_q_to_exchange(Queue, ?KEY_CALL_MEDIA_REQ, ?EXCHANGE_CALLEVT); @@ -672,27 +656,27 @@ bind_q_to_callevt(Queue, CallID, cdr) -> bind_q_to_callevt(Queue, Routing, other) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_CALLEVT). --spec bind_q_to_resource/1 :: (ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_resource/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_resource/1 :: (ne_binary()) -> 'ok'. +-spec bind_q_to_resource/2 :: (ne_binary(), ne_binary()) -> 'ok'. bind_q_to_resource(Queue) -> bind_q_to_resource(Queue, <<"#">>). bind_q_to_resource(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_RESOURCE). --spec bind_q_to_callmgr/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_callmgr/2 :: (ne_binary(), ne_binary()) -> 'ok'. bind_q_to_callmgr(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_CALLMGR). --spec bind_q_to_configuration/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_configuration/2 :: (ne_binary(), ne_binary()) -> 'ok'. bind_q_to_configuration(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_CONFIGURATION). --spec bind_q_to_monitor/2 :: (ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_monitor/2 :: (ne_binary(), ne_binary()) -> 'ok'. bind_q_to_monitor(Queue, Routing) -> bind_q_to_exchange(Queue, Routing, ?EXCHANGE_MONITOR). --spec bind_q_to_conference/2 :: (ne_binary(), conf_routing_type()) -> 'ok' | {'error', atom()}. --spec bind_q_to_conference/3 :: (ne_binary(), conf_routing_type(), 'undefined' | ne_binary()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_conference/2 :: (ne_binary(), conf_routing_type()) -> 'ok'. +-spec bind_q_to_conference/3 :: (ne_binary(), conf_routing_type(), 'undefined' | ne_binary()) -> 'ok'. bind_q_to_conference(Queue, discovery) -> bind_q_to_conference(Queue, discovery, undefined); @@ -708,8 +692,8 @@ bind_q_to_conference(Queue, event, ConfId) -> bind_q_to_conference(Queue, command, ConfId) -> bind_q_to_exchange(Queue, <>, ?EXCHANGE_CONFERENCE). --spec bind_q_to_exchange/3 :: (ne_binary(), ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. --spec bind_q_to_exchange/4 :: (ne_binary(), ne_binary(), ne_binary(), proplist()) -> 'ok' | {'error', atom()}. +-spec bind_q_to_exchange/3 :: (ne_binary(), ne_binary(), ne_binary()) -> 'ok'. +-spec bind_q_to_exchange/4 :: (ne_binary(), ne_binary(), ne_binary(), proplist()) -> 'ok'. bind_q_to_exchange(Queue, _Routing, _Exchange) when not is_binary(Queue) -> {error, invalid_queue_name}; bind_q_to_exchange(Queue, Routing, Exchange) -> @@ -722,14 +706,7 @@ bind_q_to_exchange(Queue, Routing, Exchange, Options) -> ,nowait = props:get_value(nowait, Options, false) ,arguments = [] }, - - ?AMQP_DEBUG andalso lager:debug("binding queue ~s to ~s with key ~s", [Queue, Exchange, Routing]), - - {ok, C} = amqp_mgr:my_channel(), - case amqp_channel:call(C, QB) of - #'queue.bind_ok'{} -> ok; - E -> E - end. + ok = wh_amqp_mgr:consume(QB). %%------------------------------------------------------------------------------ %% @public @@ -737,8 +714,8 @@ bind_q_to_exchange(Queue, Routing, Exchange, Options) -> %% Unbind a Queue from an Exchange %% @end %%------------------------------------------------------------------------------ --spec unbind_q_from_callevt/2 :: (ne_binary(), ne_binary() | 'media_req') -> 'ok' | {'error', atom()}. --spec unbind_q_from_callevt/3 :: (ne_binary(), ne_binary(), Type) -> 'ok' | {'error', atom()} when +-spec unbind_q_from_callevt/2 :: (ne_binary(), ne_binary() | 'media_req') -> 'ok' | {'error', _}. +-spec unbind_q_from_callevt/3 :: (ne_binary(), ne_binary(), Type) -> 'ok' | {'error', _} when Type :: 'events' | 'status_req' | 'cdr' | 'other'. unbind_q_from_callevt(Queue, media_req) -> unbind_q_from_exchange(Queue, ?KEY_CALL_MEDIA_REQ, ?EXCHANGE_CALLEVT); @@ -755,8 +732,8 @@ unbind_q_from_callevt(Queue, Routing, other) -> unbind_q_from_exchange(Queue, Routing, ?EXCHANGE_CALLEVT). --spec unbind_q_from_conference/2 :: (ne_binary(), conf_routing_type()) -> 'ok' | {'error', atom()}. --spec unbind_q_from_conference/3 :: (ne_binary(), conf_routing_type(), 'undefined' | ne_binary()) -> 'ok' | {'error', atom()}. +-spec unbind_q_from_conference/2 :: (ne_binary(), conf_routing_type()) -> 'ok' | {'error', _}. +-spec unbind_q_from_conference/3 :: (ne_binary(), conf_routing_type(), 'undefined' | ne_binary()) -> 'ok' | {'error', _}. unbind_q_from_conference(Queue, discovery) -> unbind_q_from_conference(Queue, discovery, undefined); @@ -796,7 +773,7 @@ unbind_q_from_targeted(Queue) -> unbind_q_from_whapps(Queue, Routing) -> unbind_q_from_exchange(Queue, Routing, ?EXCHANGE_WHAPPS). --spec unbind_q_from_exchange/3 :: (ne_binary(), ne_binary(), ne_binary()) -> 'ok' | {'error', atom()}. +-spec unbind_q_from_exchange/3 :: (ne_binary(), ne_binary(), ne_binary()) -> 'ok' | {'error', _}. unbind_q_from_exchange(Queue, Routing, Exchange) -> QU = #'queue.unbind'{ queue = Queue @@ -804,11 +781,7 @@ unbind_q_from_exchange(Queue, Routing, Exchange) -> ,routing_key = Routing ,arguments = [] }, - - ?AMQP_DEBUG andalso lager:debug("unbinding queue ~s to ~s with key ~s", [Queue, Exchange, Routing]), - - {ok, C} = amqp_mgr:my_channel(), - amqp_channel:call(C, QU). + wh_amqp_mgr:consume(QU). %%------------------------------------------------------------------------------ %% @public @@ -817,10 +790,11 @@ unbind_q_from_exchange(Queue, Routing, Exchange) -> %% @end %%------------------------------------------------------------------------------ %% create a consumer for a Queue --spec basic_consume/1 :: (ne_binary()) -> 'ok' | {'error', atom()}. --spec basic_consume/2 :: (ne_binary(), proplist()) -> 'ok' | {'error', atom()}. +-spec basic_consume/1 :: (ne_binary()) -> 'ok' | {'error', _}. +-spec basic_consume/2 :: (ne_binary(), proplist()) -> 'ok' | {'error', _}. basic_consume(Queue) -> basic_consume(Queue, []). + basic_consume(Queue, Options) -> BC = #'basic.consume'{ queue = Queue @@ -830,16 +804,7 @@ basic_consume(Queue, Options) -> ,exclusive = props:get_value(exclusive, Options, true) ,nowait = props:get_value(nowait, Options, false) }, - - ?AMQP_DEBUG andalso lager:debug("trying to consume from queue(~p) ~s", [Options, Queue]), - {ok, C} = amqp_mgr:my_channel(), - - case amqp_channel:subscribe(C, BC, self()) of - #'basic.consume_ok'{consumer_tag=Tag} -> - amqp_mgr:update_my_tag(Tag), - ok; - E -> E - end. + wh_amqp_mgr:consume(BC). %%------------------------------------------------------------------------------ %% @public @@ -848,12 +813,9 @@ basic_consume(Queue, Options) -> %% but it does mean the server will not send any more messages for that consumer. %% @end %%------------------------------------------------------------------------------ --spec basic_cancel/1 :: (ne_binary()) -> 'ok'. -basic_cancel(Queue) -> - ?AMQP_DEBUG andalso lager:debug("cancel consume for queue ~s", [Queue]), - {ok, C} = amqp_mgr:my_channel(), - {ok, Tag} = amqp_mgr:fetch_my_tag(), - amqp_channel:cast(C, #'basic.cancel'{consumer_tag = Tag}, self()). +-spec basic_cancel/0 :: () -> 'ok'. +basic_cancel() -> + wh_amqp_mgr:consume(#'basic.cancel'{}). %%------------------------------------------------------------------------------ %% @public @@ -896,9 +858,7 @@ is_json(#'P_basic'{content_type=CT}) -> basic_ack(#'basic.deliver'{delivery_tag=DTag}) -> basic_ack(DTag); basic_ack(DTag) -> - ?AMQP_DEBUG andalso lager:debug("basic ack of ~b", [DTag]), - {ok, C} = amqp_mgr:my_channel(), - amqp_channel:call(C, #'basic.ack'{delivery_tag=DTag}). + wh_amqp_mgr:consume(#'basic.ack'{delivery_tag=DTag}). %%------------------------------------------------------------------------------ %% @public @@ -911,9 +871,7 @@ basic_ack(DTag) -> basic_nack(#'basic.deliver'{delivery_tag=DTag}) -> basic_nack(DTag); basic_nack(DTag) -> - ?AMQP_DEBUG andalso lager:debug("basic nack of ~b", [DTag]), - {ok, C} = amqp_mgr:my_channel(), - amqp_channel:call(C, #'basic.nack'{delivery_tag=DTag}). + wh_amqp_mgr:consume(#'basic.nack'{delivery_tag=DTag}). %%------------------------------------------------------------------------------ %% @public @@ -923,7 +881,7 @@ basic_nack(DTag) -> %%------------------------------------------------------------------------------ -spec is_host_available/0 :: () -> boolean(). is_host_available() -> - amqp_mgr:is_available(). + wh_amqp_mgr:is_available(). %%------------------------------------------------------------------------------ %% @public @@ -933,9 +891,7 @@ is_host_available() -> %%------------------------------------------------------------------------------ -spec basic_qos/1 :: (non_neg_integer()) -> 'ok'. basic_qos(PreFetch) when is_integer(PreFetch) -> - ?AMQP_DEBUG andalso lager:debug("set basic qos prefetch to ~p", [PreFetch]), - {ok, C} = amqp_mgr:my_channel(), - amqp_channel:call(C, #'basic.qos'{prefetch_count = PreFetch}). + wh_amqp_mgr:consume(#'basic.qos'{prefetch_count = PreFetch}). %%------------------------------------------------------------------------------ %% @public @@ -946,8 +902,7 @@ basic_qos(PreFetch) when is_integer(PreFetch) -> %%------------------------------------------------------------------------------ -spec register_return_handler/0 :: () -> 'ok'. register_return_handler() -> - ?AMQP_DEBUG andalso lager:debug("registering return handler", []), - amqp_mgr:register_return_handler(). + wh_amqp_mgr:register_return_handler(). %%------------------------------------------------------------------------------ %% @public diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_bootstrap.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_bootstrap.erl new file mode 100644 index 00000000000..74d3e2b181e --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_bootstrap.erl @@ -0,0 +1,158 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2012, VoIP INC +%%% @doc +%%% Karls Hackity Hack.... +%%% We want to block during startup until we have a AMQP connection +%%% but due to the way wh_amqp_mgr is structured we cant block in +%%% init there. So this module will bootstrap wh_amqp_mgr +%%% and block until a connection becomes available, after that it +%%% removes itself.... +%%% @end +%%% @contributors +%%%------------------------------------------------------------------- +-module(wh_amqp_bootstrap). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([init/1 + ,handle_call/3 + ,handle_cast/2 + ,handle_info/2 + ,terminate/2 + ,code_change/3 + ]). + +-include("amqp_util.hrl"). + +-define(SERVER, ?MODULE). +-define(STARTUP_FILE, [code:lib_dir(whistle_amqp, priv), "/startup.config"]). + +-record(state, {}). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the server +%% +%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @end +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +init([]) -> + put(callid, ?LOG_SYSTEM_ID), + Init = get_config(), + UseFederation = props:get_value(use_federation, Init, false), + URIs = case props:get_value(amqp_uri, Init, ?DEFAULT_AMQP_URI) of + U when is_list(U) -> [U]; + URI -> [URI] + end, + _ = [gen_server:cast(wh_amqp_mgr, {add_broker, URI, UseFederation}) || URI <- URIs], + lager:info("waiting for AMQP connection...", []), + wh_amqp_mgr:wait_for_available_host(), + {ok, #state{}, 100}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call(_Request, _From, State) -> + {reply, {error, not_implemented}, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info(timeout, State) -> + _ = wh_amqp_sup:stop_bootstrap(), + {noreply, State}; +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + lager:debug("amqp bootstrap terminating: ~p", [_Reason]). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec get_config/0 :: () -> proplist(). +get_config() -> + case file:consult(?STARTUP_FILE) of + {ok, Prop} -> + lager:info("loaded amqp manager configuration from '~s'", [?STARTUP_FILE]), + Prop; + E -> + lager:debug("unable to load amqp manager configuration from '~s': ~p", [?STARTUP_FILE, E]), + [] + end. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_broker.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_broker.erl new file mode 100644 index 00000000000..73edbd0a3b2 --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_broker.erl @@ -0,0 +1,65 @@ +%%%============================================================================ +%%% @copyright (C) 2011-2012 VoIP Inc +%%% @doc +%%% +%%% @end +%%% @contributors +%%% Karl Anderson +%%%============================================================================ +-module(wh_amqp_broker). + +-include("amqp_util.hrl"). + +-export([new/0]). +-export([name/1]). +-export([uri/1, set_uri/2]). +-export([use_federation/1, set_use_federation/2]). +-export([params/1]). +-export([is_available/1, set_is_available/2]). + +-record(amqp_broker, {uri :: 'undefined' | string() + ,params :: 'undefined' | #'amqp_params_direct'{} | #'amqp_params_network'{} + ,use_federation = false :: boolean() + ,is_available = false :: boolean() + }). + +-opaque broker() :: #amqp_broker{}. +-export_type([broker/0]). + +-spec new/0 :: () -> amqp_broker:broker(). +new() -> + #amqp_broker{}. + +-spec name/1 :: (amqp_broker:broker()) -> 'undefined' | atom(). +name(#amqp_broker{uri=URI}) -> + wh_util:to_atom(URI, true). + +-spec uri/1 :: (amqp_broker:broker()) -> 'undefined' | atom(). +uri(#amqp_broker{uri=URI}) -> + URI. + +-spec set_uri/2 :: (atom() | string() | ne_binary(), amqp_broker:broker()) -> amqp_broker:broker(). +set_uri(URI, Broker) -> + U = wh_util:to_list(URI), + {ok, Params} = amqp_uri:parse(U), + Broker#amqp_broker{uri=U, params=Params}. + +-spec use_federation/1 :: (amqp_broker:broker()) -> boolean(). +use_federation(#amqp_broker{use_federation=UseFederation}) -> + UseFederation. + +-spec set_use_federation/2 :: (atom() | string() | ne_binary(), amqp_broker:broker()) -> amqp_broker:broker(). +set_use_federation(UseFederation, Broker) -> + Broker#amqp_broker{use_federation=wh_util:is_true(UseFederation)}. + +-spec params/1 :: (amqp_broker:broker()) -> 'undefined' | #'amqp_params_direct'{} | #'amqp_params_network'{}. +params(#amqp_broker{params=Params}) -> + Params. + +-spec is_available/1 :: (amqp_broker:broker()) -> boolean(). +is_available(#amqp_broker{is_available=IsAvailable}) -> + IsAvailable. + +-spec set_is_available/2 :: (atom() | string() | ne_binary(), amqp_broker:broker()) -> amqp_broker:broker(). +set_is_available(IsAvailable, Broker) -> + Broker#amqp_broker{is_available=wh_util:is_true(IsAvailable)}. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_connection.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_connection.erl new file mode 100644 index 00000000000..0b5f4f1a97c --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_connection.erl @@ -0,0 +1,543 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2011-2012, VoIP INC +%%% @doc +%%% Handle a host's connection/channels +%%% @end +%%% @contributions +%%% James Aimonetti +%%% Karl Anderson +%%%------------------------------------------------------------------- +-module(wh_amqp_connection). + +-behaviour(gen_server). + +-export([start_link/1]). +-export([publish/3]). +-export([consume/2]). +-export([misc_req/2]). +-export([my_channel/1]). +-export([update_my_tag/2]). +-export([fetch_my_tag/1]). +-export([use_federation/1]). +-export([stop/1]). +-export([init/1 + ,handle_call/3 + ,handle_cast/2 + ,handle_info/2 + ,terminate/2 + ,code_change/3 + ]). + +-include("amqp_util.hrl"). + +-define(SERVER, ?MODULE). +-define(START_TIMEOUT, 500). +-define(MAX_TIMEOUT, 5000). +-define(KNOWN_EXCHANGES, [{?EXCHANGE_TARGETED, ?TYPE_TARGETED} + ,{?EXCHANGE_CALLCTL, ?TYPE_CALLCTL} + ,{?EXCHANGE_CALLEVT, ?TYPE_CALLEVT} + ,{?EXCHANGE_CALLMGR, ?TYPE_CALLMGR} + ,{?EXCHANGE_MONITOR, ?TYPE_MONITOR} + ,{?EXCHANGE_RESOURCE, ?TYPE_RESOURCE} + ,{?EXCHANGE_CONFIGURATION, ?TYPE_CONFIGURATION} + ,{?EXCHANGE_CONFERENCE, ?TYPE_CONFERENCE} + ,{?EXCHANGE_WHAPPS, ?TYPE_WHAPPS} + ,{?EXCHANGE_SYSCONF, ?TYPE_SYSCONF} + ]). + +%% Channel, ChannelRef +%% Channel, ChannelRef, Tag, FromRef +-type channel_data() :: {pid(), reference()}. +-type consumer_data() :: {pid(), reference(), binary(), reference()}. + +-type consume_records() :: #'queue.declare'{} | #'queue.bind'{} | #'queue.unbind'{} | #'queue.delete'{} | + #'basic.consume'{} | #'basic.cancel'{} | #'basic.ack'{} | #'basic.nack'{} | + #'basic.qos'{}. +-type misc_records() :: #'exchange.declare'{}. + + +-export_type([consume_records/0, misc_records/0]). + +-record(state, {connection = 'undefined' :: 'undefined' | {pid(), reference()} + ,publish_channel = 'undefined' :: 'undefined' | channel_data() + ,misc_channel = 'undefined' :: 'undefined' | channel_data() + ,consumers = dict:new() :: dict() + ,broker = 'undefined' :: 'undefined' | wh_amqp_broker:broker() + }). + +%%%=================================================================== +%%% API +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @doc +%% Starts the server +%% +%% @spec start_link() -> {ok, Pid} | ignore | {error, Error} +%% @end +%%-------------------------------------------------------------------- +start_link(Broker) -> + Name = wh_amqp_broker:name(Broker), + gen_server:start_link({local, Name}, ?MODULE, [Broker], []). + +-spec publish/3 :: (atom(), #'basic.publish'{}, ne_binary() | iolist()) -> 'ok' | {'error', _}. +publish(Srv, #'basic.publish'{exchange=_Exchange, routing_key=_RK}=BasicPub, AmqpMsg) -> + FindChannel = [fun(_) -> my_channel(Srv, false) end + ,fun({error, _}) -> gen_server:call(Srv, publish_channel); + ({ok, _}=OK) -> OK + end + ], + case lists:foldl(fun(F, C) -> F(C) end, {error, no_channel}, FindChannel) of + {error, _}=E -> E; + {ok, Channel} -> + lager:debug("publish to exchange '~s' with routing key '~s' via channel ~p", [_Exchange, _RK, Channel]), + amqp_channel:call(Channel, BasicPub, AmqpMsg), + ok + end. + +-spec consume/2 :: (atom(), consume_records()) -> 'ok' | {'ok', ne_binary()} | {'error', _}. +consume(Srv, #'basic.consume'{consumer_tag=CTag}=BasicConsume) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case fetch_my_tag(Srv) of + {ok, CTag} -> ok; + _Else -> + try_to_subscribe(Srv, Channel, BasicConsume) + end + end; +consume(Srv, #'basic.cancel'{}=BasicCancel) -> + case fetch_my_tag(Srv) of + {error, _}=E -> E; + {ok, Tag} -> + {ok, Channel} = my_channel(Srv), + amqp_channel:call(Channel, BasicCancel#'basic.cancel'{consumer_tag=Tag}), + ok + end; +consume(Srv, #'queue.bind'{}=QueueBind) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case amqp_channel:call(Channel, QueueBind) of + #'queue.bind_ok'{} -> ok; + {error, _}=E -> E; + Err -> {error, Err} + end + end; +consume(Srv, #'queue.unbind'{}=QueueUnbind) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case amqp_channel:call(Channel, QueueUnbind) of + #'queue.unbind_ok'{} -> ok; + {error, _}=E -> E; + Err -> {error, Err} + end + end; +consume(Srv, #'queue.declare'{}=QueueDeclare) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case amqp_channel:call(Channel, QueueDeclare) of + #'queue.declare_ok'{queue=Q} -> {ok, Q}; + {error, _}=E -> E; + Err -> {error, Err} + end + end; +consume(Srv, #'queue.delete'{}=QueueDelete) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case amqp_channel:call(Channel, QueueDelete) of + #'queue.delete_ok'{} -> ok; + {error, _}=E -> E; + Err -> {error, Err} + end + end; +consume(Srv, #'basic.qos'{}=BasicQos) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + case amqp_channel:call(Channel, BasicQos) of + #'basic.qos_ok'{} -> ok; + {error, _}=E -> E; + Err -> {error, Err} + end + end; +consume(Srv, #'basic.ack'{}=BasicAck) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + amqp_channel:cast(Channel, BasicAck), + ok + end; +consume(Srv, #'basic.nack'{}=BasicAck) -> + case my_channel(Srv) of + {error, _}=E -> E; + {ok, Channel} -> + amqp_channel:cast(Channel, BasicAck), + ok + end. + +-spec misc_req/2 :: (atom(), misc_records()) -> 'ok'. +misc_req(Srv, #'exchange.declare'{exchange=_Ex, type=_Ty}=ExchangeDeclare) -> + {ok, Channel} = gen_server:call(Srv, misc_channel), + lager:debug("attempting to create new ~s exchange '~s' via channel ~p", [_Ty, _Ex, Channel]), + UseFederation = use_federation(Srv), + case amqp_channel:call(Channel, exchange_declare(ExchangeDeclare, UseFederation)) of + #'exchange.declare_ok'{} -> ok; + {error, _}=E -> E; + Err -> {error, Err} + end. + +-spec my_channel/1 :: (atom()) -> {'ok', pid()} | + {'error', term()}. +-spec my_channel/2 :: (atom(), boolean()) -> {'ok', pid()} | + {'error', term()}. +my_channel(Srv) -> + my_channel(Srv, true). + +my_channel(Srv, Create) -> + gen_server:call(Srv, {my_channel, self(), Create}). + +-spec update_my_tag/2 :: (atom(), ne_binary()) -> 'ok'. +update_my_tag(Srv, Tag) -> + gen_server:cast(Srv, {update_my_tag, self(), Tag}). + +-spec fetch_my_tag/1 :: (atom()) -> {'ok', binary()} | + {'error', 'not_consuming'}. +fetch_my_tag(Srv) -> + gen_server:call(Srv, {fetch_my_tag, self()}). + +-spec use_federation/1 :: (atom()) -> boolean(). +use_federation(Srv) -> + gen_server:call(Srv, use_federation). + +-spec stop/1 :: (pid()) -> 'ok'. +stop(Srv) -> + case erlang:is_process_alive(Srv) of + false -> ok; + true -> gen_server:cast(Srv, stop) + end. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Initializes the server +%% +%% @spec init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% @end +%%-------------------------------------------------------------------- +init([Broker]) -> + process_flag(trap_exit, true), + put(callid, ?LOG_SYSTEM_ID), + self() ! {connect, ?START_TIMEOUT}, + {ok, #state{broker=Broker}}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling call messages +%% +%% @spec handle_call(Request, From, State) -> +%% {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_call(use_federation, _, #state{broker=Broker}=State) -> + %% If we are diconnected dont pay attention to requests + {reply, wh_amqp_broker:use_federation(Broker), State}; +handle_call(_, _, #state{connection=undefined}=State) -> + %% If we are diconnected dont pay attention to requests + {reply, {error, amqp_down}, State}; +handle_call(publish_channel, _, #state{publish_channel={C,_}}=State) -> + {reply, {ok, C}, State}; +handle_call(misc_channel, _, #state{misc_channel={C,_}}=State) -> + {reply, {ok, C}, State}; +handle_call({my_channel, OwnerPid, Create}, _, #state{connection=Conn, consumers=Consumers}=State) -> + case dict:find(OwnerPid, Consumers) of + error when not Create-> + {reply, {error, not_found}, State}; + error -> + case start_channel(Conn) of + {C,R} when is_pid(C) andalso is_reference(R) -> % channel, channel ref + lager:debug("started new AMQP channel ~p for process ~p", [C, OwnerPid]), + FromRef = erlang:monitor(process, OwnerPid), + amqp_selective_consumer:register_default_consumer(C, self()), + {reply + ,{ok, C} + ,State#state{consumers=dict:store(OwnerPid, {C,R,<<>>,FromRef}, Consumers)} + ,hibernate + }; + closing -> + lager:debug("failed to start channel for ~p: closing", [OwnerPid]), + {reply, {error, closing}, State}; + {error, _}=E -> + lager:debug("failed to start new channel for ~p: ~p", [OwnerPid, E]), + {reply, E, State} + end; + {ok, {C,_,_,_}} -> + {reply, {ok, C}, State} + end; +handle_call({fetch_my_tag, OwnerPid}, _, #state{consumers=Consumers}=State) -> + case dict:find(OwnerPid, Consumers) of + error -> {reply, {error, not_consuming}, State}; + {ok, {_C, _R, T, _FromRef}} -> {reply, {ok, T}, State} + end; +handle_call(stop, _, State) -> + {stop, normal, ok, State}; +handle_call(_Msg, _From, State) -> + {reply, {error, not_implemented}, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling cast messages +%% +%% @spec handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_cast(_, #state{connection=undefined}=State) -> + %% If we are diconnected dont pay attention to requests + {noreply, State}; +handle_cast({update_my_tag, FromPid, Tag}, #state{consumers=Consumers}=State) -> + case dict:find(FromPid, Consumers) of + error -> {noreply, State}; + {ok, {C, R, _T, FromRef}} -> + lager:debug("updating tag for ~p from ~p to ~p", [FromPid, _T, Tag]), + {noreply, State#state{consumers=dict:store(FromPid, {C, R, Tag, FromRef}, Consumers)}, hibernate} + end; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Handling all non call/cast messages +%% +%% @spec handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% @end +%%-------------------------------------------------------------------- +handle_info({'DOWN', Ref, process, ConnPid, Reason}, #state{connection={ConnPid, Ref}, consumers=Consumers, broker=Broker}=State) -> + lager:info("connection to the AMQP broker died: ~p", [Reason]), + Name = wh_amqp_broker:name(Broker), + gen_server:cast(wh_amqp_mgr, {broker_unavailable, Name}), + notify_consumers({amqp_channel_event, Reason}, Consumers), + self() ! {connect, ?START_TIMEOUT}, + {noreply, State#state{connection=undefined}}; +handle_info({'DOWN', Ref, process, _Pid, _Reason}, State) -> + lager:debug("recieved notification monitored process ~p died ~p, searching for reference", [_Pid, _Reason]), + erlang:demonitor(Ref, [flush]), + {noreply, remove_ref(Ref, State), hibernate}; +handle_info({#'basic.return'{}, #amqp_msg{}}=ReturnMsg, State) -> + wh_amqp_mgr:notify_return_handlers(ReturnMsg), + {noreply, State}; +handle_info({connect, Timeout}, #state{broker=Broker}=State) -> + Params = wh_amqp_broker:params(Broker), + Name = wh_amqp_broker:name(Broker), + case amqp_connection:start(Params) of + {error, _Reason} -> + lager:debug("failed to connect to AMQP broker '~s' will retry in ~p: ~p", [Name, Timeout, _Reason]), + _Ref = erlang:send_after(Timeout, self(), {connect, next_timeout(Timeout)}), + {noreply, State#state{connection=undefined}}; + {ok, Connection} -> + Ref = erlang:monitor(process, Connection), + case start_channel(Connection) of + {Channel, _} = PubChan when is_pid(Channel) -> + amqp_channel:register_return_handler(Channel, self()), + lager:info("connected to AMQP broker '~s'", [Name]), + gen_server:cast(wh_amqp_mgr, {broker_available, Name}), + {noreply, #state{connection = {Connection, Ref} + ,publish_channel = PubChan + ,misc_channel = start_channel(Connection) + ,consumers = dict:new() + ,broker = Broker + }, hibernate}; + {error, _Cause} -> + lager:debug("unable to initialize publish channel on AMQP broker '~s' will retry in ~p: ~p", [Name, Timeout, _Cause]), + erlang:demonitor(Ref, [flush]), + _Ref = erlang:send_after(Timeout, self(), {connect, next_timeout(Timeout)}), + {noreply, State#state{connection=undefined}} + end + end; +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_server terminates +%% with Reason. The return value is ignored. +%% +%% @spec terminate(Reason, State) -> void() +%% @end +%%-------------------------------------------------------------------- +-spec terminate/2 :: (term(), #state{}) -> 'ok'. +terminate(_Reason, {_H, _Conn, _UseF}) -> + lager:debug("amqp host failed to startup: ~p", [_Reason]), + lager:debug("params: ~s on conn ~p, use federation: ~s", [_H, _Conn, _UseF]); +terminate(_Reason, #state{consumers=Consumers, broker=Broker}) -> + Name = wh_amqp_broker:name(Broker), + _ = spawn(fun() -> + put(callid, ?LOG_SYSTEM_ID), + notify_consumers({amqp_channel_event, terminated}, Consumers) + end), + lager:debug("connection to AMQP broker '~s' terminated: ~p", [Name, _Reason]). + +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Convert process state when code is changed +%% +%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} +%% @end +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +-spec start_channel/1 :: ('undefined' | {pid(), reference()} | pid()) -> channel_data() | {'error', 'no_connection'} | 'closing'. +start_channel(undefined) -> + {error, no_connection}; +start_channel({Connection, _}) -> + start_channel(Connection); +start_channel(Connection) when is_pid(Connection) -> + case erlang:is_process_alive(Connection) + andalso amqp_connection:open_channel(Connection) of + {ok, Channel} -> + ChanMRef = erlang:monitor(process, Channel), + {Channel, ChanMRef}; + false -> {error, no_connection}; + E -> E + end. + +-spec remove_ref/2 :: (reference(), #state{}) -> #state{}. +remove_ref(Ref, #state{connection=undefined, publish_channel={C,Ref}}=State) -> + lager:debug("reference was for publish channel ~p due to AMQP disconnect", [C]), + State#state{publish_channel=undefined}; +remove_ref(Ref, #state{connection=undefined, misc_channel={C,Ref}}=State) -> + lager:debug("reference was for misc channel ~p due to AMQP disconnect", [C]), + State#state{publish_channel=undefined}; +remove_ref(Ref, #state{connection={Conn, _}, publish_channel={C,Ref}}=State) -> + lager:debug("reference was for publish channel ~p, restarting", [C]), + State#state{publish_channel=start_channel(Conn)}; +remove_ref(Ref, #state{connection={Conn, _}, misc_channel={C,Ref}}=State) -> + lager:debug("reference was for misc channel ~p, restarting", [C]), + State#state{misc_channel=start_channel(Conn)}; +remove_ref(Ref, #state{connection=Connection, consumers=Cs}=State) -> + State#state{consumers=dict:fold(fun(K, V, Acc) -> + clean_consumers(K, V, Acc, Ref, Connection) + end, Cs, Cs)}. + +-spec notify_consumers/2 :: ({'amqp_channel_event', atom()}, dict()) -> 'ok'. +notify_consumers(Msg, Dict) -> + lists:foreach(fun({Pid,_}) -> Pid ! Msg end, dict:to_list(Dict)). + +-spec clean_consumers/5 :: (pid(), consumer_data(), dict(), reference(), 'undefined' | pid()) -> dict(). +%% Channel died while disconnected +clean_consumers(ConsumerPid, {C, ChannelRef, _, ConsumerRef}, Dict, ChannelRef, undefined) -> + lager:debug("reference was for channel ~p for ~p due to AMQP disconnect", [C, ConsumerPid]), + erlang:demonitor(ChannelRef, [flush]), + erlang:demonitor(ConsumerRef, [flush]), + ConsumerPid ! {amqp_channel_event, lost_connection}, + dict:erase(ConsumerPid, Dict); + +%% Channel died while connected +clean_consumers(ConsumerPid, {C, ChannelRef, _, ConsumerRef}=Consumer, Dict, ChannelRef, {Conn, _}) -> + case is_process_alive(Conn) of + false -> clean_consumers(ConsumerPid, Consumer, Dict, ChannelRef, undefined); + true -> + lager:debug("reference was for channel ~p for ~p, restarting", [C, ConsumerPid]), + erlang:demonitor(ChannelRef, [flush]), + erlang:is_process_alive(C) andalso amqp_channel:close(C), + case start_channel(Conn) of + {CNew, RefNew} when is_pid(CNew) andalso is_reference(RefNew) -> + lager:debug("new channel started for ~p", [ConsumerPid]), + ConsumerPid ! {amqp_channel_event, restarted}, + dict:store(ConsumerPid, {CNew, RefNew, <<>>, ConsumerRef}, Dict); + {error, no_connection} -> + lager:debug("no connection available"), + ConsumerPid ! {amqp_channel_event, no_connection}, + dict:erase(ConsumerPid, Dict); + closing -> + lager:debug("closing, no connection"), + ConsumerPid ! {amqp_channel_event, closing}, + dict:erase(ConsumerPid, Dict) + end + end; + +%% Consumer died +clean_consumers(ConsumerPid, {C, ChannelRef, _, ConsumerRef}, Dict, ConsumerRef, _) -> + lager:debug("reference was for consumer ~p, removing channel ~p", [ConsumerPid, C]), + erlang:demonitor(ChannelRef, [flush]), + erlang:demonitor(ConsumerRef, [flush]), + erlang:is_process_alive(C) andalso amqp_channel:close(C), + dict:erase(ConsumerPid, Dict); + +%% Generic channel cleanup when ConsumerPid isn't alive +clean_consumers(ConsumerPid, {C, ChannelRef, _, ConsumerRef}, Dict, _, _) -> + case erlang:is_process_alive(ConsumerPid) of + true -> Dict; + false -> + lager:debug("reference was a consumer ~p that shutdown, removing channel ~p", [ConsumerPid, C]), + erlang:demonitor(ChannelRef, [flush]), + erlang:demonitor(ConsumerRef, [flush]), + erlang:is_process_alive(C) andalso amqp_channel:close(C), + dict:erase(ConsumerPid, Dict) + end; + +%% Simple catchall +clean_consumers(_, _, Dict,_,_) -> + Dict. + +-spec try_to_subscribe/3 :: (atom(), pid(), #'basic.consume'{}) -> 'ok' | {'error', term()}. +try_to_subscribe(Srv, Channel, BasicConsume) -> + try amqp_channel:call(Channel, BasicConsume) of + #'basic.consume_ok'{consumer_tag=Tag} -> + update_my_tag(Srv, Tag), + ok; + Other -> {error, Other} + catch + _E:R -> {error, R} + end. + +-spec next_timeout/1 :: (pos_integer()) -> ?START_TIMEOUT..?MAX_TIMEOUT. +next_timeout(?MAX_TIMEOUT=Timeout) -> + Timeout; +next_timeout(Timeout) when Timeout*2 > ?MAX_TIMEOUT -> + ?MAX_TIMEOUT; +next_timeout(Timeout) when Timeout < ?START_TIMEOUT -> + ?START_TIMEOUT; +next_timeout(Timeout) -> + Timeout * 2. + +exchange_declare(ED, false) -> + ED; +exchange_declare(#'exchange.declare'{type=Type}=ED, true) -> +% -record('exchange.declare', {ticket = 0, exchange, type = <<"direct">>, passive = false, durable = false, auto_delete = false, internal = false, nowait = false, arguments = []}). + ED1 = ED#'exchange.declare'{ + type = <<"x-federation">> + ,arguments = [{<<"type">>, longstr, Type} + ,{<<"upstream-set">>, longstr, ?RABBITMQ_UPSTREAM_SET} + ] + }, + ED1. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_connection_sup.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_connection_sup.erl new file mode 100644 index 00000000000..2e401754fd7 --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_connection_sup.erl @@ -0,0 +1,70 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2012, VoIP, INC +%%% @doc +%%% +%%% @end +%%% @contributors +%%%------------------------------------------------------------------- +-module(wh_amqp_connection_sup). + +-behaviour(supervisor). + +-include_lib("whistle/include/wh_types.hrl"). + +-export([start_link/0]). +-export([add/1]). +-export([remove/1]). +-export([init/1]). + +-define(SERVER, ?MODULE). + +-define(CHILD(Name, Type, Args), fun(N, T, A) -> {N, {wh_amqp_connection, start_link, [A]}, permanent, 5000, T, [N]} end(Name, Type, Args)). + +%% =================================================================== +%% API functions +%% =================================================================== + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Starts the supervisor +%% @end +%%-------------------------------------------------------------------- +-spec start_link/0 :: () -> startlink_ret(). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +-spec add/1 :: (wh_amqp_broker:broker()) -> {'error', _} | {'ok','undefined' | pid()} | {'ok','undefined' | pid(), _}. +add(Broker) -> + Name = wh_amqp_broker:name(Broker), + supervisor:start_child(?SERVER, ?CHILD(Name, worker, Broker)). + +-spec remove/1 :: (wh_amqp_broker:broker()) -> 'ok' | {'error', 'running' | 'not_found' | 'simple_one_for_one'}. +remove(Broker) -> + Name = wh_amqp_broker:name(Broker), + _ = supervisor:terminate_child(?SERVER, Name), + supervisor:delete_child(?SERVER, Name). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +%% @end +%%-------------------------------------------------------------------- +-spec init([]) -> sup_init_ret(). +init([]) -> + RestartStrategy = one_for_one, + MaxRestarts = 5, + MaxSecondsBetweenRestarts = 10, + + SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, + Children = [], + + {ok, {SupFlags, Children}}. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_mgr.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_mgr.erl new file mode 100644 index 00000000000..bb16f4f01c6 --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_mgr.erl @@ -0,0 +1,242 @@ +%%%------------------------------------------------------------------- +%%% File : amqp_mgr.erl +%%% Authors : K Anderson +%%% : James Aimonetti +%%% Description : The AMQP connection manager. +%%% +%%% Created : March 24 2010 +%%%------------------------------------------------------------------- +-module(wh_amqp_mgr). + +-behaviour(gen_server). + +-export([start_link/0]). +-export([publish/2]). +-export([consume/1]). +-export([misc_req/1]). +-export([register_return_handler/0]). +-export([is_available/0]). +-export([wait_for_available_host/0]). +-export([notify_return_handlers/1]). +-export([init/1 + ,handle_call/3 + ,handle_cast/2 + ,handle_info/2 + ,terminate/2 + ,code_change/3 + ]). + +-include("amqp_util.hrl"). + +-define(SERVER, ?MODULE). +-define(START_TIMEOUT, 500). +-define(MAX_TIMEOUT, 5000). + +-record(state, {brokers=dict:new() :: dict() + ,available_brokers=dict:new() :: dict() + ,strategy=priority :: 'priority' + ,return_handlers = dict:new() :: dict() %% ref, pid() - list of PIDs that are interested in returned messages + }). + +%%==================================================================== +%% API +%%==================================================================== +%%-------------------------------------------------------------------- +%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} +%% Description: Starts the server +%%-------------------------------------------------------------------- +start_link() -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). + +-spec get_connection/0 :: () -> {'ok', atom()} | {'error', 'amqp_down'}. +get_connection() -> + gen_server:call(?SERVER, get_connection). + +-spec is_available/0 :: () -> boolean(). +is_available() -> + gen_server:call(?SERVER, is_available). + +-spec wait_for_available_host/0 :: () -> 'ok'. +wait_for_available_host() -> + case is_available() of + true -> ok; + false -> + timer:sleep(random:uniform(1000) + 100), + wait_for_available_host() + end. + +-spec consume/1 :: (wh_amqp_connection:consume_records()) -> 'ok' | {'ok', ne_binary()} | {'error', _}. +consume(BC) -> + case get_connection() of + {ok, Connection} -> wh_amqp_connection:consume(Connection, BC); + {error, _}=E -> E + end. + +-spec publish/2 :: (#'basic.publish'{}, #'amqp_msg'{}) -> 'ok'. +publish(BP, AM) -> + case get_connection() of + {ok, Connection} -> wh_amqp_connection:publish(Connection, BP, AM); + {error, _}=E -> E + end. + +-spec misc_req/1 :: (wh_amqp_connection:misc_records()) -> 'ok' | {'error', _}. +misc_req(Req) -> + case get_connection() of + {ok, Connection} -> wh_amqp_connection:misc_req(Connection, Req); + {error, _}=E -> E + end. + +-spec register_return_handler/0 :: () -> 'ok'. +register_return_handler() -> + gen_server:cast(?SERVER, {register_return_handler, self()}). + +-spec notify_return_handlers/1 :: ({#'basic.return'{}, #amqp_msg{}}) -> pid(). +notify_return_handlers(ReturnMsg) -> + spawn(fun() -> + put(callid, ?LOG_SYSTEM_ID), + RHDict = gen_server:call(?SERVER, get_return_handler_dict), + lager:debug("recieved notification a message couldnt be delivered, forwarding to registered return handlers"), + dict:map(fun(_, Pid) -> Pid ! ReturnMsg end, RHDict) + end). + +%%==================================================================== +%% gen_server callbacks +%%==================================================================== + +%%-------------------------------------------------------------------- +%% Function: init(Args) -> {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%% Description: Initiates the server +%%-------------------------------------------------------------------- +-spec init/1 :: ([]) -> {'ok', #state{}}. +init([]) -> + {ok, #state{}}. + +%%-------------------------------------------------------------------- +%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | +%% {stop, Reason, State} +%% Description: Handling call messages +%% +%%-------------------------------------------------------------------- +handle_call(get_return_handler_dict, _, #state{return_handlers=RHDict}=State) -> + {reply, RHDict, State}; +handle_call(is_available, _, #state{available_brokers=Brokers}=State) -> + {reply, dict:size(Brokers) > 0, State}; +handle_call(get_connection, _, State) -> + case next_available_broker(State) of + {undefined, S} -> + {reply, {error, amqp_down}, S, hibernate}; + {Broker, S} -> + Name = wh_amqp_broker:name(Broker), + {reply, {ok, Name}, S, hibernate} + end; +handle_call(_, _, State) -> + {reply, {error, not_implemented}, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_cast(Msg, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast({register_return_handler, FromPid}, #state{return_handlers=RHDict}=State) -> + lager:debug("adding ~p as a return handler", [FromPid]), + {noreply, State#state{return_handlers=dict:store(erlang:monitor(process, FromPid), FromPid, RHDict)}, hibernate}; +handle_cast({add_broker, URI, UseFederation}, #state{brokers=Brokers}=State) -> + Builders = [fun(B) -> wh_amqp_broker:set_uri(URI, B) end + ,fun(B) -> wh_amqp_broker:set_use_federation(UseFederation, B) end + ], + case catch lists:foldl(fun(F, B) -> F(B) end, wh_amqp_broker:new(), Builders) of + {'EXIT', _} -> + lager:error("failed to parse AMQP broker URI '~p', dropping", [URI]), + {noreply, State}; + Broker -> + Name = wh_amqp_broker:name(Broker), + case wh_amqp_connection_sup:add(Broker) of + {ok, _} -> + _Ref = erlang:monitor(process, Name), + {noreply, State#state{brokers=dict:store(Name, Broker, Brokers)}, hibernate}; + {error, {already_started, _}} -> + _Ref = erlang:monitor(process, Name), + {noreply, State#state{brokers=dict:store(Name, Broker, Brokers)}, hibernate}; + {error, {Reason, _}} -> + lager:info("unable to start AMQP connection to '~s': ~p", [Name, Reason]), + _ = wh_amqp_connection_sup:remove(Broker), + {noreply, State} + end + end; +handle_cast({broker_unavailable, Name}, #state{available_brokers=Brokers}=State) -> + {noreply, State#state{available_brokers=dict:erase(Name, Brokers)}}; +handle_cast({broker_available, Name}, #state{brokers=Brokers, available_brokers=AvailableBrokers}=State) -> + case dict:find(Name, Brokers) of + error -> + lager:debug("received notice that unknown AMQP broker '~s' is available, ignoring", [Name]), + {noreply, State}; + {ok, Broker} -> + lager:debug("received notice that AMQP broker '~s' is available", [Name]), + {noreply, State#state{available_brokers=dict:store(Name, Broker, AvailableBrokers)}, hibernate} + end; +handle_cast(_Msg, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: handle_info(Info, State) -> {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} +%% Description: Handling all non call/cast messages +%%-------------------------------------------------------------------- +handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{return_handlers=RHDict}=State) -> + lager:debug("recieved notification monitored process ~p died ~p, searching for reference", [_Pid, _Reason]), + erlang:demonitor(Ref, [flush]), + {noreply, State#state{return_handlers=dict:erase(Ref, RHDict)}, hibernate}; +handle_info({'DOWN', Ref, process, {Name, _}, _R}, #state{available_brokers=Brokers}=State) -> + erlang:demonitor(Ref, [flush]), + lager:info("lost connection to AMQP broker '~s'", [Name]), + {noreply, State#state{available_brokers=dict:erase(Name, Brokers)}, hibernate}; +handle_info(_Info, State) -> + {noreply, State}. + +%%-------------------------------------------------------------------- +%% Function: terminate(Reason, State) -> void() +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +-spec terminate/2 :: (term(), #state{}) -> no_return(). +terminate(_Reason, _) -> + lager:debug("amqp manager terminated: ~p", [_Reason]). + +%%-------------------------------------------------------------------- +%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +-spec next_available_broker/1 :: (#state{}) -> {'undefined' | wh_amqp_broker:broker(), #state{}}. +-spec next_available_broker/3 :: ('priority', [{atom(), wh_amqp_broker:broker()},...] | [], #state{}) -> {'undefined' | wh_amqp_broker:broker(), #state{}}. + +next_available_broker(#state{strategy=Strategy, available_brokers=Brokers}=State) -> + case dict:to_list(Brokers) of + [] -> {undefined, State}; + [{_, Broker}] -> {Broker, State}; + Else -> next_available_broker(Strategy, Else, State) + end. + +next_available_broker(_, [], State) -> + {undefined, State}; +next_available_broker(priority, [{_, Broker}|Brokers], State) -> + case wh_amqp_broker:is_available(Broker) of + true -> {Broker, State}; + false -> next_available_broker(priority, Brokers, State) + end. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_params.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_params.erl deleted file mode 100644 index 73de376c3a5..00000000000 --- a/lib/whistle_amqp-1.0.0/src/wh_amqp_params.erl +++ /dev/null @@ -1,16 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author James Aimonetti <> -%%% @copyright (C) 2011, James Aimonetti -%%% @doc -%%% -%%% @end -%%% Created : 12 Nov 2011 by James Aimonetti <> -%%%------------------------------------------------------------------- --module(wh_amqp_params). - --export([host/1]). - --include("amqp_util.hrl"). - -host(#'amqp_params_network'{host=H}) -> - H. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_sup.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_sup.erl new file mode 100644 index 00000000000..9c944d0a9b8 --- /dev/null +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_sup.erl @@ -0,0 +1,62 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2012, VoIP, INC +%%% @doc +%%% +%%% @end +%%% @contributors +%%%------------------------------------------------------------------- +-module(wh_amqp_sup). + +-behaviour(supervisor). + +-include_lib("whistle/include/wh_types.hrl"). + +-export([start_link/0]). +-export([stop_bootstrap/0]). +-export([init/1]). + +-define(SERVER, ?MODULE). +-define(CHILD(Name, Type), fun(N, T) -> {N, {N, start_link, []}, permanent, 5000, T, [N]} end(Name, Type)). +-define(CHILDREN, [{wh_amqp_connection_sup, supervisor}, {wh_amqp_mgr, worker}, {wh_amqp_bootstrap, worker}]). + +%% =================================================================== +%% API functions +%% =================================================================== + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Starts the supervisor +%% @end +%%-------------------------------------------------------------------- +-spec start_link/0 :: () -> startlink_ret(). +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +-spec stop_bootstrap/0 :: () -> 'ok' | {'error', 'running' | 'not_found' | 'simple_one_for_one'}. +stop_bootstrap() -> + _ = supervisor:terminate_child(?SERVER, wh_amqp_bootstrap). + +%% =================================================================== +%% Supervisor callbacks +%% =================================================================== + +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Whenever a supervisor is started using supervisor:start_link/[2,3], +%% this function is called by the new process to find out about +%% restart strategy, maximum restart frequency and child +%% specifications. +%% @end +%%-------------------------------------------------------------------- +-spec init([]) -> sup_init_ret(). +init([]) -> + RestartStrategy = one_for_one, + MaxRestarts = 5, + MaxSecondsBetweenRestarts = 10, + + SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts}, + Children = [?CHILD(Name, Type) || {Name, Type} <- ?CHILDREN], + + {ok, {SupFlags, Children}}. diff --git a/lib/whistle_amqp-1.0.0/src/wh_amqp_worker.erl b/lib/whistle_amqp-1.0.0/src/wh_amqp_worker.erl index 93bbaf3f2d0..140e27d1f1e 100644 --- a/lib/whistle_amqp-1.0.0/src/wh_amqp_worker.erl +++ b/lib/whistle_amqp-1.0.0/src/wh_amqp_worker.erl @@ -14,11 +14,20 @@ -behaviour(gen_listener). %% API --export([start_link/1, handle_resp/2, send_request/4, new_request/5]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, handle_event/2 - ,terminate/2, code_change/3]). +-export([start_link/1]). +-export([call/4, call/5]). +-export([cast/3]). +-export([any_resp/1]). +-export([handle_resp/2]). +-export([send_request/4]). +-export([init/1 + ,handle_call/3 + ,handle_cast/2 + ,handle_info/2 + ,handle_event/2 + ,terminate/2 + ,code_change/3 + ]). -include("amqp_util.hrl"). @@ -27,21 +36,26 @@ -export_type([publish_fun/0, validate_fun/0]). +-record(state, {current_msg_id :: ne_binary() + ,client_pid :: pid() + ,client_ref :: reference() + ,client_from :: {pid(), reference()} + ,client_vfun :: fun((api_terms()) -> boolean()) + ,neg_resp :: wh_json:json_object() + ,neg_resp_count = 0 :: non_neg_integer() + ,neg_resp_threshold = 2 :: pos_integer() + ,req_timeout_ref :: reference() + ,req_start_time :: wh_now() + ,callid :: ne_binary() + }). + -define(FUDGE, 2600). --record(state, { - current_msg_id :: ne_binary() - ,client_pid :: pid() - ,client_ref :: reference() - ,client_from :: {pid(), reference()} - ,client_vfun :: fun((api_terms()) -> boolean()) - ,neg_resp :: wh_json:json_object() - ,neg_resp_count = 0 :: non_neg_integer() - ,neg_resp_threshold = 2 :: pos_integer() - ,req_timeout_ref :: reference() - ,req_start_time :: wh_now() - ,callid :: ne_binary() - }). +-define(BINDINGS, [{self, []}]). +-define(RESPONDERS, [{{?MODULE, handle_resp}, [{<<"*">>, <<"*">>}]}]). +-define(QUEUE_NAME, <<>>). +-define(QUEUE_OPTIONS, []). +-define(CONSUME_OPTIONS, []). %%%=================================================================== %%% API @@ -55,21 +69,57 @@ %% @end %%-------------------------------------------------------------------- start_link(Args) -> - gen_listener:start_link(?MODULE - ,[{bindings, [{self, []}]} - ,{responders, [{{?MODULE, handle_resp}, [{<<"*">>, <<"*">>}]}]} - ] - ,[Args] - ). - + gen_listener:start_link(?MODULE, [{bindings, ?BINDINGS} + ,{responders, ?RESPONDERS} + ,{queue_name, ?QUEUE_NAME} + ,{queue_options, ?QUEUE_OPTIONS} + ,{consume_options, ?CONSUME_OPTIONS} + ], [Args]). + +-spec call/4 :: (server_ref(), api_terms(), wh_amqp_worker:publish_fun(), wh_amqp_worker:validate_fun()) -> {'ok', wh_json:json_object()} | + {'error', _}. +-spec call/5 :: (server_ref(), api_terms(), wh_amqp_worker:publish_fun(), wh_amqp_worker:validate_fun(), pos_integer()) -> {'ok', wh_json:json_object()} | + {'error', _}. +call(Srv, Req, PubFun, VFun) -> + call(Srv, Req, PubFun, VFun, 1500). + +call(Srv, Req, PubFun, VFun, Timeout) -> + case poolboy:checkout(Srv, false, 1000) of + W when is_pid(W) -> + Prop = case wh_json:is_json_object(Req) of + true -> wh_json:to_proplist(Req); + false -> Req + end, + Reply = gen_listener:call(W, {request, Prop, PubFun, VFun, Timeout}, Timeout + ?FUDGE), + poolboy:checkin(Srv, W), + Reply; + full -> + lager:debug("failed to checkout worker: full"), + {error, pool_full} + end. + +-spec cast/3 :: (server_ref(), api_terms(), wh_amqp_worker:publish_fun()) -> 'ok' | {'error', _}. +cast(Srv, Req, PubFun) -> + case poolboy:checkout(Srv, false, 1000) of + W when is_pid(W) -> + poolboy:checkin(Srv, W), + Prop = case wh_json:is_json_object(Req) of + true -> wh_json:to_proplist(Req); + false -> Req + end, + gen_listener:cast(W, {publish, Prop, PubFun}); + full -> + lager:debug("failed to checkout worker: full"), + {error, pool_full} + end. + +-spec any_resp/1 :: (any()) -> 'true'. +any_resp(_) -> true. + +-spec handle_resp/2 :: (wh_json:json_object(), proplist()) -> 'ok'. handle_resp(JObj, Props) -> gen_listener:cast(props:get_value(server, Props), {event, wh_json:get_value(<<"Msg-ID">>, JObj), JObj}). --spec new_request/5 :: (pid(), api_terms(), publish_fun(), validate_fun(), pos_integer()) -> {'ok', wh_json:json_object()} | - {'error', _}. -new_request(Srv, ReqProp, PubFun, VFun, Timeout) -> - gen_listener:call(Srv, {request, ReqProp, PubFun, VFun, Timeout}, Timeout+?FUDGE). - -spec send_request/4 :: (ne_binary(), pid(), function(), proplist()) -> 'ok'. send_request(CallID, Self, PublishFun, ReqProp) -> put(callid, CallID), @@ -97,9 +147,7 @@ init([Args]) -> process_flag(trap_exit, true), put(callid, ?LOG_SYSTEM_ID), lager:debug("starting amqp worker"), - NegThreshold = props:get_value(neg_resp_threshold, Args, 2), - {ok, #state{neg_resp_threshold=NegThreshold}}. %%-------------------------------------------------------------------- @@ -119,27 +167,18 @@ init([Args]) -> handle_call({request, ReqProp, PublishFun, VFun, Timeout}, {ClientPid, _}=From, State) -> _ = wh_util:put_callid(ReqProp), CallID = get(callid), - - lager:debug("starting AMQP request for ~p", [ClientPid]), - Self = self(), - ClientRef = erlang:monitor(process, ClientPid), ReqRef = erlang:start_timer(Timeout, Self, req_timeout), - {ReqProp1, MsgID} = case props:get_value(<<"Msg-ID">>, ReqProp) of undefined -> M = wh_util:rand_hex_binary(8), {[{<<"Msg-ID">>, M} | ReqProp], M}; M -> {ReqProp, M} end, - + lager:debug("published request with msg id ~s for ~p", [MsgID, ClientPid]), P = proc_lib:spawn(?MODULE, send_request, [CallID, Self, PublishFun, ReqProp1]), _R = erlang:monitor(process, P), - lager:debug("pid ~p spawned (~p)", [P, _R]), - - lager:debug("published request with msg id ~s", [MsgID]), - {noreply, State#state{ client_pid = ClientPid ,client_ref = ClientRef @@ -164,6 +203,9 @@ handle_call(_Request, _From, State) -> %% {stop, Reason, State} %% @end %%-------------------------------------------------------------------- +handle_cast({publish, ReqProp, PublishFun}, State) -> + PublishFun(ReqProp), + {noreply, State}; handle_cast({set_negative_threshold, NegThreshold}, State) -> lager:debug("set negative threshold to ~p", [NegThreshold]), {noreply, State#state{neg_resp_threshold = NegThreshold}}; @@ -177,16 +219,12 @@ handle_cast({event, MsgId, JObj}, #state{current_msg_id = MsgId ,neg_resp_threshold = NegThreshold }=State) when NegCount < NegThreshold -> _ = wh_util:put_callid(JObj), - lager:debug("recv response for msg id ~s", [MsgId]), - lager:debug("response took ~b micro to return", [timer:now_diff(erlang:now(), StartTime)]), - + lager:debug("response for msg id ~s took ~b micro to return", [MsgId, timer:now_diff(erlang:now(), StartTime)]), case VFun(JObj) of true -> erlang:demonitor(ClientRef, [flush]), - gen_server:reply(From, {ok, JObj}), _ = erlang:cancel_timer(ReqRef), - put(callid, ?LOG_SYSTEM_ID), {noreply, reset(State)}; false -> @@ -213,12 +251,10 @@ handle_cast(_Msg, State) -> handle_info(timeout, #state{neg_resp=JObj, neg_resp_count=Thresh, neg_resp_threshold=Thresh ,client_ref=ClientRef, client_from=From, req_timeout_ref=ReqRef }=State) -> - lager:debug("negative resp threshold reached"), + lager:debug("negative response threshold reached, returning last negative message"), erlang:demonitor(ClientRef, [flush]), - gen_server:reply(From, {error, JObj}), _ = erlang:cancel_timer(ReqRef), - put(callid, ?LOG_SYSTEM_ID), {noreply, reset(State)}; handle_info(timeout, State) -> @@ -229,11 +265,9 @@ handle_info({'DOWN', ClientRef, process, _Pid, _Reason}, #state{current_msg_id = ,callid = CallID }=State) -> put(callid, CallID), - lager:debug("client ~p down with msg id ~s", [_Pid, _MsgID]), - + lager:debug("requestor processes ~p died while waiting for msg id ~s", [_Pid, _MsgID]), erlang:demonitor(ClientRef, [flush]), _ = erlang:cancel_timer(ReqRef), - put(callid, ?LOG_SYSTEM_ID), {noreply, reset(State)}; handle_info({timeout, ReqRef, req_timeout}, #state{current_msg_id = _MsgID @@ -244,13 +278,9 @@ handle_info({timeout, ReqRef, req_timeout}, #state{current_msg_id = _MsgID }=State) -> put(callid, CallID), lager:debug("request timeout exceeded for msg id: ~s", [_MsgID]), - erlang:demonitor(ClientRef, [flush]), - gen_server:reply(From, {error, timeout}), - _ = erlang:cancel_timer(ReqRef), - put(callid, ?LOG_SYSTEM_ID), {noreply, reset(State)}; handle_info(_Info, State) -> diff --git a/lib/whistle_amqp-1.0.0/src/whistle_amqp.erl b/lib/whistle_amqp-1.0.0/src/whistle_amqp.erl index f5b53d2cb50..7aef03fc7ca 100644 --- a/lib/whistle_amqp-1.0.0/src/whistle_amqp.erl +++ b/lib/whistle_amqp-1.0.0/src/whistle_amqp.erl @@ -1,37 +1,50 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2012, VoIP, INC +%%% @doc +%%% +%%% @end +%%% @contributors +%%%------------------------------------------------------------------- -module(whistle_amqp). --behaviour(application). --author('James Aimonetti '). +-include_lib("whistle/include/wh_types.hrl"). --export([start/2, start/0, start_link/0, stop/0, stop/1]). +-export([start_link/0, start/0]). +-export([stop/0]). -%% -start(_Type, _Args) -> - _ = start_deps(), - whistle_amqp_sup:start_link(). - -%% @spec start_link() -> {ok,Pid::pid()} -%% @doc Starts the app for inclusion in a supervisor tree +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Starts the app for inclusion in a supervisor tree +%% @end +%%-------------------------------------------------------------------- +-spec start_link/0 :: () -> startlink_ret(). start_link() -> _ = start_deps(), - whistle_amqp_sup:start_link(). + wh_amqp_sup:start_link(). -%% @spec start() -> ok -%% @doc Start the amqp server. +-spec start/0 :: () -> 'ok' | {'error', _}. start() -> _ = start_deps(), application:start(whistle_amqp, permanent). -start_deps() -> - whistle_amqp_deps:ensure(?MODULE), - ok = wh_util:ensure_started(sasl), - ok = wh_util:ensure_started(riak_err), - ok = wh_util:ensure_started(amqp_client). - -%% @spec stop() -> ok -%% @doc Stop the amqp server. +%%-------------------------------------------------------------------- +%% @public +%% @doc +%% Stop the app +%% @end +%%-------------------------------------------------------------------- +-spec stop/0 :: () -> 'ok'. stop() -> application:stop(whistle_amqp). -stop(_) -> +%%-------------------------------------------------------------------- +%% @private +%% @doc +%% Ensures that all dependencies for this app are already running +%% @end +%%-------------------------------------------------------------------- +-spec start_deps/0 :: () -> 'ok'. +start_deps() -> + _ = [wh_util:ensure_started(App) || App <- [sasl, riak_error, amqp_client]], ok. diff --git a/lib/whistle_amqp-1.0.0/src/whistle_amqp_deps.erl b/lib/whistle_amqp-1.0.0/src/whistle_amqp_deps.erl deleted file mode 100644 index b1617aeb110..00000000000 --- a/lib/whistle_amqp-1.0.0/src/whistle_amqp_deps.erl +++ /dev/null @@ -1,86 +0,0 @@ -%%% @author James Aimonetti -%%% @copyright (C) 2010, James Aimonetti -%%% @doc Ensure that the relatively-installed dependencies are on the code -%%% loading path, and locate resources relative -%%% to this application's path. -%%% -%%% @end -%%% Created : 8 Nov 2010 by James Aimonetti - --module(whistle_amqp_deps). --author('author '). - --export([ensure/0, ensure/1]). --export([get_base_dir/0, get_base_dir/1]). --export([local_path/1, local_path/2]). --export([deps_on_path/0, new_siblings/1]). - -%% @spec deps_on_path() -> [ProjNameAndVers] -%% @doc List of project dependencies on the path. -deps_on_path() -> - F = fun (X, Acc) -> - ProjDir = filename:dirname(X), - case {filename:basename(X), - filename:basename(filename:dirname(ProjDir))} of - {"ebin", "deps"} -> - [filename:basename(ProjDir) | Acc]; - _ -> - Acc - end - end, - ordsets:from_list(lists:foldl(F, [], code:get_path())). - -%% @spec new_siblings(Module) -> [Dir] -%% @doc Find new siblings paths relative to Module that aren't already on the -%% code path. -new_siblings(Module) -> - Existing = deps_on_path(), - SiblingEbin = filelib:wildcard(local_path(["deps", "*", "ebin"], Module)), - Siblings = [filename:dirname(X) || X <- SiblingEbin, - ordsets:is_element( - filename:basename(filename:dirname(X)), - Existing) =:= false], - lists:filter(fun filelib:is_dir/1, - lists:append([[filename:join([X, "ebin"]), - filename:join([X, "include"])] || - X <- Siblings])). - - -%% @spec ensure(Module) -> ok -%% @doc Ensure that all ebin and include paths for dependencies -%% of the application for Module are on the code path. -ensure(Module) -> - code:add_pathsa(new_siblings(Module)), - %% code:clash(), - ok. - -%% @spec ensure() -> ok -%% @doc Ensure that the ebin and include paths for dependencies of -%% this application are on the code path. Equivalent to -%% ensure(?Module). -ensure() -> - ensure(?MODULE). - -%% @spec get_base_dir(Module) -> string() -%% @doc Return the application directory for Module. It assumes Module is in -%% a standard OTP layout application in the ebin or src directory. -get_base_dir(Module) -> - {file, Here} = code:is_loaded(Module), - filename:dirname(filename:dirname(Here)). - -%% @spec get_base_dir() -> string() -%% @doc Return the application directory for this application. Equivalent to -%% get_base_dir(?MODULE). -get_base_dir() -> - get_base_dir(?MODULE). - -%% @spec local_path([string()], Module) -> string() -%% @doc Return an application-relative directory from Module's application. -local_path(Components, Module) -> - filename:join([get_base_dir(Module) | Components]). - -%% @spec local_path(Components) -> string() -%% @doc Return an application-relative directory for this application. -%% Equivalent to local_path(Components, ?MODULE). -local_path(Components) -> - local_path(Components, ?MODULE). diff --git a/lib/whistle_amqp-1.0.0/src/whistle_amqp_sup.erl b/lib/whistle_amqp-1.0.0/src/whistle_amqp_sup.erl deleted file mode 100644 index 7c1972e75b5..00000000000 --- a/lib/whistle_amqp-1.0.0/src/whistle_amqp_sup.erl +++ /dev/null @@ -1,33 +0,0 @@ --module(whistle_amqp_sup). - --behaviour(supervisor). - -%% API --export([start_link/0]). - -%% Supervisor callbacks --export([init/1]). - -%% Helper macro for declaring children of supervisor --define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). - -%% =================================================================== -%% API functions -%% =================================================================== - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -%% =================================================================== -%% Supervisor callbacks -%% =================================================================== - -init([]) -> - {ok, { {one_for_one, 5, 10} - ,[ - ?CHILD(amqp_host_sup, supervisor) - ,?CHILD(amqp_mgr, worker) - ] - } - }. - diff --git a/whistle_apps/src/whistle_apps.erl b/whistle_apps/src/whistle_apps.erl index 282b6fd8818..c1ed43928bc 100644 --- a/whistle_apps/src/whistle_apps.erl +++ b/whistle_apps/src/whistle_apps.erl @@ -35,6 +35,7 @@ start_deps() -> ok = wh_util:ensure_started(sasl), ok = wh_util:ensure_started(crypto), ok = wh_util:ensure_started(ibrowse), + ok = wh_util:ensure_started(gproc), ok = wh_util:ensure_started(riak_err), ok = wh_util:ensure_started(couchbeam), ok = wh_util:ensure_started(whistle_amqp).