diff --git a/consumer/nnrf.go b/consumer/nnrf.go index c9ffcc48..cf03704e 100644 --- a/consumer/nnrf.go +++ b/consumer/nnrf.go @@ -231,7 +231,7 @@ func SendNrfForNfInstance(nrfUri string, targetNfType, requestNfType models.NfTy for _, nfProfile := range result.NfInstances { if _, ok := smfSelf.NfStatusSubscriptions.Load(nfProfile.NfInstanceId); !ok { nrfSubscriptionData := models.NrfSubscriptionData{ - NfStatusNotificationUri: fmt.Sprintf("%s://%s:%d/nsmf-callback/nf-status-notify", + NfStatusNotificationUri: fmt.Sprintf("%s://%s:%d/nsmf-callback/v1/nf-status-notify", smfSelf.URIScheme, smfSelf.RegisterIPv4, smfSelf.SBIPort), diff --git a/logger/logger.go b/logger/logger.go index abc7a7ab..ca09e1e9 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -24,6 +24,7 @@ var ( ConsumerLog *zap.SugaredLogger GinLog *zap.SugaredLogger GrpcLog *zap.SugaredLogger + ProducerLog *zap.SugaredLogger UPNodeLog *zap.SugaredLogger FsmLog *zap.SugaredLogger TxnFsmLog *zap.SugaredLogger @@ -69,6 +70,7 @@ func init() { ConsumerLog = log.Sugar().With("component", "SMF", "category", "Consumer") GinLog = log.Sugar().With("component", "SMF", "category", "GIN") GrpcLog = log.Sugar().With("component", "SMF", "category", "GRPC") + ProducerLog = log.Sugar().With("component", "SMF", "category", "Producer") UPNodeLog = log.Sugar().With("component", "SMF", "category", "UPNode") FsmLog = log.Sugar().With("component", "SMF", "category", "Fsm") TxnFsmLog = log.Sugar().With("component", "SMF", "category", "TxnFsm") diff --git a/pdusession/api_individual_sm_context.go b/pdusession/api_individual_sm_context.go index 1b65ac14..2d1be957 100644 --- a/pdusession/api_individual_sm_context.go +++ b/pdusession/api_individual_sm_context.go @@ -18,7 +18,6 @@ import ( "strings" "github.com/gin-gonic/gin" - mi "github.com/omec-project/util/metricinfo" "github.com/omec-project/openapi" "github.com/omec-project/openapi/models" smf_context "github.com/omec-project/smf/context" @@ -28,6 +27,7 @@ import ( "github.com/omec-project/smf/msgtypes/svcmsgtypes" "github.com/omec-project/smf/transaction" "github.com/omec-project/util/httpwrapper" + mi "github.com/omec-project/util/metricinfo" ) // HTTPReleaseSmContext - Release SM Context diff --git a/pdusession/api_sm_contexts_collection.go b/pdusession/api_sm_contexts_collection.go index d445172c..651cc977 100644 --- a/pdusession/api_sm_contexts_collection.go +++ b/pdusession/api_sm_contexts_collection.go @@ -18,7 +18,6 @@ import ( "strings" "github.com/gin-gonic/gin" - mi "github.com/omec-project/util/metricinfo" "github.com/omec-project/openapi" "github.com/omec-project/openapi/models" smf_context "github.com/omec-project/smf/context" @@ -28,6 +27,7 @@ import ( "github.com/omec-project/smf/msgtypes/svcmsgtypes" "github.com/omec-project/smf/transaction" "github.com/omec-project/util/httpwrapper" + mi "github.com/omec-project/util/metricinfo" ) // HTTPPostSmContexts - Create SM Context diff --git a/producer/callback.go b/producer/callback.go index 7495dddd..e70ae05b 100644 --- a/producer/callback.go +++ b/producer/callback.go @@ -12,22 +12,28 @@ import ( "github.com/omec-project/openapi/models" nrfCache "github.com/omec-project/openapi/nrfcache" - smf_context "github.com/omec-project/smf/context" + "github.com/omec-project/smf/consumer" + smfContext "github.com/omec-project/smf/context" "github.com/omec-project/smf/logger" "github.com/omec-project/smf/qos" "github.com/omec-project/smf/transaction" "github.com/omec-project/util/httpwrapper" ) +var ( + NRFCacheRemoveNfProfileFromNrfCache = nrfCache.RemoveNfProfileFromNrfCache + SendRemoveSubscription = consumer.SendRemoveSubscription +) + func HandleSMPolicyUpdateNotify(eventData interface{}) error { txn := eventData.(*transaction.Transaction) request := txn.Req.(models.SmPolicyNotification) - smContext := txn.Ctxt.(*smf_context.SMContext) + smContext := txn.Ctxt.(*smfContext.SMContext) logger.PduSessLog.Infoln("In HandleSMPolicyUpdateNotify") pcfPolicyDecision := request.SmPolicyDecision - if smContext.SMContextState != smf_context.SmStateActive { + if smContext.SMContextState != smfContext.SmStateActive { // Wait till the state becomes SmStateActive again // TODO: implement waiting in concurrent architecture logger.PduSessLog.Warnf("SMContext[%s-%02d] should be SmStateActive, but actual %s", @@ -67,7 +73,7 @@ func HandleSMPolicyUpdateNotify(eventData interface{}) error { return nil } -func BuildAndSendQosN1N2TransferMsg(smContext *smf_context.SMContext) error { +func BuildAndSendQosN1N2TransferMsg(smContext *smfContext.SMContext) error { // N1N2 Request towards AMF n1n2Request := models.N1N2MessageTransferRequest{} @@ -96,7 +102,7 @@ func BuildAndSendQosN1N2TransferMsg(smContext *smf_context.SMContext) error { n1n2Request.JsonData = &models.N1N2MessageTransferReqData{PduSessionId: smContext.PDUSessionID} // N1 Msg - if smNasBuf, err := smf_context.BuildGSMPDUSessionModificationCommand(smContext); err != nil { + if smNasBuf, err := smfContext.BuildGSMPDUSessionModificationCommand(smContext); err != nil { logger.PduSessLog.Errorf("build GSM BuildGSMPDUSessionModificationCommand failed: %s", err) } else { n1n2Request.BinaryDataN1Message = smNasBuf @@ -104,7 +110,7 @@ func BuildAndSendQosN1N2TransferMsg(smContext *smf_context.SMContext) error { } // N2 Msg - n2Pdu, err := smf_context.BuildPDUSessionResourceModifyRequestTransfer(smContext) + n2Pdu, err := smfContext.BuildPDUSessionResourceModifyRequestTransfer(smContext) if err != nil { smContext.SubPduSessLog.Errorf("SMPolicyUpdate, build PDUSession Resource Modify Request Transfer Error(%s)", err.Error()) } else { @@ -142,8 +148,11 @@ func HandleNfSubscriptionStatusNotify(request *httpwrapper.Request) *httpwrapper } } +// NfSubscriptionStatusNotifyProcedure is handler method of notification procedure. +// According to event type retrieved in the notification data, it performs some actions. +// For example, if event type is deregistered, it deletes cached NF profile and performs an NF discovery. func NfSubscriptionStatusNotifyProcedure(notificationData models.NotificationData) *models.ProblemDetails { - logger.PduSessLog.Debugf("NfSubscriptionStatusNotify: %+v", notificationData) + logger.ProducerLog.Debugf("NfSubscriptionStatusNotify: %+v", notificationData) if notificationData.Event == "" || notificationData.NfInstanceUri == "" { problemDetails := &models.ProblemDetails{ @@ -155,11 +164,28 @@ func NfSubscriptionStatusNotifyProcedure(notificationData models.NotificationDat } nfInstanceId := notificationData.NfInstanceUri[strings.LastIndex(notificationData.NfInstanceUri, "/")+1:] + logger.ProducerLog.Infof("Received Subscription Status Notification from NRF: %v", notificationData.Event) // If nrf caching is enabled, go ahead and delete the entry from the cache. - // This will force the smf to do nf discovery and get the updated nf profile from the nrf. - if smf_context.SMF_Self().EnableNrfCaching { - ok := nrfCache.RemoveNfProfileFromNrfCache(nfInstanceId) - logger.PduSessLog.Debugf("nfinstance %v deleted from cache: %v", nfInstanceId, ok) + // This will force the PCF to do nf discovery and get the updated nf profile from the NRF. + if notificationData.Event == models.NotificationEventType_DEREGISTERED { + if smfContext.SMF_Self().EnableNrfCaching { + ok := NRFCacheRemoveNfProfileFromNrfCache(nfInstanceId) + logger.ProducerLog.Debugf("nfinstance %v deleted from cache: %v", nfInstanceId, ok) + } + if subscriptionId, ok := smfContext.SMF_Self().NfStatusSubscriptions.Load(nfInstanceId); ok { + logger.ConsumerLog.Debugf("SubscriptionId of nfInstance %v is %v", nfInstanceId, subscriptionId.(string)) + problemDetails, err := SendRemoveSubscription(subscriptionId.(string)) + if problemDetails != nil { + logger.ConsumerLog.Errorf("Remove NF Subscription Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.ConsumerLog.Errorf("Remove NF Subscription Error[%+v]", err) + } else { + logger.ConsumerLog.Infoln("Remove NF Subscription successful") + smfContext.SMF_Self().NfStatusSubscriptions.Delete(nfInstanceId) + } + } else { + logger.ProducerLog.Infof("nfinstance %v not found in map", nfInstanceId) + } } return nil diff --git a/producer/subscription_test.go b/producer/subscription_test.go new file mode 100644 index 00000000..49d342b4 --- /dev/null +++ b/producer/subscription_test.go @@ -0,0 +1,189 @@ +// Copyright 2024 Canonical Ltd. +// +// SPDX-License-Identifier: Apache-2.0 + +package producer + +import ( + "fmt" + "net/http" + "os" + "testing" + + "github.com/omec-project/openapi/models" + smfContext "github.com/omec-project/smf/context" + "github.com/omec-project/smf/factory" + "github.com/stretchr/testify/assert" +) + +var ( + nfInstanceID = "34343-4343-43-434-343" + subscriptionID = "46326-232353-2323" +) + +func setupTest() { + if err := factory.InitConfigFactory("../config/smfcfg.yaml"); err != nil { + fmt.Printf("Could not InitConfigFactory: %+v", err) + } +} + +func TestNfSubscriptionStatusNotify(t *testing.T) { + t.Logf("test cases fore NfSubscriptionStatusNotify") + callCountSendRemoveSubscription := 0 + callCountNRFCacheRemoveNfProfileFromNrfCache := 0 + origSendRemoveSubscription := SendRemoveSubscription + origNRFCacheRemoveNfProfileFromNrfCache := NRFCacheRemoveNfProfileFromNrfCache + defer func() { + SendRemoveSubscription = origSendRemoveSubscription + NRFCacheRemoveNfProfileFromNrfCache = origNRFCacheRemoveNfProfileFromNrfCache + }() + SendRemoveSubscription = func(subscriptionId string) (problemDetails *models.ProblemDetails, err error) { + t.Logf("test SendRemoveSubscription called") + callCountSendRemoveSubscription++ + return nil, nil + } + NRFCacheRemoveNfProfileFromNrfCache = func(nfInstanceId string) bool { + t.Logf("test NRFCacheRemoveNfProfileFromNrfCache called") + callCountNRFCacheRemoveNfProfileFromNrfCache++ + return true + } + udmProfile := models.NfProfileNotificationData{ + UdrInfo: &models.UdrInfo{ + SupportedDataSets: []models.DataSetId{ + models.DataSetId_SUBSCRIPTION, + }, + }, + NfInstanceId: nfInstanceID, + NfType: "UDM", + NfStatus: "DEREGISTERED", + } + badRequestProblem := models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "MANDATORY_IE_MISSING", + Detail: "Missing IE [Event]/[NfInstanceUri] in NotificationData", + } + parameters := []struct { + expectedProblem *models.ProblemDetails + testName string + result string + nfInstanceId string + nfInstanceIdForSubscription string + subscriptionID string + notificationEventType string + expectedCallCountSendRemoveSubscription int + expectedCallCountNRFCacheRemoveNfProfileFromNrfCache int + enableNrfCaching bool + }{ + { + nil, + "Notification event type DEREGISTERED NRF caching is enabled", + "NF profile removed from cache and subscription is removed", + nfInstanceID, + nfInstanceID, + subscriptionID, + "NF_DEREGISTERED", + 1, + 1, + true, + }, + { + nil, + "Notification event type DEREGISTERED NRF caching is enabled Subscription is not found", + "NF profile removed from cache and subscription is not removed", + nfInstanceID, + "", + "", + "NF_DEREGISTERED", + 0, + 1, + true, + }, + { + nil, + "Notification event type DEREGISTERED NRF caching is disabled", + "NF profile is not removed from cache and subscription is removed", + nfInstanceID, + nfInstanceID, + subscriptionID, + "NF_DEREGISTERED", + 1, + 0, + false, + }, + { + nil, + "Notification event type REGISTERED NRF caching is enabled", + "NF profile is not removed from cache and subscription is not removed", + nfInstanceID, + nfInstanceID, + subscriptionID, + "NF_REGISTERED", + 0, + 0, + true, + }, + { + nil, + "Notification event type DEREGISTERED NRF caching is enabled NfInstanceUri in notificationData is different", + "NF profile removed from cache and subscription is not removed", + nfInstanceID, + nfInstanceID, + subscriptionID, + "NF_DEREGISTERED", + 1, + 1, + true, + }, + { + &badRequestProblem, + "Notification event type DEREGISTERED NRF caching is enabled NfInstanceUri in notificationData is empty", + "Return StatusBadRequest with cause MANDATORY_IE_MISSING", + "", + "", + subscriptionID, + "NF_DEREGISTERED", + 0, + 0, + true, + }, + { + &badRequestProblem, + "Notification event type empty NRF caching is enabled", + "Return StatusBadRequest with cause MANDATORY_IE_MISSING", + nfInstanceID, + nfInstanceID, + subscriptionID, + "", + 0, + 0, + true, + }, + } + for i := range parameters { + t.Run(fmt.Sprintf("NfSubscriptionStatusNotify testname %v result %v", parameters[i].testName, parameters[i].result), func(t *testing.T) { + smfContext.SMF_Self().EnableNrfCaching = parameters[i].enableNrfCaching + smfContext.SMF_Self().NfStatusSubscriptions.Store(parameters[i].nfInstanceIdForSubscription, parameters[i].subscriptionID) + notificationData := models.NotificationData{ + Event: models.NotificationEventType(parameters[i].notificationEventType), + NfInstanceUri: parameters[i].nfInstanceId, + NfProfile: &udmProfile, + ProfileChanges: []models.ChangeItem{}, + } + err := NfSubscriptionStatusNotifyProcedure(notificationData) + assert.Equal(t, parameters[i].expectedProblem, err, "NfSubscriptionStatusNotifyProcedure is failed.") + // Subscription is removed. + assert.Equal(t, parameters[i].expectedCallCountSendRemoveSubscription, callCountSendRemoveSubscription, "Subscription is not removed.") + // NF Profile is removed from NRF cache. + assert.Equal(t, parameters[i].expectedCallCountNRFCacheRemoveNfProfileFromNrfCache, callCountNRFCacheRemoveNfProfileFromNrfCache, "NF Profile is not removed from NRF cache.") + callCountSendRemoveSubscription = 0 + callCountNRFCacheRemoveNfProfileFromNrfCache = 0 + smfContext.SMF_Self().NfStatusSubscriptions.Delete(parameters[i].nfInstanceIdForSubscription) + }) + } +} + +func TestMain(m *testing.M) { + setupTest() + exitVal := m.Run() + os.Exit(exitVal) +}