From 36503973a5c0a3ff8ecb765b4b835585eb78912a Mon Sep 17 00:00:00 2001 From: SAILESH VVR Date: Sat, 14 Sep 2024 16:36:04 +0000 Subject: [PATCH] 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 | 117 ++++++++++++++++++++++++++ internal/sbi/processor/pdu_session.go | 38 +++++++++ internal/sbi/processor/sm_common.go | 9 ++ 5 files changed, 268 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..83f6f970 100644 --- a/internal/sbi/consumer/udm_service.go +++ b/internal/sbi/consumer/udm_service.go @@ -199,3 +199,120 @@ 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 { + sdmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_SDM, + models.NfServiceStatus_REGISTERED) + if sdmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + + SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() + SDMConf.SetBasePath(sdmUri) + + client = Nudm_SubscriberDataManagement.NewAPIClient(SDMConf) + } + } + + 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 { + sdmUri := util.SearchNFServiceUri(smf_context.GetSelf().UDMProfile, models.ServiceName_NUDM_SDM, + models.NfServiceStatus_REGISTERED) + if sdmUri == "" { + return nil, errors.Errorf("SMF can not select an UDM by NRF: SearchNFServiceUri failed") + } + + SDMConf := Nudm_SubscriberDataManagement.NewConfiguration() + SDMConf.SetBasePath(sdmUri) + + client = Nudm_SubscriberDataManagement.NewAPIClient(SDMConf) + } + } + + 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.