330 lines
8.7 KiB
Erlang
330 lines
8.7 KiB
Erlang
%%% @doc
|
|
%%% zx_sys_conf: An interface to etc/sys.conf
|
|
%%%
|
|
%%% It may seem overkill to write an interface module for a config file that only tracks
|
|
%%% five things, but scattering this all around the project is just a bit too l33t for
|
|
%%% an infrastructure project like ZX.
|
|
%%%
|
|
%%% Each exported function that is named after an attribute has two versions, one of
|
|
%%% arity-1 and one of arity-2. The arity-1 version is a "getter", and the arity-2
|
|
%%% version is a "setter". Other functions deal with the data in a way that returns
|
|
%%% an answer and updates the state accordingly.
|
|
%%%
|
|
%%% Bad configuration data causes a reset to defaults so that the system can function.
|
|
%%%
|
|
%%% TODO: Change this to a gen_server that just babysits the config data.
|
|
%%% @end
|
|
|
|
-module(zx_sys_conf).
|
|
-author("Craig Everett <zxq9@zxq9.com>").
|
|
-copyright("Craig Everett <zxq9@zxq9.com>").
|
|
-license("GPL-3.0").
|
|
|
|
-export([load/0, save/1,
|
|
timeout/1, timeout/2,
|
|
retries/1, retries/2, retry/1, retries_left/1,
|
|
maxconn/1, maxconn/2,
|
|
managed/1, managed/2, add_managed/2, rem_managed/2,
|
|
mirrors/1, mirrors/2, add_mirror/2, rem_mirror/2,
|
|
reset/0]).
|
|
|
|
-export_type([data/0]).
|
|
|
|
-include("zx_logger.hrl").
|
|
|
|
|
|
|
|
%%% Type Definitions
|
|
|
|
-record(d,
|
|
{timeout = 5 :: pos_integer(),
|
|
retries = 3 :: non_neg_integer(),
|
|
maxconn = 5 :: pos_integer(),
|
|
managed = sets:new() :: sets:set(zx:realm()),
|
|
mirrors = sets:new() :: sets:set(zx:host())}).
|
|
|
|
-opaque data() :: #d{}.
|
|
|
|
|
|
|
|
%%% Interface functions
|
|
|
|
-spec load() -> data().
|
|
%% @doc
|
|
%% Read from etc/sys.conf and return a populated data() record if it exists, or
|
|
%% populate default values and write a new one if it does not. If a damaged sys.conf
|
|
%% is discovered it will be repaired. This function is side-effecty so should only
|
|
%% be called by zx_daemon and utility code.
|
|
|
|
load() ->
|
|
Path = path(),
|
|
case file:consult(Path) of
|
|
{ok, List} ->
|
|
populate_data(List);
|
|
{error, Reason} ->
|
|
ok = log(error, "Load ~160tp failed with: ~160tp", [Path, Reason]),
|
|
Data = #d{},
|
|
ok = save(Data),
|
|
Data
|
|
end.
|
|
|
|
|
|
populate_data(List) ->
|
|
Timeout =
|
|
case proplists:get_value(timeout, List, 5) of
|
|
TO when is_integer(TO) and TO > 0 -> TO;
|
|
_ -> 5
|
|
end,
|
|
Retries =
|
|
case proplists:get_value(retries, List, 3) of
|
|
RT when is_integer(RT) and RT > 0 -> RT;
|
|
_ -> 3
|
|
end,
|
|
MaxConn =
|
|
case proplists:get_value(maxconn, List, 5) of
|
|
MC when is_integer(MC) and MC > 0 -> MC;
|
|
_ -> 5
|
|
end,
|
|
Managed =
|
|
case proplists:get_value(managed, List, []) of
|
|
MN when is_list(MN) -> sets:from_list(MN);
|
|
_ -> sets:new()
|
|
end,
|
|
Mirrors =
|
|
case proplists:get_value(mirrors, List, []) of
|
|
MR when is_list(MR) -> sets:from_list(MR);
|
|
_ -> sets:new()
|
|
end,
|
|
#d{timeout = Timeout,
|
|
retries = Retries,
|
|
maxconn = MaxConn,
|
|
managed = Managed,
|
|
mirrors = Mirrors}.
|
|
|
|
|
|
-spec save(data()) -> ok.
|
|
%% @doc
|
|
%% Save the current etc/sys.conf to disk.
|
|
|
|
save(#d{timeout = Timeout,
|
|
retries = Retries,
|
|
maxconn = MaxConn,
|
|
managed = Managed,
|
|
mirrors = Mirrors}) ->
|
|
Terms =
|
|
[{timeout, Timeout},
|
|
{retries, Retries},
|
|
{maxconn, MaxConn},
|
|
{managed, sets:to_list(Managed)},
|
|
{mirrors, sets:to_list(Mirrors)}],
|
|
ok = zx_lib:write_terms(path(), Terms),
|
|
log(info, "Wrote etc/sys.conf").
|
|
|
|
|
|
-spec timeout(data()) -> pos_integer().
|
|
%% @doc
|
|
%% Return the timeout value.
|
|
|
|
timeout(#d{timeout = Timeout}) ->
|
|
Timeout.
|
|
|
|
|
|
-spec timeout(Value, Data) -> NewData
|
|
when Value :: pos_integer(),
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Set the timeout attribute to a new value.
|
|
|
|
timeout(Value, Data) when Value > 0 ->
|
|
Data#d{timeout = Value}.
|
|
|
|
|
|
-spec retries(data()) -> non_neg_integer().
|
|
%% @doc
|
|
%% Return the retries value.
|
|
|
|
retries(#d{retries = Retries}) ->
|
|
Retries.
|
|
|
|
|
|
-spec retries(Value, Data) -> NewData
|
|
when Value :: non_neg_integer(),
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Set the retries attribute to a new value.
|
|
|
|
retries(Value, Data) when Value > 0 ->
|
|
Data#d{retries = Value}.
|
|
|
|
|
|
-spec retry(Data) -> Result
|
|
when Data :: data(),
|
|
Result :: {ok, NewData}
|
|
| no_retries,
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Tell the caller whether there are any more retries remaining or return `ok' and
|
|
%% update the state.
|
|
|
|
retry(#d{retries = {0, _}}) ->
|
|
no_retries;
|
|
retry(Data = #d{retries = {Remaining, Setting}}) ->
|
|
NewRemaining = Remaining - 1,
|
|
NewData = Data#d{retries = {NewRemaining, Setting}},
|
|
{ok, NewData}.
|
|
|
|
|
|
-spec retries_left(data()) -> non_neg_integer().
|
|
%% @doc
|
|
%% Return the number of retries remaining.
|
|
|
|
retries_left(#d{retries = {Remaining, _}}) ->
|
|
Remaining.
|
|
|
|
|
|
-spec maxconn(data()) -> pos_integer().
|
|
%% @doc
|
|
%% Return the value of maxconn.
|
|
|
|
maxconn(#d{maxconn = MaxConn}) ->
|
|
MaxConn.
|
|
|
|
|
|
-spec maxconn(Value, Data) -> NewData
|
|
when Value :: pos_integer(),
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Set the value of maxconn.
|
|
|
|
maxconn(Value, Data) when is_integer(Value) and Value > 0 ->
|
|
Data#d{maxconn = Value}.
|
|
|
|
|
|
-spec managed(data()) -> [zx:realm()].
|
|
%% @doc
|
|
%% Return the list of realms managed by the current node.
|
|
|
|
managed(#d{managed = Managed}) ->
|
|
sets:to_list(Managed).
|
|
|
|
|
|
-spec managed(List, Data) -> NewData
|
|
when List :: [zx:realm()],
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Reset the set of managed realms entirely.
|
|
%% The realms must be configured on the current realm at a minimum.
|
|
|
|
managed(List, Data) ->
|
|
Desired = sets:from_list(List),
|
|
Configured = sets:from_list(zx_lib:list_realms()),
|
|
NewManaged = sets:intersection(Desired, Configured),
|
|
Data#d{managed = NewManaged}.
|
|
|
|
|
|
-spec add_managed(Realm, Data) -> Result
|
|
when Realm :: zx:realm(),
|
|
Data :: data(),
|
|
Result :: {ok, NewData}
|
|
| {error, unconfigured},
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Add a new realm to the list of managed realms. The new realm must be configured on
|
|
%% the current node. This node will then behave as the prime node for the realm (whether
|
|
%% it is or not).
|
|
|
|
add_managed(Realm, Data = #d{managed = Managed}) ->
|
|
case zx_lib:realm_exists(Realm) of
|
|
true ->
|
|
NewData = Data#d{managed = sets:add_element(Realm, Managed)},
|
|
ok = log(info, "Now managing realm: ~160tp", [Realm]),
|
|
{ok, NewData};
|
|
false ->
|
|
ok = log(warning, "Cannot manage unconfigured realm: ~160tp", [Realm]),
|
|
{error, unconfigured}
|
|
end.
|
|
|
|
|
|
-spec rem_managed(Realm, Data) -> Result
|
|
when Realm :: zx:realm(),
|
|
Data :: data(),
|
|
Result :: {ok, NewData}
|
|
| {error, unmanaged},
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Stop managing a realm.
|
|
|
|
rem_managed(Realm, Data = #d{managed = Managed}) ->
|
|
case sets:is_element(Realm, Managed) of
|
|
true ->
|
|
NewData = Data#d{managed = sets:del_element(Realm, Managed)},
|
|
ok = log(info, "No longer managing realm: ~160tp", [Realm]),
|
|
{ok, NewData};
|
|
false ->
|
|
ok = log(warning, "Cannot stop managing unmanaged realm: ~160tp", [Realm]),
|
|
{error, unmanaged}
|
|
end.
|
|
|
|
|
|
-spec mirrors(data()) -> [zx:host()].
|
|
%% @doc
|
|
%% Return the list of private mirrors.
|
|
|
|
mirrors(#d{mirrors = Mirrors}) ->
|
|
sets:to_list(Mirrors).
|
|
|
|
|
|
-spec mirrors(Hosts, Data) -> NewData
|
|
when Hosts :: [zx:host()],
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @private
|
|
%% Reset the mirror configuration.
|
|
|
|
mirrors(Hosts, Data) ->
|
|
Data#d{mirrors = sets:from_list(Hosts)}.
|
|
|
|
|
|
-spec add_mirror(Host, Data) -> NewData
|
|
when Host :: zx:host(),
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @doc
|
|
%% Add a mirror to the permanent configuration.
|
|
|
|
add_mirror(Host, Data = #d{mirrors = Mirrors}) ->
|
|
Data#d{mirrors = sets:add_element(Host, Mirrors)}.
|
|
|
|
|
|
-spec rem_mirror(Host, Data) -> NewData
|
|
when Host :: zx:host(),
|
|
Data :: data(),
|
|
NewData :: data().
|
|
%% @private
|
|
%% Remove a host from the list of permanent mirrors.
|
|
|
|
rem_mirror(Host, Data = #d{mirrors = Mirrors}) ->
|
|
Data#d{mirrors = sets:del_element(Host, Mirrors)}.
|
|
|
|
|
|
-spec reset() -> data().
|
|
%% @private
|
|
%% Reset sys.conf.
|
|
|
|
reset() ->
|
|
Data = #d{},
|
|
save(Data),
|
|
Data.
|
|
|
|
|
|
-spec path() -> file:filename().
|
|
%% @private
|
|
%% Return the path to $ZOMP_DIR/etc/sys.conf.
|
|
|
|
path() ->
|
|
filename:join(zx_lib:path(etc), "sys.conf").
|