summaryrefslogtreecommitdiff
path: root/src/dynomite_app.erl
blob: 6ee0b978b9b4c5ce02836a84786aa12b63f33718 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
%%%-------------------------------------------------------------------
%%% File:      dynomite.erl
%%% @author    Cliff Moon <cliff@powerset.com> []
%%% @copyright 2008 Cliff Moon
%%% @doc
%%%
%%% @end
%%%
%%% @since 2008-06-27 by Cliff Moon
%%%-------------------------------------------------------------------
-module(dynomite_app).
-author('cliff@powerset.com').
-author('brad@cloudant.com').

-behaviour(application).

-include("../include/config.hrl").
-include("../../couch/src/couch_db.hrl").

%% Application callbacks
-export([start/2, stop/1]).

-define(APPS, [crypto,sasl,mochiweb]).
-define(DEFAULT_CLUSTER_URL, "http://localhost:5984/_cluster").

%%====================================================================
%% Application callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% @spec start(Type, StartArgs) -> {ok, Pid} |
%%                                 {ok, Pid, State} |
%%                                 {error, Reason}
%% @doc This function is called whenever an application
%% is started using application:start/1,2, and should start the processes
%% of the application. If the application is structured according to the
%% OTP design principles as a supervision tree, this means starting the
%% top supervisor of the tree.
%% @end
%%--------------------------------------------------------------------


%% @doc start required apps, join cluster, start dynomite supervisor
start(_Type, _StartArgs) ->
    % get process_dict hack for startargs (i.e. not from .app file)
    PdStartArgs = case erase(startargs) of
    undefined ->
        [];
    Args ->
        Args
    end,

    % start required apps
    State = start_apps(),

    % start dynomite supervisor
    ok = start_node(),
    case dynomite_sup:start_link(PdStartArgs) of
    {ok, Supervisor} ->
        {ok, Supervisor, State};
    Error ->
        Error
    end.


%%--------------------------------------------------------------------
%% @spec stop(State) -> void()
%% @doc This function is called whenever an application
%% has stopped. It is intended to be the opposite of Module:start/2 and
%% should do any necessary cleaning up. The return value is ignored.
%% @end
%%--------------------------------------------------------------------
stop({_, Sup}) ->
    showroom_log:message(alert, "dynomite application stopped", []),
    exit(Sup, normal),
    ok.


%%====================================================================
%% Internal functions
%%====================================================================

start_apps() ->
    Fun = fun(App, AccIn) ->
        Result = case application:start(App) of
        ok ->
            App;
        {error, {already_started, App}} ->
            nil;
        _Error ->
            exit(app_start_fail)
        end,
        if
        Result =/= nil -> [App|AccIn];
        true -> AccIn
        end
    end,
    lists:foldl(Fun, [], ?APPS).


%% @spec start_node() -> ok | {error, Reason}
%% @doc start this node (join to dist. erlang cluster)
start_node() ->
    PingUrl = couch_config:get("cluster","ping", ?DEFAULT_CLUSTER_URL),
    ?LOG_DEBUG("PingUrl: ~p", [PingUrl]),
    Result = case get_pingnode(PingUrl, 1) of
    {ok, PingNode} ->
        join(PingNode);
    _ ->
        ?LOG_INFO("No pingnode found.  Becoming single-node cluster", [])
    end,
    couch_api:create_db(<<"users">>, []), % all nodes have local 'users' db
    Result.


%% @spec get_pingnode(Url::string(), Retries::int()) -> node() |
%%       {error, Reason}
%% @doc make a http get call to Url to get cluster information
get_pingnode(Url, Retries) ->
    try couch_rep_httpc:request(#http_db{url=Url, retries=Retries}) of
    {[{<<"ping_node">>, Node}]} ->
        {ok, list_to_atom(binary_to_list(Node))};
    _ ->
        {error, pingnode_not_found}
    catch
      _:_ ->
        {error, pingnode_not_found}
    end.


join(PingNode) ->
    if
    node() =:= PingNode ->
        ok; % we must be brain, so we'll take over the world
    true ->
        case net_adm:ping(PingNode) of
        pong ->
            % there is a cluster, we just joined it
                ?LOG_DEBUG("ping successful, we're in.", []),
                timer:sleep(1000); %% grr, what a hack, erlang.  rly?
        pang ->
            ?LOG_ERROR("ping not successful.", []),
            throw({cluster_error, ?l2b("cluster ping not successful")})
        end
    end,
    ok.