./ct_report/coverage/mongoose_service.COVER.html

1 %%==============================================================================
2 %% Copyright 2018 Erlang Solutions Ltd.
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
17 -module(mongoose_service).
18
19 -include("mongoose.hrl").
20
21 %% API
22 -export([start/0,
23 stop/0,
24 config_spec/1,
25 get_deps/1,
26 is_loaded/1,
27 assert_loaded/1]).
28
29 %% Shell utilities
30 -export([loaded_services_with_opts/0]).
31
32 %% Service management utilities for tests
33 -export([replace_services/2, ensure_stopped/1, ensure_started/2]).
34
35 -ignore_xref([loaded_services_with_opts/0, replace_services/2, ensure_stopped/1, ensure_started/2]).
36
37 -type service() :: module().
38 -type opt_key() :: atom().
39 -type opt_value() :: mongoose_config:value().
40 -type options() :: #{opt_key() => opt_value()}.
41 -type start_result() :: any().
42 -type service_list() :: [{service(), options()}].
43 -type service_map() :: #{service() => options()}.
44
45 -export_type([service/0, service_list/0, service_map/0, options/0]).
46
47 -callback start(options()) -> start_result().
48 -callback stop() -> any().
49 -callback config_spec() -> mongoose_config_spec:config_section().
50 -callback deps() -> [service()].
51
52 -optional_callbacks([deps/0]).
53
54 %% @doc Start all configured services in the dependency order.
55 -spec start() -> ok.
56 start() ->
57 93 [start_service(Service, Opts) || {Service, Opts} <- sorted_services()],
58 93 ok.
59
60 %% @doc Stop all configured services in the reverse dependency order
61 %% to avoid stopping services which have other services dependent on them.
62 -spec stop() -> ok.
63 stop() ->
64 93 [stop_service(Service) || {Service, _Opts} <- lists:reverse(sorted_services())],
65 93 ok.
66
67 %% @doc Replace services at runtime - only for testing and debugging.
68 %% Running services from ToStop are stopped and services from ToEnsure are (re)started when needed.
69 %% Unused dependencies are stopped if no running services depend on them anymore.
70 %% To prevent an unused dependency from being stopped, you need to include it in ToEnsure.
71 -spec replace_services([service()], service_map()) -> ok.
72 replace_services(ToStop, ToEnsure) ->
73 4 Current = loaded_services_with_opts(),
74 4 Old = maps:with(ToStop ++ maps:keys(ToEnsure), Current),
75 4 OldWithDeps = mongoose_service_deps:resolve_deps(Old),
76 4 SortedOldWithDeps = mongoose_service_deps:sort_deps(OldWithDeps),
77 4 WithoutOld = maps:without(maps:keys(OldWithDeps), Current),
78 4 WithNew = maps:merge(WithoutOld, ToEnsure),
79 4 Target = mongoose_service_deps:resolve_deps(WithNew),
80
81 %% Stop each affected service if it is not in Target (stop deps first)
82 4 [ensure_stopped(Service) || {Service, _} <- lists:reverse(SortedOldWithDeps),
83 5 not maps:is_key(Service, Target)],
84
85 %% Ensure each service from Target
86 4 [ensure_started(Service, Opts) || {Service, Opts} <- mongoose_service_deps:sort_deps(Target)],
87 4 ok.
88
89 -spec config_spec(service()) -> mongoose_config_spec:config_section().
90 config_spec(Service) ->
91 186 Service:config_spec().
92
93 -spec get_deps(service()) -> [service()].
94 get_deps(Service) ->
95 581 case mongoose_lib:is_exported(Service, deps, 0) of
96
:-(
true -> Service:deps();
97 581 false -> []
98 end.
99
100 -spec sorted_services() -> service_list().
101 sorted_services() ->
102 186 mongoose_service_deps:sort_deps(loaded_services_with_opts()).
103
104 -spec set_services(service_map()) -> ok.
105 set_services(Services) ->
106 147 mongoose_config:set_opt(services, Services).
107
108 -spec ensure_stopped(service()) -> {stopped, options()} | already_stopped.
109 ensure_stopped(Service) ->
110 83 case loaded_services_with_opts() of
111 #{Service := Opts} = Services ->
112 71 stop_service(Service, Services),
113 71 {stopped, Opts};
114 _Services ->
115 12 already_stopped
116 end.
117
118 -spec ensure_started(service(), options()) ->
119 {started, start_result()} | {restarted, options(), start_result()} | already_started.
120 ensure_started(Service, Opts) ->
121 82 case loaded_services_with_opts() of
122 #{Service := Opts} ->
123 9 already_started;
124 #{Service := PrevOpts} = Services ->
125 3 stop_service(Service, Services),
126 3 {ok, Result} = start_service(Service, Opts, Services),
127 3 {restarted, PrevOpts, Result};
128 Services ->
129 70 {ok, Result} = start_service(Service, Opts, Services),
130 70 {started, Result}
131 end.
132
133 -spec start_service(service(), options(), service_map()) -> {ok, start_result()}.
134 start_service(Service, Opts, Services) ->
135 73 set_services(Services#{Service => Opts}),
136 73 try
137 73 start_service(Service, Opts)
138 catch
139 C:R:S ->
140
:-(
set_services(Services),
141
:-(
erlang:raise(C, R, S)
142 end.
143
144 -spec stop_service(service(), service_map()) -> ok.
145 stop_service(Service, Services) ->
146 74 stop_service(Service),
147 74 set_services(maps:remove(Service, Services)).
148
149 start_service(Service, Opts) ->
150 259 assert_loaded(Service),
151 259 try
152 259 Res = Service:start(Opts),
153 259 ?LOG_INFO(#{what => service_started, service => Service,
154 259 text => <<"Started MongooseIM service">>}),
155 259 case Res of
156 116 {ok, _} -> Res;
157 143 _ -> {ok, Res}
158 end
159 catch Class:Reason:Stacktrace ->
160
:-(
?LOG_CRITICAL(#{what => service_startup_failed,
161 text => <<"Failed to start MongooseIM service">>,
162 service => Service, opts => Opts,
163
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
164
:-(
erlang:raise(Class, Reason, Stacktrace)
165 end.
166
167 stop_service(Service) ->
168 257 assert_loaded(Service),
169 257 try Service:stop() of
170 _ ->
171 257 ?LOG_INFO(#{what => service_stopped, service => Service,
172 257 text => <<"Stopped MongooseIM service">>}),
173 257 ok
174 catch Class:Reason:Stacktrace ->
175
:-(
?LOG_ERROR(#{what => service_stop_failed, service => Service,
176 text => <<"Failed to stop MongooseIM service">>,
177
:-(
class => Class, reason => Reason, stacktrace => Stacktrace}),
178
:-(
erlang:raise(Class, Reason, Stacktrace)
179 end.
180
181 -spec assert_loaded(service()) -> ok.
182 assert_loaded(Service) ->
183 516 case is_loaded(Service) of
184 true ->
185 516 ok;
186 false ->
187
:-(
error(#{what => service_not_loaded,
188 text => <<"Service missing from mongoose_config">>,
189 service => Service})
190 end.
191
192 -spec is_loaded(service()) -> boolean().
193 is_loaded(Service) ->
194 876 case mongoose_config:lookup_opt([services, Service]) of
195 811 {ok, _Opts} -> true;
196 65 {error, not_found} -> false
197 end.
198
199 -spec loaded_services_with_opts() -> service_map().
200 loaded_services_with_opts() ->
201 363 mongoose_config:get_opt(services).
Line Hits Source