Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance VRF and node selection #412

Merged
merged 3 commits into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 31 additions & 12 deletions apps/ergw_core/src/ergw_gsn_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
gy_events_to_credits/3,
pcc_events_to_charging_rule_report/1,
make_gy_credit_request/3]).
-export([select_vrf/2,
-export([select_vrf/3,
allocate_ips/7, release_context_ips/1]).
-export([init_tunnel/4,
assign_tunnel_teid/3,
Expand Down Expand Up @@ -565,22 +565,41 @@ make_gy_credit_request(Ev, Add, CreditsNeeded) ->
%%%===================================================================

%% select/2
select(_, []) -> undefined;
select(first, L) -> hd(L);
select(_, []) -> {error, none};
select(first, L) -> {ok, hd(L)};
select(random, L) when is_list(L) ->
lists:nth(rand:uniform(length(L)), L).
{ok, lists:nth(rand:uniform(length(L)), L)}.

%% select/3
select(Method, L1, L2) when is_map(L2) ->
select(Method, L1, maps:keys(L2));
select(Method, lists:filter(maps:is_key(_, L2), L1));
select(Method, L1, L2) when is_list(L1), is_list(L2) ->
{L,_} = lists:partition(fun(A) -> lists:member(A, L2) end, L1),
select(Method, L).

%% select_vrf/2
select_vrf({AvaVRFs, _AvaPools}, APN) ->
{ok, APNOpts} = ergw_apn:get(APN),
select(random, maps:get(vrfs, APNOpts), AvaVRFs).
select(Method, lists:filter(lists:member(_, L2), L1)).

do_select_vrf({AvaVRFs, []}, ApnVRFs, []) ->
select(random, ApnVRFs, AvaVRFs);
do_select_vrf({_, AvaPools}, ApnVRFs, []) ->
NVRFs = lists:usort([VRF || #{vrf := VRF} <- AvaPools]),
select(random, ApnVRFs, NVRFs);
do_select_vrf({_, AvaPools}, ApnVRFs, [NAT]) ->
NVRFs =
lists:foldl(
fun(#{vrf := VRF, nat_port_blocks := Blocks}, NATs) ->
case lists:member(NAT, Blocks) of
true -> NATs#{VRF => true};
false -> NATs
end;
(_, NATs) -> NATs
end, #{}, AvaPools),
select(random, ApnVRFs, NVRFs).

%% select_vrf/3
select_vrf(NodeCaps, APN, #{'NAT-Pool-Id' := NAT}) ->
{ok, #{vrfs := ApnVRFs}} = ergw_apn:get(APN),
do_select_vrf(NodeCaps, ApnVRFs, [NAT]);
select_vrf(NodeCaps, APN, _) ->
{ok, #{vrfs := ApnVRFs}} = ergw_apn:get(APN),
do_select_vrf(NodeCaps, ApnVRFs, []).

%%%===================================================================

Expand Down
235 changes: 103 additions & 132 deletions apps/ergw_core/src/ergw_pfcp_context.erl
Original file line number Diff line number Diff line change
Expand Up @@ -884,12 +884,11 @@ select(first, L) -> hd(L);
select(random, L) when is_list(L) ->
lists:nth(rand:uniform(length(L)), L).


%% select_upf/1
select_upf(Candidates) ->
do([error_m ||
Available = ergw_sx_node_reg:available(),
Pid <- select_upf_with(fun({Pid, _}) -> {ok, Pid} end, Candidates, Available),
Pid <- select_upf_with(fun(_, {Pid, _}) -> {ok, Pid} end, Candidates, Available),
ergw_sx_node:attach(Pid)
]).

Expand All @@ -899,7 +898,7 @@ select_upf_with(_, [], _) ->
select_upf_with(Fun, Candidates, Available) ->
case ergw_node_selection:snaptr_candidate(Candidates) of
{{Node, _, _}, Next} when is_map_key(Node, Available) ->
case Fun(maps:get(Node, Available)) of
case Fun(Node, maps:get(Node, Available)) of
{ok, _} = Result ->
Result;
_ ->
Expand All @@ -909,162 +908,134 @@ select_upf_with(Fun, Candidates, Available) ->
select_upf_with(Fun, Next, Available)
end.

%% select_upf_pools/3
select_upf_pool(Candidates, APNOpts, WantPools) ->
do([error_m ||
{_, _, Pools} = Node <- select_by_caps(Candidates, APNOpts, WantPools),
begin
%% select a random VRF
VRF = select(random, lists:usort([V || #{vrf := V} <- Pools])),
FilterF =
fun (IP) ->
lists:foldl(
fun(#{vrf := HasV, ip_versions := Versions, ip_pools := HasP,
nat_port_blocks := HasNAT}, P) ->
case HasV =:= VRF andalso lists:member(IP, Versions) of
true ->
case HasNAT of
[] ->
P ++ [{Pool, undefined} || Pool <- HasP];
_ ->
P ++ [{Pool, NAT} || Pool <- HasP, NAT <- HasNAT]
end;
false ->
P
end
end, [], Pools)
end,
Maybe = fun({_,_} = Pool) -> Pool;
(_) -> {undefined, undefined}
end,
{PoolV4, NAT} = Maybe(select(random, FilterF(v4))),
{PoolV6, _} = Maybe(select(random, FilterF(v6))),

return({Node, VRF, PoolV4, NAT, PoolV6})
end
]).
select_ip_pool(Version, HasVersions, Pools) ->
case lists:member(Version, HasVersions) of
true -> select(random, Pools);
false -> undefined
end.

apn_filter(vrfs, V, F) ->
F#{vrf => V};
apn_filter(ip_pools, V, F) ->
F#{ip_pools => [[X] || X <- V]};
apn_filter(nat_port_blocks, V, F) ->
F#{nat_port_blocks => [[X] || X <- V]};
apn_filter(_, _, F) ->
F.

%% select_upf/3
select_upf(Candidates, Session0, APNOpts) ->
Wanted = maps:fold(fun apn_filter/3, #{}, APNOpts),
do([error_m ||
{_, _VRF, PoolV4, NAT, PoolV6} =
Node <- select_upf_pool(Candidates, APNOpts, []),
begin
Session1 = Session0#{'Framed-Pool' => PoolV4, 'Framed-IPv6-Pool' => PoolV6},
Session2 =
case maps:get(nat_port_blocks, APNOpts, []) of
Blocks when is_list(Blocks), length(Blocks) /= 0 ->
Session1#{'NAT-Pool-Id' => NAT};
_ ->
Session1
end,
Session = init_session_ue_ifid(APNOpts, Session2),
return({Node, Session})
end
{_, _, _, Pools} = Node <- select_by_caps(Wanted, undefined, Candidates),

#{ip_pools := IPpools, ip_versions := IPvs,
nat_port_blocks := NATblocks, vrf := VRF} = select(random, Pools),
PoolV4 = select_ip_pool(v4, IPvs, IPpools),
PoolV6 = select_ip_pool(v6, IPvs, IPpools),
NAT = select(random, NATblocks),

Session1 = Session0#{'Framed-Pool' => PoolV4, 'Framed-IPv6-Pool' => PoolV6},
Session2 = case maps:is_key(nat_port_blocks, Wanted) of
true -> Session1#{'NAT-Pool-Id' => NAT};
false -> Session1
end,
Session = init_session_ue_ifid(APNOpts, Session2),
UPinfo = {Node, VRF, PoolV4, NAT, PoolV6},

return({UPinfo, Session})
]).

filter([]) -> #{};
filter([{_, undefined}|T]) -> filter(T);
filter([{Ver, IP}|T]) when Ver == v4; Ver == v6 ->
maps:update_with(ip_versions, fun([X]) -> [[Ver|X]] end, [[Ver]],
maps:update_with(ip_pools, fun([X]) -> [lists:usort([IP|X])] end, [[IP]], filter(T)));
filter([{nat, NAT}|T]) ->
maps:put(nat_port_blocks, [[NAT]], filter(T)).

%% reselect_upf/4
reselect_upf(Candidates, Session, APNOpts, {_, _, PoolV4, NATBlock, PoolV6} = UPinfo) ->
reselect_upf(Candidates, Session, _APNOpts, {{NodeName, _, _, _} = Node0, VRF0, PoolV4, NATBlock, PoolV6}) ->
NAT = maps:get('NAT-Pool-Id', Session, undefined),
IP4 = maps:get('Framed-Pool', Session, undefined),
IP6 = maps:get('Framed-IPv6-Pool', Session, undefined),

do([error_m ||
{{Pid, NodeCaps, _}, VRF, _, _, _} <-
begin
if (IP4 /= PoolV4 orelse IP6 /= PoolV6 orelse
NATBlock /= NAT) ->
WantPools = [{IP4, v4, NAT} || is_binary(IP4)]
++ [{IP6, v6, undefined} || is_binary(IP6)],
select_upf_pool(Candidates, APNOpts, WantPools);
true ->
return(UPinfo)
end
{{_, Pid, NodeCaps, _}, VRF} <-
if (IP4 /= PoolV4 orelse IP6 /= PoolV6 orelse
NATBlock /= NAT) ->
Wanted = filter([{v4, IP4}, {v6, IP6}, {nat, NAT}]),
do([error_m ||
{_, _, _, Pools} = Node1 <- select_by_caps(Wanted, NodeName, Candidates),
#{vrf := VRF1} = select(random, Pools),
return({Node1, VRF1})
]);
true ->
return({Node0, VRF0})
end,
{PCtx, _} <- ergw_sx_node:attach(Pid),
return({PCtx, NodeCaps, #bearer{interface = 'SGi-LAN', vrf = VRF}})
]).

have_wanted_caps({WantVRF, WantIP, WantPool, WantNAT},
#{ip_pools := Pools, vrf := VRF, ip_versions := IPs,
nat_port_blocks := NATblocks}) ->
(VRF =:= '_' orelse WantVRF =:= VRF)
andalso (WantPool =:= '_' orelse lists:member(WantPool, Pools))
andalso (WantIP =:= '_' orelse lists:member(WantIP, IPs))
andalso (WantIP =/= v4 orelse
(WantNAT =:= undefined
orelse (WantNAT =:= '_' andalso NATblocks /= [])
orelse lists:member(WantNAT, NATblocks))).

common_caps_intersection([], _, Common) ->
Common;
common_caps_intersection([Want|More], HasCaps, Common0) ->
Common =
lists:filter(
fun(Has) -> have_wanted_caps(Want, Has) end, HasCaps),
common_caps_intersection(More, HasCaps, Common0 ++ Common).

common_caps_subset([], _, IsSubSet) ->
{IsSubSet, []};
common_caps_subset([Want|More], HasCaps, IsSubSet) ->
case common_caps_subset(More, HasCaps, IsSubSet) of
{true, SubSet} ->
SubS =
lists:filter(
fun(Has) -> have_wanted_caps(Want, Has) end, HasCaps),
{true andalso SubS /= [], SubSet ++ SubS};
Other ->
Other
end.
common_caps_f_pred(Has, Want)
when is_binary(Has), is_list(Want) ->
lists:member(Has, Want);
common_caps_f_pred(Has, WantAny)
when is_list(Has), is_list(WantAny) ->
fun Pred([]) -> false;
Pred([WantAll|T]) -> Pred(T) orelse WantAll -- Has =:= []
end(WantAny);
common_caps_f_pred(_, _) ->
false.

common_caps_f_walk(_, none) ->
true;
common_caps_f_walk(Pool, {K, V, Next})
when is_map_key(K, Pool) ->
case common_caps_f_pred(maps:get(K, Pool), V) of
true -> common_caps_f_walk(Pool, maps:next(Next));
false -> false
end;
common_caps_f_walk(_, _) ->
false.

common_caps_f(Wanted, Pool) ->
common_caps_f_walk(Pool, maps:next(maps:iterator(Wanted))).

common_caps_n(Wanted, NodePools) ->
lists:filter(common_caps_f(Wanted, _), NodePools).

%% common_caps/3
common_caps(WPools, HPools, true) ->
Pools = common_caps_intersection(WPools, HPools, []),
{length(Pools) /= 0, Pools};
common_caps(WPools, HPools, false) ->
common_caps_subset(WPools, HPools, true).

%% common_caps/5
common_caps(_, [], _Available, _AnyPool, Candidates) ->
Candidates;
common_caps(Wanted, [{Node, _, _, _, _} = UPF|Next], Available, AnyPool, Candidates)
common_caps_n(_, [], _) ->
[];
common_caps_n(Wanted, [{Node, _, _, _, _} = UPF|Next], Available)
when is_map_key(Node, Available) ->
{_, {_, NodePools}} = maps:get(Node, Available),
case common_caps(Wanted, NodePools, AnyPool) of
{true, _} ->
[UPF|common_caps(Wanted, Next, Available, AnyPool,Candidates)];
{false,_} ->
common_caps(Wanted, Next, Available, AnyPool, Candidates)
case common_caps_n(Wanted, NodePools) of
[] -> common_caps_n(Wanted, Next, Available);
_ -> [UPF|common_caps_n(Wanted, Next, Available)]
end;
common_caps(Wanted, [_|Next], Available, AnyPool, Candidates) ->
common_caps(Wanted, Next, Available, AnyPool, Candidates).
common_caps_n(Wanted, [_|Next], Available) ->
common_caps_n(Wanted, Next, Available).

%% select_by_caps/3
select_by_caps(Candidates, #{vrfs := VRFs, ip_pools := Pools} = APNopts, []) ->
NATpools = maps:get(nat_port_blocks, APNopts, [undefined]),
Wanted = [{VRF, '_', Pool, NAT} || VRF <- VRFs, Pool <- Pools, NAT <- NATpools],
filter_by_caps(Candidates, Wanted, true);

select_by_caps(Candidates, #{vrfs := VRFs}, WantPools) ->
Wanted = [{VRF, IP, Pool, NAT} ||
VRF <- VRFs, {Pool, IP, NAT} <- WantPools],
filter_by_caps(Candidates, Wanted, false).

%% filter_by_caps/3
filter_by_caps(Candidates, Wanted, AnyPool) ->
select_by_caps(Wanted, Preferred, Candidates) ->
Available = ergw_sx_node_reg:available(),
Eligible = common_caps(Wanted, Candidates, Available, AnyPool, []),
Eligible = common_caps_n(Wanted, Candidates, Available),

%% Note: common_caps/5 filters all **Available** nodes by capabilities first,
%% select_upf_with/3 can therefor simply take the node with the highest precedence.
select_upf_with(
fun(Node) -> filter_by_caps_f(Node, Wanted, AnyPool) end, Eligible, Available).
case lists:keymember(Preferred, 1, Eligible) of
true when is_map_key(Preferred, Available) ->
Node = maps:get(Preferred, Available),
filter_by_caps_f(Preferred, Wanted, Node);
_ ->
%% Note: common_caps/5 filters all **Available** nodes by capabilities first,
%% select_upf_with/3 can therefor simply take the node with the highest precedence.
select_upf_with(filter_by_caps_f(_, Wanted, _), Eligible, Available)
end.

%% filter_by_caps_f/1
filter_by_caps_f({Pid, {_, NodePools} = NodeCaps}, Wanted, AnyPool) ->
{_, SPools} = common_caps(Wanted, NodePools, AnyPool),
{ok, {Pid, NodeCaps, SPools}}.
%% filter_by_caps_f/2
filter_by_caps_f(NodeName, Wanted, {Pid, {_, NodePools} = NodeCaps}) ->
SPools = common_caps_n(Wanted, NodePools),
{ok, {NodeName, Pid, NodeCaps, SPools}}.

%%%===================================================================

Expand Down
12 changes: 9 additions & 3 deletions apps/ergw_core/src/tdf.erl
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,6 @@ start_session(#data{apn = APN, context = Context, dp_node = Node,
{error, Err1} -> throw(Err1#ctx_err{context = Context})
end,

VRF = ergw_gsn_lib:select_vrf(NodeCaps, APN),
Bearer1 = maps:update_with(right, _#bearer{vrf = VRF}, Bearer0),

Now = erlang:monotonic_time(),
SOpts = #{now => Now},

Expand All @@ -395,6 +392,15 @@ start_session(#data{apn = APN, context = Context, dp_node = Node,
{ok, Result2} -> Result2;
{error, Err2} -> throw({fail, Err2})
end,

VRF =
case ergw_gsn_lib:select_vrf(NodeCaps, APN, SessionOpts1) of
{ok, SelVRF} -> SelVRF;
{error, _} -> Err2a = ?CTX_ERR(?FATAL, system_failure),
throw(Err2a#ctx_err{context = Context})
end,

Bearer1 = maps:update_with(right, _#bearer{vrf = VRF}, Bearer0),
Bearer2 = apply_bearer_opts(SessionOpts1, Bearer1),

%% -----------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions apps/ergw_core/test/ergw_pgw_test_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,7 @@ apn(invalid_apn) -> [<<"IN", "VA", "LID">>];
apn(dotted_apn) -> ?'APN-EXA.MPLE';
apn(proxy_apn) -> ?'APN-PROXY';
apn(async_sx) -> [<<"async-sx">>];
apn(multi_vrf) -> [<<"multi-vrf">>];
apn({_, _, APN})
when APN =:= v4only; APN =:= prefV4;
APN =:= v6only; APN =:= prefV6 ->
Expand Down
Loading