diff options
author | Linus Nordberg <linus@sunet.se> | 2020-01-10 13:54:38 +0100 |
---|---|---|
committer | Linus Nordberg <linus@sunet.se> | 2020-01-10 14:06:11 +0100 |
commit | bcf1816564b17aa0fb2a581d2887486212f8171a (patch) | |
tree | 919ae1e69d6683d31db24f39889790505351d7d2 /p11p-daemon/src/p11p_manager.erl | |
parent | 34278d385bb0d005972e5fc25a4d5d36e40733ee (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.erl | 209 |
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))]). |