diff --git a/apps/ergw_core/src/ergw_gtp_gsn_lib.erl b/apps/ergw_core/src/ergw_gtp_gsn_lib.erl index 9175fcc5..17f1e6a5 100644 --- a/apps/ergw_core/src/ergw_gtp_gsn_lib.erl +++ b/apps/ergw_core/src/ergw_gtp_gsn_lib.erl @@ -11,7 +11,7 @@ {parse_transform, cut}]). -export([connect_upf_candidates/4, create_session/10]). --export([triggered_charging_event/4, usage_report/3, close_context/3]). +-export([triggered_charging_event/4, usage_report/3, close_context/3, close_context/4]). -export([update_tunnel_endpoint/3, handle_peer_change/3, update_tunnel_endpoint/2, apply_bearer_change/5]). @@ -232,15 +232,20 @@ usage_report(URRActions, UsageReport, #{pfcp := PCtx, 'Session' := Session}) -> %% close_context/3 close_context(_, {API, TermCause}, Context) -> close_context(API, TermCause, Context); -close_context(API, TermCause, #{pfcp := PCtx, 'Session' := Session} = Data) +close_context(API, TermCause, #{pfcp := PCtx} = Data) when is_atom(TermCause) -> UsageReport = ergw_pfcp_context:delete_session(TermCause, PCtx), - ergw_gtp_gsn_session:close_context(TermCause, UsageReport, PCtx, Session), - ergw_prometheus:termination_cause(API, TermCause), - maps:remove(pfcp, Data); + close_context(API, TermCause, UsageReport, Data); close_context(_API, _TermCause, Data) -> Data. +%% close_context/4 +close_context(API, TermCause, UsageReport, #{pfcp := PCtx, 'Session' := Session} = Data) + when is_atom(TermCause) -> + ergw_gtp_gsn_session:close_context(TermCause, UsageReport, PCtx, Session), + ergw_prometheus:termination_cause(API, TermCause), + maps:remove(pfcp, Data). + %%==================================================================== %% Helper %%==================================================================== diff --git a/apps/ergw_core/src/gtp_context.erl b/apps/ergw_core/src/gtp_context.erl index 7a245990..2cf30161 100644 --- a/apps/ergw_core/src/gtp_context.erl +++ b/apps/ergw_core/src/gtp_context.erl @@ -387,6 +387,23 @@ handle_event({call, From}, {next_state, shutdown, Data, [{reply, From, {ok, PCtx}}]} end; +%% PFCP Session Deleted By the UP function +handle_event({call, From}, + {sx, #pfcp{type = session_report_request, + ie = #{pfcpsrreq_flags := #pfcpsrreq_flags{psdbu = 1}, + report_type := ReportType} = IEs}}, + _State, #{pfcp := PCtx} = Data0) -> + TermCause = + case ReportType of + #report_type{upir = 1} -> + up_inactivity_timeout; + _ -> + deleted_by_upf + end, + UsageReport = maps:get(usage_report_srr, IEs, undefined), + Data = ergw_gtp_gsn_lib:close_context('pfcp', TermCause, UsageReport, Data0), + {next_state, shutdown, Data, [{reply, From, {ok, PCtx}}]}; + %% User Plane Inactivity Timer expired handle_event({call, From}, {sx, #pfcp{type = session_report_request, diff --git a/apps/ergw_core/test/ergw_test_sx_up.erl b/apps/ergw_core/test/ergw_test_sx_up.erl index 6cdd9e52..14d88282 100644 --- a/apps/ergw_core/test/ergw_test_sx_up.erl +++ b/apps/ergw_core/test/ergw_test_sx_up.erl @@ -6,7 +6,7 @@ %% API -export([start/2, stop/1, restart/1, - send/2, send/3, usage_report/4, + send/2, send/3, usage_report/4, usage_report/5, up_inactivity_timer_expiry/2, up_quota_validity_time_expiry/2, reset/1, history/1, history/2, @@ -73,7 +73,10 @@ send(Role, SEID, Msg) -> gen_server:call(server_name(Role), {send, SEID, Msg}). usage_report(Role, PCtx, MatchSpec, Report) -> - gen_server:call(server_name(Role), {usage_report, PCtx, MatchSpec, Report}). + usage_report(Role, PCtx, MatchSpec, [], Report). + +usage_report(Role, PCtx, MatchSpec, IEs, Report) -> + gen_server:call(server_name(Role), {usage_report, PCtx, MatchSpec, IEs, Report}). up_inactivity_timer_expiry(Role, PCtx) -> gen_server:call(server_name(Role), {up_inactivity_timer_expiry, PCtx}). @@ -215,7 +218,7 @@ handle_call({send, Msg}, _From, {reply, ok, State}; handle_call({usage_report, #pfcp_ctx{seid = #seid{cp = SEID}, urr_by_id = Rules}, - MatchSpec, Report}, _From, State0) -> + MatchSpec, IEs0, Report}, _From, State0) -> Ids = ets:match_spec_run(maps:to_list(Rules), ets:match_spec_compile(MatchSpec)), URRs = if is_function(Report, 2) -> @@ -223,7 +226,7 @@ handle_call({usage_report, #pfcp_ctx{seid = #seid{cp = SEID}, urr_by_id = Rules} true -> [#usage_report_srr{group = [#urr_id{id = Id}|Report]} || Id <- Ids] end, - IEs = [#report_type{usar = 1}|URRs], + IEs = IEs0 ++ [#report_type{usar = 1}|URRs], SRreq = #pfcp{version = v1, type = session_report_request, ie = IEs}, State = do_send(SEID, SRreq, State0), {reply, ok, State}; diff --git a/apps/ergw_core/test/pgw_SUITE.erl b/apps/ergw_core/test/pgw_SUITE.erl index b60f3be7..7c56ad3a 100644 --- a/apps/ergw_core/test/pgw_SUITE.erl +++ b/apps/ergw_core/test/pgw_SUITE.erl @@ -697,6 +697,7 @@ common() -> sx_upf_restart, sx_timeout, sx_ondemand, + pfcp_session_deleted_by_the_up_function, gy_validity_timer_cp, gy_validity_timer_up, simple_aaa, @@ -2941,6 +2942,45 @@ sx_ondemand(Config) -> ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), wait4contexts(?TIMEOUT). +%%-------------------------------------------------------------------- +pfcp_session_deleted_by_the_up_function() -> + [{doc, "Test TEBUR with PSDBU (Ts 29.244, clause 5.18"}]. +pfcp_session_deleted_by_the_up_function(Config) -> + CtxKey = #context_key{socket = 'irx-socket', id = {imsi, ?'IMSI', 5}}, + + {GtpC, _, _} = create_session(Config), + + {_Handler, Server} = gtp_context_reg:lookup(CtxKey), + true = is_pid(Server), + {ok, PCtx} = gtp_context:test_cmd(Server, pfcp_ctx), + + MatchSpec = ets:fun2ms(fun({Id, _}) -> Id end), + IEs = [#pfcpsrreq_flags{psdbu = 1}], + Report = + [#usage_report_trigger{tebur = 1, _= 0}, + #volume_measurement{total = 5, uplink = 2, downlink = 3}, + #tp_packet_measurement{total = 12, uplink = 5, downlink = 7}], + + lists:foreach( + fun(_X) -> + ct:sleep(100), + ergw_test_sx_up:usage_report('pgw-u01', PCtx, MatchSpec, IEs, Report) + end, lists:seq(1, 3)), + + ct:sleep(100), + delete_session(not_found, GtpC), + + ?equal(true, meck:called(ergw_aaa_session, invoke, ['_', '_', {gx, 'CCR-Terminate'}, '_'])), + ?equal(true, meck:called(ergw_aaa_session, invoke, ['_', '_', {gy, 'CCR-Terminate'}, '_'])), + ?equal(true, meck:called(ergw_aaa_session, invoke, ['_', '_', stop, '_'])), + + ?equal([], outstanding_requests()), + ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), + wait4contexts(?TIMEOUT), + + meck_validate(Config), + ok. + %%-------------------------------------------------------------------- gy_validity_timer_cp() ->