summaryrefslogtreecommitdiff
path: root/p11p-daemon/src/p11p_manager.erl
diff options
context:
space:
mode:
authorLinus Nordberg <linus@sunet.se>2020-01-10 13:54:38 +0100
committerLinus Nordberg <linus@sunet.se>2020-01-10 14:06:11 +0100
commitbcf1816564b17aa0fb2a581d2887486212f8171a (patch)
tree919ae1e69d6683d31db24f39889790505351d7d2 /p11p-daemon/src/p11p_manager.erl
parent34278d385bb0d005972e5fc25a4d5d36e40733ee (diff)
Rename remote -> client
Also rename token -> vtoken where appropriate.
Diffstat (limited to 'p11p-daemon/src/p11p_manager.erl')
-rw-r--r--p11p-daemon/src/p11p_manager.erl209
1 files changed, 209 insertions, 0 deletions
diff --git a/p11p-daemon/src/p11p_manager.erl b/p11p-daemon/src/p11p_manager.erl
new file mode 100644
index 0000000..7c3bdb9
--- /dev/null
+++ b/p11p-daemon/src/p11p_manager.erl
@@ -0,0 +1,209 @@
+%%% Copyright (c) 2019, Sunet.
+%%% See LICENSE for licensing information.
+
+%% A manager is a genserver for coordination of clients and vtokens.
+
+%% Provide a lookup service for servers in need of a client to send
+%% requests to, by keeping track of which module is current for a
+%% given vtoken and spawn a p11p_client genserver "on demand".
+%%
+%% Provide a client event and a server event API for servers and
+%% clients, respectively, where events like " token timed out" and
+%% "p11 app hung up" can be reported.
+%%
+%% Keep track of successful p11 requests which might cause state
+%% changes in a token, like logins. When switching token under the
+%% feet of the p11 app, replay whatever is needed to the new
+%% token.
+%% Certain state changing p11 requests cannot be replayed, like
+%% generation of a new key. Any such (successful) request invalidates
+%% all other clients for the given vtoken.
+
+-module(p11p_manager).
+
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+-export([client_for_token/1, client_event/2]). % For servers.
+-export([server_event/2]). % For clients.
+
+%% Genserver callbacks.
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+%% Records and types.
+-record(client, {
+ tokname :: string(),
+ servid :: atom(),
+ modpath :: string(),
+ modenv :: [],
+ balance :: integer(),
+ pid :: pid() | undefined
+ }).
+
+-record(vtoken, {
+ mode :: p11p_config:token_mode_t(),
+ balance_count :: integer(),
+ clients :: [#client{}] % Active client in hd().
+ }).
+
+-record(state, {
+ vtokens :: #{string() => #vtoken{}}
+ }).
+
+%% API implementation.
+-spec start_link() -> {ok, pid()} | {error, term()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec client_for_token(string()) -> pid().
+client_for_token(TokName) ->
+ gen_server:call(?MODULE, {client_for_token, TokName}).
+client_event(Event, Args) ->
+ gen_server:cast(?MODULE, {client_event, Event, Args}).
+
+server_event(Event, Args) ->
+ gen_server:cast(?MODULE, {server_event, Event, Args}).
+
+%% Genserver callbacks.
+init([]) ->
+ {ok, #state{vtokens = init_vtokens(p11p_config:tokens())}}.
+
+handle_call({client_for_token, TokNameIn}, _, #state{vtokens = Tokens} = S) ->
+ #{TokNameIn := TokenIn} = Tokens,
+ ClientsIn = TokenIn#vtoken.clients,
+ lager:debug("all clients: ~p", [ClientsIn]),
+ {Clients, BalanceCount} =
+ case TokenIn#vtoken.balance_count of
+ 0 ->
+ lager:debug("~p: balancing: next client", [self()]),
+ Rotated = rotate_clients(ClientsIn),
+ First = hd(Rotated),
+ {Rotated, First#client.balance - 1};
+ N when N > 0 ->
+ lager:debug("~p: balancing: ~B more invocations", [self(), N]),
+ {ClientsIn, N - 1};
+ -1 ->
+ {ClientsIn, -1}
+ end,
+ #client{tokname = TokNameIn,
+ servid = ServId,
+ modpath = ModPath,
+ modenv = ModEnv,
+ pid = PidIn} = SelectedClient = hd(Clients),
+ case PidIn of
+ undefined ->
+ {ok, Pid} =
+ p11p_client:start_link(ServId, TokNameIn, ModPath, ModEnv),
+ Client = SelectedClient#client{pid = Pid},
+ Token = TokenIn#vtoken{clients = [Client | tl(Clients)],
+ balance_count = BalanceCount},
+ {reply, Pid, S#state{vtokens = Tokens#{TokNameIn := Token}}};
+ _ ->
+ {reply, PidIn, S}
+ end;
+handle_call(Call, _From, State) ->
+ lager:debug("Unhandled call: ~p~n", [Call]),
+ {reply, unhandled, State}.
+
+handle_cast({server_event, timeout, [TokNameIn, Server]},
+ #state{vtokens = Tokens} = S) ->
+ lager:debug("~p: ~s: timed out, stopping ~p", [self(), TokNameIn, Server]),
+ gen_server:stop(Server), % Hang up on p11 client.
+ %% TODO: do some code dedup with client_for_token?
+ #{TokNameIn := TokenIn} = Tokens,
+ Clients = TokenIn#vtoken.clients,
+ SelectedClient = hd(Clients),
+ Client = SelectedClient#client{pid = undefined},
+ Token = TokenIn#vtoken{clients = tl(Clients) ++ [Client]},
+ lager:debug("~p: ~s: updated token: ~p", [self(), TokNameIn, Token]),
+ {noreply, S#state{vtokens = Tokens#{TokNameIn := Token}}};
+
+handle_cast({client_event, client_gone, [TokName, Pid]},
+ #state{vtokens = Tokens} = S) ->
+ lager:debug("~p: asking client ~p to stop", [self(), Pid]),
+ p11p_client:stop(Pid, normal),
+ #{TokName := TokenIn} = Tokens,
+ Clients = lists:map(fun(E) ->
+ case E#client.pid of
+ Pid -> E#client{pid = undefined};
+ _ -> E
+ end
+ end, TokenIn#vtoken.clients),
+ Token = TokenIn#vtoken{clients = Clients},
+ {noreply, S#state{vtokens = Tokens#{TokName := Token}}};
+
+handle_cast(Cast, State) ->
+ lager:debug("Unhandled cast: ~p~n", [Cast]),
+ {noreply, State}.
+
+handle_info({Port, {exit_status, Status}}, State) ->
+ %% FIXME: do we need to be trapping exits explicitly?
+ lager:info("~p: process ~p exited with ~p", [self(), Port, Status]),
+ {noreply, State};
+handle_info(Info, State) ->
+ lager:debug("~p: Unhandled info: ~p~n", [self(), Info]),
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVersion, State, _Extra) ->
+ {ok, State}.
+
+%% Private functions
+-spec init_vtokens([p11p_config:token()]) -> #{string() => #vtoken{}}.
+init_vtokens(ConfTokens) ->
+ init_vtokens(ConfTokens, #{}).
+init_vtokens([], Acc)->
+ lager:debug("~p: created tokens from config: ~p", [self(), Acc]),
+ Acc;
+init_vtokens([H|T], Acc)->
+ init_vtokens(T, Acc#{p11p_config:nameof(H) => new_vtoken(H)}).
+
+new_vtoken(Conf) ->
+ Name = p11p_config:nameof(Conf),
+ Mode = p11p_config:token_mode(Name),
+ Clients = clients(Name,
+ p11p_config:modules_for_token(Name),
+ Mode),
+ R0 = hd(Clients),
+ #vtoken{
+ mode = p11p_config:token_mode(Name),
+ balance_count = R0#client.balance,
+ clients = Clients
+ }.
+
+clients(TokName, ConfModules, ConfMode) ->
+ clients(TokName, ConfModules, ConfMode, []).
+clients(_, [], _, Acc) ->
+ Acc;
+clients(TokName, [H|T], ConfMode, Acc) ->
+ ModName = p11p_config:nameof(H),
+ ServName = "p11p_client:" ++ TokName ++ ":" ++ ModName,
+ ModPath = p11p_config:module_path(H),
+ ModEnv = p11p_config:module_env(H),
+ clients(TokName, T, ConfMode, [#client{
+ tokname = TokName,
+ servid = list_to_atom(ServName),
+ modpath = ModPath,
+ modenv = ModEnv,
+ balance = balance(ConfMode, length(T) + 1)
+ }
+ | Acc]).
+
+-spec balance(p11p_config:token_mode_t(), non_neg_integer()) -> integer().
+balance({balance, Ratios}, N) ->
+ lists:nth(N, Ratios);
+balance(_, _) ->
+ -1.
+
+%% -spec balance_count(p11p_config:token_mode_t()) -> integer().
+%% balance_count(#vtoken{mode = {balance, _}, balance_count = C}) ->
+%% C - 1;
+%% balance_count(_) ->
+%% -1.
+
+rotate_clients(L) ->
+ lists:reverse([hd(L) | lists:reverse(tl(L))]).