zx/zomp/lib/otpr/zx/0.1.0/src/zx_sys_conf.erl
2018-08-03 14:55:33 +00:00

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").