From 690c8a093e84bceab94acefeaea612c1c1cbcbb6 Mon Sep 17 00:00:00 2001 From: SAILESH VVR Date: Sat, 14 Sep 2024 16:36:04 +0000 Subject: [PATCH 1/5] feat: SMF to support SDM Subscription and Unsubscription for UE Session --- internal/context/context.go | 4 + internal/context/sm_ue.go | 100 +++++++++++++++++++++++++ internal/sbi/consumer/udm_service.go | 103 ++++++++++++++++++++++++++ internal/sbi/processor/pdu_session.go | 38 ++++++++++ internal/sbi/processor/sm_common.go | 9 +++ 5 files changed, 254 insertions(+) create mode 100644 internal/context/sm_ue.go diff --git a/internal/context/context.go b/internal/context/context.go index f8ab9b63..6f8751c4 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -79,6 +79,8 @@ type SMFContext struct { // Each pdu session should have a unique charging id ChargingIDGenerator *idgenerator.IDGenerator + + Ues *Ues } func GenerateChargingID() int32 { @@ -248,6 +250,8 @@ func InitSmfContext(config *factory.Config) { smfContext.Locality = configuration.Locality TeidGenerator = idgenerator.NewGenerator(1, math.MaxUint32) + + smfContext.Ues = InitSmfUeData() } func InitSMFUERouting(routingConfig *factory.RoutingConfig) { diff --git a/internal/context/sm_ue.go b/internal/context/sm_ue.go new file mode 100644 index 00000000..364769a9 --- /dev/null +++ b/internal/context/sm_ue.go @@ -0,0 +1,100 @@ +package context + +import "sync" + +type UeData struct { + PduSessionCount int // store number of PDU Sessions for each UE + SdmSubscriptionId string // store SDM Subscription ID per UE +} + +type Ues struct { + ues map[string]UeData // map to store UE data with SUPI as key + mu sync.Mutex // mutex for concurrent access +} + +func InitSmfUeData() *Ues { + return &Ues{ + ues: make(map[string]UeData), + } +} + +// IncrementPduSessionCount increments the PDU session count for a given UE. +func (u *Ues) IncrementPduSessionCount(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + ueData.PduSessionCount++ + u.ues[ueId] = ueData +} + +// DecrementPduSessionCount decrements the PDU session count for a given UE. +func (u *Ues) DecrementPduSessionCount(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + if ueData.PduSessionCount > 0 { + ueData.PduSessionCount-- + u.ues[ueId] = ueData + } +} + +// SetSubscriptionId sets the SDM subscription ID for a given UE. +func (u *Ues) SetSubscriptionId(ueId, subscriptionId string) { + u.mu.Lock() + defer u.mu.Unlock() + + ueData := u.ues[ueId] + ueData.SdmSubscriptionId = subscriptionId + u.ues[ueId] = ueData +} + +// GetSubscriptionId returns the SDM subscription ID for a given UE. +func (u *Ues) GetSubscriptionId(ueId string) string { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId].SdmSubscriptionId +} + +// GetUeData returns the data for a given UE. +func (u *Ues) GetUeData(ueId string) UeData { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId] +} + +// DeleteUe deletes a UE. +func (u *Ues) DeleteUe(ueId string) { + u.mu.Lock() + defer u.mu.Unlock() + + delete(u.ues, ueId) +} + +// UeExists checks if a UE already exists. +func (u *Ues) UeExists(ueId string) bool { + u.mu.Lock() + defer u.mu.Unlock() + + _, exists := u.ues[ueId] + return exists +} + +// IsLastPduSession checks if it is the last PDU session for a given UE. +func (u *Ues) IsLastPduSession(ueID string) bool { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueID].PduSessionCount == 1 +} + +// GetPduSessionCount returns the number of sessions for a given UE. +func (u *Ues) GetPduSessionCount(ueId string) int { + u.mu.Lock() + defer u.mu.Unlock() + + return u.ues[ueId].PduSessionCount +} diff --git a/internal/sbi/consumer/udm_service.go b/internal/sbi/consumer/udm_service.go index 37948d98..629e5e1c 100644 --- a/internal/sbi/consumer/udm_service.go +++ b/internal/sbi/consumer/udm_service.go @@ -199,3 +199,106 @@ func (s *nudmService) GetSmData(ctx context.Context, supi string, return sessSubData, rsp, err } + +func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContext, smPlmnID *models.PlmnId) ( + *models.ProblemDetails, error, +) { + var client *Nudm_SubscriberDataManagement.APIClient + for _, service := range *s.consumer.Context().UDMProfile.NfServices { + if service.ServiceName == models.ServiceName_NUDM_SDM { + SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() + SDMConf.SetBasePath(service.ApiPrefix) + client = s.getSubscribeDataManagementClient(service.ApiPrefix) + } + } + + if client == nil { + return nil, fmt.Errorf("sdm client failed") + } + + sdmSubscription := models.SdmSubscription{ + NfInstanceId: smf_context.GetSelf().NfInstanceID, + PlmnId: smPlmnID, + } + + resSubscription, httpResp, localErr := client.SubscriptionCreationApi.Subscribe( + ctx, smCtx.Supi, sdmSubscription) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.ConsumerLog.Errorf("Subscribe response body cannot close: %+v", + rspCloseErr) + } + } + }() + + if localErr == nil { + smf_context.GetSelf().Ues.SetSubscriptionId(smCtx.Supi, resSubscription.SubscriptionId) + logger.PduSessLog.Infoln("SDM Subscription Successful UE:", smCtx.Supi, "SubscriptionId:", + resSubscription.SubscriptionId) + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + return nil, localErr + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + return &problem, nil + } else { + return nil, openapi.ReportError("server no response") + } + + smf_context.GetSelf().Ues.IncrementPduSessionCount(smCtx.Supi) + return nil, nil +} + +func (s *nudmService) UnSubscribe(smCtx *smf_context.SMContext) ( + *models.ProblemDetails, error, +) { + ctx, _, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) + if oauthErr != nil { + return nil, fmt.Errorf("Get Token Context Error[%v]", oauthErr) + } + + if smf_context.GetSelf().Ues.IsLastPduSession(smCtx.Supi) { + var client *Nudm_SubscriberDataManagement.APIClient + for _, service := range *s.consumer.Context().UDMProfile.NfServices { + if service.ServiceName == models.ServiceName_NUDM_SDM { + SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() + SDMConf.SetBasePath(service.ApiPrefix) + client = s.getSubscribeDataManagementClient(service.ApiPrefix) + } + } + + if client == nil { + return nil, fmt.Errorf("sdm client failed") + } + + subscriptionId := smf_context.GetSelf().Ues.GetSubscriptionId(smCtx.Supi) + + httpResp, localErr := client.SubscriptionDeletionApi.Unsubscribe(ctx, smCtx.Supi, subscriptionId) + defer func() { + if httpResp != nil { + if rspCloseErr := httpResp.Body.Close(); rspCloseErr != nil { + logger.PduSessLog.Errorf("Unsubscribe response body cannot close: %+v", + rspCloseErr) + } + } + }() + if localErr == nil { + logger.PduSessLog.Infoln("SDM UnSubscription Successful UE:", smCtx.Supi, "SubscriptionId:", + subscriptionId) + } else if httpResp != nil { + if httpResp.Status != localErr.Error() { + return nil, localErr + } + problem := localErr.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + return &problem, nil + } else { + return nil, openapi.ReportError("server no response") + } + smf_context.GetSelf().Ues.DeleteUe(smCtx.Supi) + } else { + smf_context.GetSelf().Ues.DecrementPduSessionCount(smCtx.Supi) + } + + return nil, nil +} diff --git a/internal/sbi/processor/pdu_session.go b/internal/sbi/processor/pdu_session.go index 38e31454..550335bb 100644 --- a/internal/sbi/processor/pdu_session.go +++ b/internal/sbi/processor/pdu_session.go @@ -121,6 +121,17 @@ func (p *Processor) HandlePDUSessionSMContextCreate( } } + if !smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + if problemDetails, err := p.Consumer(). + Subscribe(ctx, smContext, smPlmnID); problemDetails != nil { + smContext.Log.Errorln("SDM Subscription Failed Problem:", problemDetails) + } else if err != nil { + smContext.Log.Errorln("SDM Subscription Error:", err) + } + } else { + smf_context.GetSelf().Ues.IncrementPduSessionCount(smContext.Supi) + } + establishmentRequest := m.PDUSessionEstablishmentRequest if err := HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest); err != nil { smContext.Log.Errorf("PDU Session Establishment fail by %s", err) @@ -325,6 +336,15 @@ func (p *Processor) HandlePDUSessionSMContextUpdate( } } + if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + problemDetails, clientErr := p.Consumer().UnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if clientErr != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Error[%+v]", err) + } + } + if smContext.UeCmRegistered { problemDetails, errUeCmDeregistration := p.Consumer().UeCmDeregistration(smContext) if problemDetails != nil { @@ -878,6 +898,15 @@ func (p *Processor) HandlePDUSessionSMContextRelease( } } + if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + problemDetails, err := p.Consumer().UnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Error[%+v]", err) + } + } + if smContext.UeCmRegistered { problemDetails, err := p.Consumer().UeCmDeregistration(smContext) if problemDetails != nil { @@ -969,6 +998,15 @@ func (p *Processor) HandlePDUSessionSMContextLocalRelease( } } + if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + problemDetails, err := p.Consumer().UnSubscribe(smContext) + if problemDetails != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.PduSessLog.Errorf("SDM UnSubscription Error[%+v]", err) + } + } + if smContext.UeCmRegistered { problemDetails, err := p.Consumer().UeCmDeregistration(smContext) if problemDetails != nil { diff --git a/internal/sbi/processor/sm_common.go b/internal/sbi/processor/sm_common.go index adad74b8..9d2ba93a 100644 --- a/internal/sbi/processor/sm_common.go +++ b/internal/sbi/processor/sm_common.go @@ -15,6 +15,15 @@ func (p *Processor) RemoveSMContextFromAllNF(smContext *smf_context.SMContext, s } } + if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + problemDetails, err := p.Consumer().UnSubscribe(smContext) + if problemDetails != nil { + smContext.Log.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + smContext.Log.Errorf("SDM UnSubscription Error[%+v]", err) + } + } + // Because the amfUE who called this SMF API is being locked until the API Handler returns, // sending SMContext Status Notification should run asynchronously // so that this function returns immediately. From 1ac8c65dd0ed864fe9baec40678a722bebb63def Mon Sep 17 00:00:00 2001 From: "CTFang@WireLab" Date: Mon, 30 Sep 2024 03:34:56 +0000 Subject: [PATCH 2/5] fix: comply new NF structure --- internal/sbi/consumer/udm_service.go | 22 ++++++++++++++-------- internal/sbi/processor/pdu_session.go | 8 ++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/internal/sbi/consumer/udm_service.go b/internal/sbi/consumer/udm_service.go index 629e5e1c..4ffb4032 100644 --- a/internal/sbi/consumer/udm_service.go +++ b/internal/sbi/consumer/udm_service.go @@ -179,6 +179,9 @@ func (s *nudmService) GetSmData(ctx context.Context, supi string, SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() SDMConf.SetBasePath(service.ApiPrefix) client = s.getSubscribeDataManagementClient(service.ApiPrefix) + if client != nil { + break + } } } @@ -209,6 +212,9 @@ func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContex SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() SDMConf.SetBasePath(service.ApiPrefix) client = s.getSubscribeDataManagementClient(service.ApiPrefix) + if client != nil { + break + } } } @@ -217,7 +223,7 @@ func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContex } sdmSubscription := models.SdmSubscription{ - NfInstanceId: smf_context.GetSelf().NfInstanceID, + NfInstanceId: s.consumer.Context().NfInstanceID, PlmnId: smPlmnID, } @@ -233,7 +239,7 @@ func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContex }() if localErr == nil { - smf_context.GetSelf().Ues.SetSubscriptionId(smCtx.Supi, resSubscription.SubscriptionId) + s.consumer.Context().Ues.SetSubscriptionId(smCtx.Supi, resSubscription.SubscriptionId) logger.PduSessLog.Infoln("SDM Subscription Successful UE:", smCtx.Supi, "SubscriptionId:", resSubscription.SubscriptionId) } else if httpResp != nil { @@ -246,19 +252,19 @@ func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContex return nil, openapi.ReportError("server no response") } - smf_context.GetSelf().Ues.IncrementPduSessionCount(smCtx.Supi) + s.consumer.Context().Ues.IncrementPduSessionCount(smCtx.Supi) return nil, nil } func (s *nudmService) UnSubscribe(smCtx *smf_context.SMContext) ( *models.ProblemDetails, error, ) { - ctx, _, oauthErr := smf_context.GetSelf().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) + ctx, _, oauthErr := s.consumer.Context().GetTokenCtx(models.ServiceName_NUDM_SDM, models.NfType_UDM) if oauthErr != nil { return nil, fmt.Errorf("Get Token Context Error[%v]", oauthErr) } - if smf_context.GetSelf().Ues.IsLastPduSession(smCtx.Supi) { + if s.consumer.Context().Ues.IsLastPduSession(smCtx.Supi) { var client *Nudm_SubscriberDataManagement.APIClient for _, service := range *s.consumer.Context().UDMProfile.NfServices { if service.ServiceName == models.ServiceName_NUDM_SDM { @@ -272,7 +278,7 @@ func (s *nudmService) UnSubscribe(smCtx *smf_context.SMContext) ( return nil, fmt.Errorf("sdm client failed") } - subscriptionId := smf_context.GetSelf().Ues.GetSubscriptionId(smCtx.Supi) + subscriptionId := s.consumer.Context().Ues.GetSubscriptionId(smCtx.Supi) httpResp, localErr := client.SubscriptionDeletionApi.Unsubscribe(ctx, smCtx.Supi, subscriptionId) defer func() { @@ -295,9 +301,9 @@ func (s *nudmService) UnSubscribe(smCtx *smf_context.SMContext) ( } else { return nil, openapi.ReportError("server no response") } - smf_context.GetSelf().Ues.DeleteUe(smCtx.Supi) + s.consumer.Context().Ues.DeleteUe(smCtx.Supi) } else { - smf_context.GetSelf().Ues.DecrementPduSessionCount(smCtx.Supi) + s.consumer.Context().Ues.DecrementPduSessionCount(smCtx.Supi) } return nil, nil diff --git a/internal/sbi/processor/pdu_session.go b/internal/sbi/processor/pdu_session.go index 550335bb..4e7cdbde 100644 --- a/internal/sbi/processor/pdu_session.go +++ b/internal/sbi/processor/pdu_session.go @@ -121,7 +121,7 @@ func (p *Processor) HandlePDUSessionSMContextCreate( } } - if !smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + if !p.Context().Ues.UeExists(smContext.Supi) { if problemDetails, err := p.Consumer(). Subscribe(ctx, smContext, smPlmnID); problemDetails != nil { smContext.Log.Errorln("SDM Subscription Failed Problem:", problemDetails) @@ -129,7 +129,7 @@ func (p *Processor) HandlePDUSessionSMContextCreate( smContext.Log.Errorln("SDM Subscription Error:", err) } } else { - smf_context.GetSelf().Ues.IncrementPduSessionCount(smContext.Supi) + p.Context().Ues.IncrementPduSessionCount(smContext.Supi) } establishmentRequest := m.PDUSessionEstablishmentRequest @@ -898,7 +898,7 @@ func (p *Processor) HandlePDUSessionSMContextRelease( } } - if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + if p.Context().Ues.UeExists(smContext.Supi) { problemDetails, err := p.Consumer().UnSubscribe(smContext) if problemDetails != nil { logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) @@ -998,7 +998,7 @@ func (p *Processor) HandlePDUSessionSMContextLocalRelease( } } - if smf_context.GetSelf().Ues.UeExists(smContext.Supi) { + if p.Context().Ues.UeExists(smContext.Supi) { problemDetails, err := p.Consumer().UnSubscribe(smContext) if problemDetails != nil { logger.PduSessLog.Errorf("SDM UnSubscription Failed Problem[%+v]", problemDetails) From eb404de2400d1afdf54ddc9c1a4ce44542b5ac81 Mon Sep 17 00:00:00 2001 From: "CTFang@WireLab" Date: Mon, 30 Sep 2024 03:50:06 +0000 Subject: [PATCH 3/5] fix: remove unused code --- internal/sbi/consumer/udm_service.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/sbi/consumer/udm_service.go b/internal/sbi/consumer/udm_service.go index 4ffb4032..64b3e73c 100644 --- a/internal/sbi/consumer/udm_service.go +++ b/internal/sbi/consumer/udm_service.go @@ -176,8 +176,6 @@ func (s *nudmService) GetSmData(ctx context.Context, supi string, var client *Nudm_SubscriberDataManagement.APIClient for _, service := range *s.consumer.Context().UDMProfile.NfServices { if service.ServiceName == models.ServiceName_NUDM_SDM { - SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() - SDMConf.SetBasePath(service.ApiPrefix) client = s.getSubscribeDataManagementClient(service.ApiPrefix) if client != nil { break @@ -209,8 +207,6 @@ func (s *nudmService) Subscribe(ctx context.Context, smCtx *smf_context.SMContex var client *Nudm_SubscriberDataManagement.APIClient for _, service := range *s.consumer.Context().UDMProfile.NfServices { if service.ServiceName == models.ServiceName_NUDM_SDM { - SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() - SDMConf.SetBasePath(service.ApiPrefix) client = s.getSubscribeDataManagementClient(service.ApiPrefix) if client != nil { break @@ -268,9 +264,10 @@ func (s *nudmService) UnSubscribe(smCtx *smf_context.SMContext) ( var client *Nudm_SubscriberDataManagement.APIClient for _, service := range *s.consumer.Context().UDMProfile.NfServices { if service.ServiceName == models.ServiceName_NUDM_SDM { - SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() - SDMConf.SetBasePath(service.ApiPrefix) client = s.getSubscribeDataManagementClient(service.ApiPrefix) + if client != nil { + break + } } } From 175a0715da4e9e40fdf730a0ed7db4da604746f1 Mon Sep 17 00:00:00 2001 From: Ian Chen Date: Tue, 29 Oct 2024 10:49:04 +0800 Subject: [PATCH 4/5] fix: use defer to subscribe the SDM svc --- internal/sbi/processor/pdu_session.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/sbi/processor/pdu_session.go b/internal/sbi/processor/pdu_session.go index 4e7cdbde..cb0bcede 100644 --- a/internal/sbi/processor/pdu_session.go +++ b/internal/sbi/processor/pdu_session.go @@ -121,16 +121,21 @@ func (p *Processor) HandlePDUSessionSMContextCreate( } } - if !p.Context().Ues.UeExists(smContext.Supi) { - if problemDetails, err := p.Consumer(). - Subscribe(ctx, smContext, smPlmnID); problemDetails != nil { - smContext.Log.Errorln("SDM Subscription Failed Problem:", problemDetails) - } else if err != nil { - smContext.Log.Errorln("SDM Subscription Error:", err) + var doSubscribe bool = false + defer func(){ + if doSubscribe { + if !p.Context().Ues.UeExists(smContext.Supi) { + if problemDetails, err := p.Consumer(). + Subscribe(ctx, smContext, smPlmnID); problemDetails != nil { + smContext.Log.Errorln("SDM Subscription Failed Problem:", problemDetails) + } else if err != nil { + smContext.Log.Errorln("SDM Subscription Error:", err) + } + } else { + p.Context().Ues.IncrementPduSessionCount(smContext.Supi) + } } - } else { - p.Context().Ues.IncrementPduSessionCount(smContext.Supi) - } + }() establishmentRequest := m.PDUSessionEstablishmentRequest if err := HandlePDUSessionEstablishmentRequest(smContext, establishmentRequest); err != nil { @@ -248,6 +253,7 @@ func (p *Processor) HandlePDUSessionSMContextCreate( smContext.PostRemoveDataPath() }() + doSubscribe = true response.JsonData = smContext.BuildCreatedData() c.Header("Location", smContext.Ref) c.Render(http.StatusCreated, openapi.MultipartRelatedRender{Data: response}) From 4b6eb4faf168b301ab7c3e9b1810280f6513d356 Mon Sep 17 00:00:00 2001 From: "CTFang@WireLab" Date: Tue, 29 Oct 2024 02:56:33 +0000 Subject: [PATCH 5/5] fix: linter error --- internal/sbi/processor/pdu_session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sbi/processor/pdu_session.go b/internal/sbi/processor/pdu_session.go index cb0bcede..03249548 100644 --- a/internal/sbi/processor/pdu_session.go +++ b/internal/sbi/processor/pdu_session.go @@ -122,7 +122,7 @@ func (p *Processor) HandlePDUSessionSMContextCreate( } var doSubscribe bool = false - defer func(){ + defer func() { if doSubscribe { if !p.Context().Ues.UeExists(smContext.Supi) { if problemDetails, err := p.Consumer().