Skip to content

Commit 0de9874

Browse files
authored
Fixes after restoration (#637)
* Stale value in SBIPFCPCommunicationChan after restoration Signed-off-by: Arrobo, Gabriel <[email protected]> * Added a recover() around the goroutine body Signed-off-by: Arrobo, Gabriel <[email protected]> * Remove duplicate logging Signed-off-by: Arrobo, Gabriel <[email protected]> * Address Copilot's comments Signed-off-by: Arrobo, Gabriel <[email protected]> --------- Signed-off-by: Arrobo, Gabriel <[email protected]>
1 parent 7bd9ee9 commit 0de9874

5 files changed

Lines changed: 140 additions & 11 deletions

File tree

consumer/nf_management.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -747,12 +747,6 @@ func SendCreateSubscription(nrfUri string, nrfSubscriptionData models.Subscripti
747747
if err == nil {
748748
return nrfSubData, nil, nil
749749
} else if res != nil {
750-
// Logged for every error response now, not only for the ones the removed guard let
751-
// through. Warn rather than Error: with the guard gone, a response whose body decodes is
752-
// returned to the caller as problem details and a nil error, so the caller decides whether
753-
// that is an error and reports it. Logging it as one here would contradict the nil error
754-
// and duplicate the caller's own line.
755-
logger.ConsumerLog.Warnf("SendCreateSubscription received error response: %v", res.Status)
756750
if problem, handledErr := util.HandleOpenAPIError(err); problem != nil {
757751
return nrfSubData, problem, nil
758752
} else if handledErr != nil {

pfcp/adapter/adapter.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,12 +323,20 @@ func HandlePfcpSessionEstablishmentResponse(msg *udp.Message) {
323323
logger.PfcpLog.Errorf("pfcp session establishment response cause error: %v", err)
324324
return
325325
}
326+
// Gated on the state, like the modification and release handlers. Restoration issues an
327+
// establishment without waiting on this channel, so an unconditional send here would leave a
328+
// stale value for whichever unrelated modification or release next waits on it.
329+
awaited := smContext.SMContextState == context.SmStatePfcpCreatePending
326330
// UPF Accept
327331
if causeValue == ie.CauseRequestAccepted {
328-
smContext.SBIPFCPCommunicationChan <- context.SessionEstablishSuccess
332+
if awaited {
333+
smContext.SBIPFCPCommunicationChan <- context.SessionEstablishSuccess
334+
}
329335
smContext.SubPfcpLog.Infof("PFCP Session Establishment accepted")
330336
} else {
331-
smContext.SBIPFCPCommunicationChan <- context.SessionEstablishFailed
337+
if awaited {
338+
smContext.SBIPFCPCommunicationChan <- context.SessionEstablishFailed
339+
}
332340
smContext.SubPfcpLog.Errorf("PFCP Session Establishment rejected with cause [%v]", causeValue)
333341
if causeValue == ie.CauseNoEstablishedPFCPAssociation {
334342
SetUpfInactive(*rspNodeID)

pfcp/handler/handler.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,19 @@ func HandlePfcpSessionEstablishmentResponse(msg *udp.Message) {
538538
logger.PfcpLog.Errorf("failed to parse Cause IE: %+v", err)
539539
return
540540
}
541+
// Gated on the state, like the modification and release handlers below. Restoration issues
542+
// an establishment without waiting on this channel, so an unconditional send here would leave
543+
// a stale value for whichever unrelated modification or release next waits on it.
544+
awaited := smContext.SMContextState == smf_context.SmStatePfcpCreatePending
541545
if causeValue == ie.CauseRequestAccepted {
542-
smContext.SBIPFCPCommunicationChan <- smf_context.SessionEstablishSuccess
546+
if awaited {
547+
smContext.SBIPFCPCommunicationChan <- smf_context.SessionEstablishSuccess
548+
}
543549
smContext.SubPfcpLog.Infoln("PFCP Session Establishment accepted")
544550
} else {
545-
smContext.SBIPFCPCommunicationChan <- smf_context.SessionEstablishFailed
551+
if awaited {
552+
smContext.SBIPFCPCommunicationChan <- smf_context.SessionEstablishFailed
553+
}
546554
smContext.SubPfcpLog.Errorf("PFCP Session Establishment rejected with cause [%v]", causeValue)
547555
if causeValue == ie.CauseNoEstablishedPFCPAssociation {
548556
SetUpfInactive(*rspNodeID, msg.PfcpMessage.MessageTypeName())

pfcp/handler/handler_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,113 @@ func TestHandlePfcpSessionEstablishmentResponseNilTunnel(t *testing.T) {
284284
t.Errorf("expected pending PFCP txn for seq %d to be consumed on the nil-Tunnel ignore path, but it was still present", seq)
285285
}
286286
}
287+
288+
// TestHandlePfcpSessionEstablishmentResponseChannelGatedByState covers the SMContextState gate on
289+
// SBIPFCPCommunicationChan: the normal establishment path waits in SmStatePfcpCreatePending and
290+
// must receive the signal, while restoration's reissue leaves the context in some other state and
291+
// must not receive it (an unconditional send would leave a stale value for the next unrelated
292+
// modification or release that waits on the channel).
293+
func TestHandlePfcpSessionEstablishmentResponseChannelGatedByState(t *testing.T) {
294+
for _, tc := range []struct {
295+
name string
296+
imsi string
297+
state context.SMContextState
298+
wantSignal bool
299+
}{
300+
{
301+
name: "awaited establishment sends the signal",
302+
imsi: "imsi-100000000000001",
303+
state: context.SmStatePfcpCreatePending,
304+
wantSignal: true,
305+
},
306+
{
307+
name: "unawaited response (e.g. restoration) withholds the signal",
308+
imsi: "imsi-100000000000002",
309+
state: context.SmStateActive,
310+
wantSignal: false,
311+
},
312+
} {
313+
t.Run(tc.name, func(t *testing.T) {
314+
// AllocateLocalSEID reads factory.SmfConfig.Configuration.EnableDbStore, so the config
315+
// must be initialized for the SEID allocation path not to panic when this test runs in
316+
// isolation.
317+
if factory.SmfConfig.Configuration == nil {
318+
factory.SmfConfig = factory.Config{
319+
Configuration: &factory.Configuration{
320+
KafkaInfo: factory.KafkaInfo{EnableKafka: boolPointer(false)},
321+
EnableUpfAdapter: false,
322+
},
323+
}
324+
}
325+
326+
nodeID := context.NewNodeID("1.1.1.1")
327+
smContext := context.NewSMContext(tc.imsi, 10)
328+
smContext.SMContextState = tc.state
329+
330+
smContext.Tunnel = &context.UPTunnel{
331+
DataPathPool: context.DataPathPool{
332+
10: &context.DataPath{
333+
IsDefaultPath: true,
334+
FirstDPNode: &context.DataPathNode{
335+
UPF: &context.UPF{NodeID: *nodeID},
336+
},
337+
},
338+
},
339+
}
340+
341+
datapath := &context.DataPath{
342+
FirstDPNode: &context.DataPathNode{
343+
UPF: &context.UPF{NodeID: *nodeID},
344+
},
345+
}
346+
smContext.AllocateLocalSEIDForDataPath(datapath)
347+
348+
var localSEID uint64
349+
for _, pfcpCtx := range smContext.PFCPContext {
350+
if pfcpCtx.LocalSEID != 0 {
351+
localSEID = pfcpCtx.LocalSEID
352+
}
353+
}
354+
if localSEID == 0 {
355+
t.Fatal("failed to allocate a local SEID for the test SMContext")
356+
}
357+
358+
seq := uint32(localSEID)
359+
pfcp_message.InsertPfcpTxn(seq, nodeID)
360+
361+
rsp := message.NewSessionEstablishmentResponse(
362+
0,
363+
0,
364+
localSEID,
365+
seq,
366+
0,
367+
ie.NewCause(ie.CauseRequestAccepted),
368+
ie.NewNodeID("1.1.1.1", "", ""),
369+
ie.NewRecoveryTimeStamp(time.Now()),
370+
)
371+
372+
udpMessage := udp.Message{
373+
RemoteAddr: &net.UDPAddr{
374+
IP: net.ParseIP("1.1.1.1"),
375+
Port: 8809,
376+
},
377+
PfcpMessage: rsp,
378+
}
379+
380+
handler.HandlePfcpSessionEstablishmentResponse(&udpMessage)
381+
382+
select {
383+
case status := <-smContext.SBIPFCPCommunicationChan:
384+
if !tc.wantSignal {
385+
t.Errorf("expected no send to SBIPFCPCommunicationChan when SMContextState is %v, got signal %v", tc.state, status)
386+
} else if status != context.SessionEstablishSuccess {
387+
t.Errorf("expected SessionEstablishSuccess, got %v", status)
388+
}
389+
default:
390+
if tc.wantSignal {
391+
t.Error("expected a send to SBIPFCPCommunicationChan when SMContextState is SmStatePfcpCreatePending, got none")
392+
}
393+
}
394+
})
395+
}
396+
}

producer/restoration.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010
"net"
11+
"runtime/debug"
1112
"sync"
1213
"time"
1314

@@ -182,6 +183,14 @@ func RestoreSessionsOnUPF(nodeID context.NodeID, recovery time.Time) {
182183

183184
go func() {
184185
defer restorationsInProgress.CompareAndDelete(nodeIP, run)
186+
// A panic here must not escape: this repairs one restarted UPF on a background goroutine,
187+
// and an unrecovered panic in any goroutine takes down the whole process -- turning one UPF's
188+
// restart into an SMF restart that loses every session on every UPF, not just this one.
189+
defer func() {
190+
if r := recover(); r != nil {
191+
logger.PfcpLog.Errorf("UPF[%s] restoration panicked and was abandoned: %v\n%s", nodeIP, r, debug.Stack())
192+
}
193+
}()
185194
restoreSessions(nodeID, nodeIP, run)
186195
}()
187196
}
@@ -804,7 +813,7 @@ func resolveUnrestorable(unrestored []*context.SMContext, nodeIP string) (notRel
804813
func releaseOneSession(smContext *context.SMContext) (err error) {
805814
defer func() {
806815
if r := recover(); r != nil {
807-
err = fmt.Errorf("releasing the session panicked: %v", r)
816+
err = fmt.Errorf("releasing the session panicked: %v\n%s", r, debug.Stack())
808817
}
809818
}()
810819
return releaseUnrestorableSession(smContext)

0 commit comments

Comments
 (0)