1%% @author Couchbase <info@couchbase.com>
2%% @copyright 2013 Couchbase, Inc.
3%%
4%% Licensed under the Apache License, Version 2.0 (the "License");
5%% you may not use this file except in compliance with the License.
6%% You may obtain a copy of the License at
7%%
8%%      http://www.apache.org/licenses/LICENSE-2.0
9%%
10%% Unless required by applicable law or agreed to in writing, software
11%% distributed under the License is distributed on an "AS IS" BASIS,
12%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13%% See the License for the specific language governing permissions and
14%% limitations under the License.
15
16-module(ns_babysitter).
17
18-behavior(application).
19
20-export([start/2, stop/1]).
21-export([make_pidfile/0, delete_pidfile/0]).
22
23-include("ns_common.hrl").
24-include_lib("ale/include/ale.hrl").
25
26start(_, _) ->
27    %% we're reading environment of ns_server application. Thus we
28    %% need to load it.
29    ok = application:load(ns_server),
30
31    setup_static_config(),
32    init_logging(),
33
34    %% To initialize logging static config must be setup thus this weird
35    %% machinery is required to log messages from setup_static_config().
36    self() ! done,
37    log_pending(),
38
39    {have_host, true} = {have_host, ('nonode@nohost' =/= node())},
40
41    ok = dist_manager:configure_net_kernel(),
42
43    Cookie =
44        case erlang:get_cookie() of
45            nocookie ->
46                Bytes = crypto:strong_rand_bytes(32),
47                NewCookie = binary_to_atom(misc:hexify(Bytes), latin1),
48                erlang:set_cookie(node(), NewCookie),
49                NewCookie;
50            SomeCookie ->
51                SomeCookie
52        end,
53
54    ?log_info("babysitter cookie: ~p~n", [Cookie]),
55    maybe_write_file(cookiefile, Cookie, "babysitter cookie"),
56    maybe_write_file(nodefile, node(), "babysitter node name"),
57
58    % Clear the HTTP proxy environment variables as they are honored, when they
59    % are set, by the golang net/http package.
60    true = os:unsetenv("http_proxy"),
61    true = os:unsetenv("https_proxy"),
62
63    ns_babysitter_sup:start_link().
64
65maybe_write_file(Env, Content, Name) ->
66    case application:get_env(Env) of
67        {ok, File} ->
68            filelib:ensure_dir(File),
69            misc:atomic_write_file(File, erlang:atom_to_list(Content) ++ "\n"),
70            ?log_info("Saved ~s to ~s", [Name, File]);
71        _ ->
72            ok
73    end.
74
75log_pending() ->
76    receive
77        done ->
78            ok;
79        {LogLevel, Fmt, Args} ->
80            ?LOG(LogLevel, Fmt, Args),
81            log_pending()
82    end.
83
84get_config_path() ->
85    case application:get_env(ns_server, config_path) of
86        {ok, V} -> V;
87        _ ->
88            erlang:error("config_path parameter for ns_server application is missing!")
89    end.
90
91setup_static_config() ->
92    Terms = case file:consult(get_config_path()) of
93                {ok, T} when is_list(T) ->
94                    T;
95                _ ->
96                    erlang:error("failed to read static config: " ++ get_config_path() ++ ". It must be readable file with list of pairs~n")
97            end,
98    self() ! {info, "Static config terms:~n~p", [Terms]},
99    lists:foreach(fun ({K,V}) ->
100                          case application:get_env(ns_server, K) of
101                              undefined ->
102                                  application:set_env(ns_server, K, V);
103                              _ ->
104                                  self() ! {warn,
105                                            "not overriding parameter ~p, which is given from command line",
106                                            [K]}
107                          end
108                  end, Terms).
109
110init_logging() ->
111    ale:with_configuration_batching(
112      fun () ->
113              do_init_logging()
114      end),
115    ale:info(?NS_SERVER_LOGGER, "Brought up babysitter logging").
116
117do_init_logging() ->
118    {ok, Dir} = application:get_env(ns_server, error_logger_mf_dir),
119
120    ok = misc:mkdir_p(Dir),
121    ok = convert_disk_log_files(Dir),
122
123    ok = ns_server:start_disk_sink(babysitter_sink, ?BABYSITTER_LOG_FILENAME),
124
125    ok = ale:start_logger(?NS_SERVER_LOGGER, debug),
126    ok = ale:set_loglevel(?ERROR_LOGGER, debug),
127
128    ok = ale:add_sink(?NS_SERVER_LOGGER, babysitter_sink, debug),
129    ok = ale:add_sink(?ERROR_LOGGER, babysitter_sink, debug),
130
131    case misc:get_env_default(ns_server, dont_suppress_stderr_logger, false) of
132        true ->
133            ale:stop_sink(stderr),
134            ok = ale:start_sink(stderr, ale_stderr_sink, []),
135
136            lists:foreach(
137              fun (Logger) ->
138                      ok = ale:add_sink(Logger, stderr, debug)
139              end, [?NS_SERVER_LOGGER, ?ERROR_LOGGER]);
140        false ->
141            ok
142    end.
143
144stop(_) ->
145    ok.
146
147convert_disk_log_files(Dir) ->
148    lists:foreach(
149      fun (Log) ->
150              ok = convert_disk_log_file(Dir, Log)
151      end,
152      [?DEFAULT_LOG_FILENAME,
153       ?ERRORS_LOG_FILENAME,
154       ?VIEWS_LOG_FILENAME,
155       ?MAPREDUCE_ERRORS_LOG_FILENAME,
156       ?COUCHDB_LOG_FILENAME,
157       ?DEBUG_LOG_FILENAME,
158       ?XDCR_LOG_FILENAME,
159       ?XDCR_ERRORS_LOG_FILENAME,
160       ?STATS_LOG_FILENAME,
161       ?BABYSITTER_LOG_FILENAME,
162       ?SSL_PROXY_LOG_FILENAME,
163       ?REPORTS_LOG_FILENAME,
164       ?XDCR_TRACE_LOG_FILENAME,
165       ?ACCESS_LOG_FILENAME]).
166
167convert_disk_log_file(Dir, Name) ->
168    [OldName, "log"] = string:tokens(Name, "."),
169
170    IdxFile = filename:join(Dir, OldName ++ ".idx"),
171    SizFile = filename:join(Dir, OldName ++ ".siz"),
172
173    case filelib:is_regular(IdxFile) of
174        true ->
175            {Ix, NFiles} = read_disk_log_index_file(filename:join(Dir, OldName)),
176            Ixs = lists:seq(Ix, 1, -1) ++ lists:seq(NFiles, Ix + 1, -1),
177
178            lists:foreach(
179              fun ({NewIx, OldIx}) ->
180                      OldPath = filename:join(Dir,
181                                              OldName ++
182                                                  "." ++ integer_to_list(OldIx)),
183                      NewSuffix = case NewIx of
184                                      0 ->
185                                          ".log";
186                                      _ ->
187                                          ".log." ++ integer_to_list(NewIx)
188                                  end,
189                      NewPath = filename:join(Dir, OldName ++ NewSuffix),
190
191                      case file:rename(OldPath, NewPath) of
192                          {error, enoent} ->
193                              ok;
194                          ok ->
195                              ok
196                      end,
197
198                      file:delete(SizFile),
199                      file:delete(IdxFile)
200              end, misc:enumerate(Ixs, 0));
201        false ->
202            ok
203    end.
204
205read_disk_log_index_file(Path) ->
206    {Ix, _, _, NFiles} = disk_log_1:read_index_file(Path),
207
208    %% Index can be one greater than number of files. This means that maximum
209    %% number of files is not yet reached.
210    %%
211    %% Pretty weird behavior: if we're writing to the first file out of 20
212    %% read_index_file returns {1, _, _, 1}. But as we move to the second file
213    %% the result becomes be {2, _, _, 1}.
214    case Ix =:= NFiles + 1 of
215        true ->
216            {Ix, Ix};
217        false ->
218            {Ix, NFiles}
219    end.
220
221make_pidfile() ->
222    case application:get_env(ns_babysitter, pidfile) of
223        {ok, PidFile} -> make_pidfile(PidFile);
224        X -> X
225    end.
226
227make_pidfile(PidFile) ->
228    Pid = os:getpid(),
229    %% Pid is a string representation of the process id, so we append
230    %% a newline to the end.
231    ok = misc:write_file(PidFile, list_to_binary(Pid ++ "\n")),
232    ok.
233
234delete_pidfile() ->
235    case application:get_env(ns_babysitter, pidfile) of
236        {ok, PidFile} -> delete_pidfile(PidFile);
237        X -> X
238    end.
239
240delete_pidfile(PidFile) ->
241    ok = file:delete(PidFile).
242
243