diff --git a/ausf_test.go b/ausf_test.go new file mode 100644 index 0000000..efc84a3 --- /dev/null +++ b/ausf_test.go @@ -0,0 +1,615 @@ +// SPDX-FileCopyrightText: 2024 Canonical Ltd. +// +// SPDX-License-Identifier: Apache-2.0 +// +/* + * AUSF Unit Testcases + * + */ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "strings" + "testing" + "time" + + "github.com/antihax/optional" + "github.com/omec-project/ausf/consumer" + ausfContext "github.com/omec-project/ausf/context" + "github.com/omec-project/ausf/factory" + "github.com/omec-project/ausf/producer" + "github.com/omec-project/ausf/service" + "github.com/omec-project/openapi/Nnrf_NFDiscovery" + "github.com/omec-project/openapi/models" + "github.com/stretchr/testify/assert" +) + +var ( + AUSFTest = &service.AUSF{} + nfInstanceID = "34343-4343-43-434-343" + subscriptionID = "46326-232353-2323" +) + +func setupTest() { + if err := os.Setenv("POD_IP", "127.0.0.1"); err != nil { + fmt.Printf("could not set env POD_IP: %+v", err) + } + if err := factory.InitConfigFactory("factory/ausfcfg.yaml"); err != nil { + fmt.Printf("could not InitConfigFactory: %+v", err) + } +} + +func TestRegisterNF(t *testing.T) { + origRegisterNFInstance := consumer.SendRegisterNFInstance + origSearchNFInstances := consumer.SendSearchNFInstances + origUpdateNFInstance := consumer.SendUpdateNFInstance + defer func() { + consumer.SendRegisterNFInstance = origRegisterNFInstance + consumer.SendSearchNFInstances = origSearchNFInstances + consumer.SendUpdateNFInstance = origUpdateNFInstance + }() + fmt.Printf("test case TestRegisterNF") + var prof models.NfProfile + consumer.SendRegisterNFInstance = func(nrfUri string, nfInstanceId string, profile models.NfProfile) (models.NfProfile, string, string, error) { + prof = profile + prof.HeartBeatTimer = 1 + fmt.Printf("test RegisterNFInstance called") + return prof, "", "", nil + } + consumer.SendSearchNFInstances = func(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { + fmt.Printf("test SearchNFInstance called") + return models.SearchResult{}, nil + } + consumer.SendUpdateNFInstance = func(patchItem []models.PatchItem) (nfProfile models.NfProfile, problemDetails *models.ProblemDetails, err error) { + return prof, nil, nil + } + go AUSFTest.RegisterNF() + service.ConfigPodTrigger <- true + time.Sleep(5 * time.Second) + assert.Equal(t, service.KeepAliveTimer != nil, true) + + service.ConfigPodTrigger <- false + time.Sleep(1 * time.Second) + assert.Equal(t, service.KeepAliveTimer == nil, true) +} + +func TestGetUDMUri(t *testing.T) { + fmt.Printf("test cases for Get UDM URI") + callCountSearchNFInstances := 0 + callCountSendNfDiscovery := 0 + origNRFCacheSearchNFInstances := consumer.NRFCacheSearchNFInstances + origSendNfDiscoveryToNrf := consumer.SendNfDiscoveryToNrf + udmUri1 := "https://10.0.13.1:8090" + udmUri2 := "https://20.20.13.1:8090" + udmProfile1 := models.NfProfile{ + UdmInfo: &models.UdmInfo{ + RoutingIndicators: []string{}, + }, + NfInstanceId: nfInstanceID, + Ipv4Addresses: []string{udmUri1}, + NfType: "UDM", + NfStatus: "REGISTERED", + } + services1 := []models.NfService{ + { + ServiceInstanceId: "datarepository", + ServiceName: models.ServiceName_NUDM_UEAU, + Versions: &[]models.NfServiceVersion{ + { + ApiFullVersion: "1", + ApiVersionInUri: "versionUri", + }, + }, + Scheme: "https", + NfServiceStatus: models.NfServiceStatus_REGISTERED, + ApiPrefix: udmUri1, + IpEndPoints: &[]models.IpEndPoint{ + { + Ipv4Address: "10.0.13.1", + Transport: models.TransportProtocol_TCP, + Port: 8090, + }, + }, + }, + } + udmProfile1.NfServices = &services1 + nfInstances1 := []models.NfProfile{ + udmProfile1, + } + searchResult1 := models.SearchResult{ + ValidityPeriod: 7, + NfInstances: nfInstances1, + NrfSupportedFeatures: "", + } + udmProfile2 := models.NfProfile{ + UdmInfo: &models.UdmInfo{ + RoutingIndicators: []string{}, + }, + NfInstanceId: "9999-4343-43-434-343", + Ipv4Addresses: []string{udmUri2}, + NfType: "UDM", + NfStatus: "REGISTERED", + } + services2 := []models.NfService{ + { + ServiceInstanceId: "datarepository", + ServiceName: models.ServiceName_NUDM_UEAU, + Versions: &[]models.NfServiceVersion{ + { + ApiFullVersion: "1", + ApiVersionInUri: "versionUri", + }, + }, + Scheme: "https", + NfServiceStatus: models.NfServiceStatus_REGISTERED, + ApiPrefix: udmUri2, + IpEndPoints: &[]models.IpEndPoint{ + { + Ipv4Address: "20.20.13.1", + Transport: models.TransportProtocol_TCP, + Port: 8090, + }, + }, + }, + } + udmProfile2.NfServices = &services2 + nfInstances2 := []models.NfProfile{ + udmProfile2, + } + searchResult2 := models.SearchResult{ + ValidityPeriod: 7, + NfInstances: nfInstances2, + NrfSupportedFeatures: "", + } + defer func() { + consumer.NRFCacheSearchNFInstances = origNRFCacheSearchNFInstances + consumer.SendNfDiscoveryToNrf = origSendNfDiscoveryToNrf + }() + consumer.NRFCacheSearchNFInstances = func(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { + fmt.Printf("test SearchNFInstance called") + callCountSearchNFInstances++ + return searchResult1, nil + } + consumer.SendNfDiscoveryToNrf = func(nrfUri string, targetNfType, requestNfType models.NfType, param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { + fmt.Printf("test SendNfDiscoveryToNrf called") + callCountSendNfDiscovery++ + return searchResult2, nil + } + + parameters := []struct { + testName string + result string + udmUri string + inputEnableNrfCaching bool + expectedCallCountSearchNFInstances int + expectedCallCountSendNfDiscovery int + }{ + { + "NRF caching is enabled request is sent to discover UDM", + "UDM URI is retrieved from NRF cache", + "https://10.0.13.1:8090", + true, + 1, + 0, + }, + { + "NRF caching is disabled request is sent to discover UDM", + "UDM URI is retrieved from NRF trough the NF discovery process", + "https://20.20.13.1:8090", + false, + 0, + 1, + }, + } + for i := range parameters { + t.Run(fmt.Sprintf("NRF caching is [%v]", parameters[i].inputEnableNrfCaching), func(t *testing.T) { + ausfContext.GetSelf().EnableNrfCaching = parameters[i].inputEnableNrfCaching + udm_uri := producer.GetUdmUrl(ausfContext.GetSelf().NrfUri) + assert.Equal(t, parameters[i].expectedCallCountSearchNFInstances, callCountSearchNFInstances, "NF instance is searched in the cache.") + assert.Equal(t, parameters[i].expectedCallCountSendNfDiscovery, callCountSendNfDiscovery, "NF discovery request is sent to NRF.") + assert.Equal(t, parameters[i].udmUri, udm_uri, "UDR Uri is set.") + callCountSendNfDiscovery = 0 + callCountSearchNFInstances = 0 + }) + } +} + +func TestCreateSubscriptionSuccess(t *testing.T) { + fmt.Printf("test cases for CreateSubscription") + udmProfile := models.NfProfile{ + UdmInfo: &models.UdmInfo{ + RoutingIndicators: []string{}, + }, + NfInstanceId: nfInstanceID, + NfType: "UDM", + NfStatus: "REGISTERED", + } + nfInstances := []models.NfProfile{ + udmProfile, + } + searchResult := models.SearchResult{ + ValidityPeriod: 7, + NfInstances: nfInstances, + NrfSupportedFeatures: "", + } + stringReader := strings.NewReader("successful!") + stringReadCloser := io.NopCloser(stringReader) + httpResponse := http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: stringReadCloser, + } + callCountSendCreateSubscription := 0 + origStoreApiSearchNFInstances := consumer.StoreApiSearchNFInstances + origCreateSubscription := consumer.CreateSubscription + defer func() { + consumer.StoreApiSearchNFInstances = origStoreApiSearchNFInstances + consumer.CreateSubscription = origCreateSubscription + }() + consumer.StoreApiSearchNFInstances = func(*Nnrf_NFDiscovery.NFInstancesStoreApiService, context.Context, models.NfType, models.NfType, *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, *http.Response, error) { + fmt.Printf("test SearchNFInstances called") + return searchResult, &httpResponse, nil + } + consumer.CreateSubscription = func(nrfUri string, nrfSubscriptionData models.NrfSubscriptionData) (nrfSubData models.NrfSubscriptionData, problemDetails *models.ProblemDetails, err error) { + fmt.Printf("test SendCreateSubscription called") + callCountSendCreateSubscription++ + return models.NrfSubscriptionData{ + NfStatusNotificationUri: "https://:0/nausf-callback/v1/nf-status-notify", + ReqNfType: "AUSF", + SubscriptionId: subscriptionID, + }, nil, nil + } + // NRF caching is disabled + ausfContext.GetSelf().EnableNrfCaching = false + param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ + ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NUDM_UEAU}), + } + parameters := []struct { + expectedError error + testName string + result string + nfInstanceId string + subscriptionId string + expectedCallCountSendCreateSubscription int + }{ + { + nil, + "NF instances are found in Store Api subscription is not created for NFInstanceID yet", + "Subscription is created", + nfInstanceID, + subscriptionID, + 1, + }, + { + nil, + "NF instances are found in Store Api subscription is already created for NFInstanceID", + "Subscription is not created again", + nfInstanceID, + subscriptionID, + 0, + }, + } + for i := range parameters { + t.Run(fmt.Sprintf("CreateSubscription testname %v result %v", parameters[i].testName, parameters[i].result), func(t *testing.T) { + _, err := consumer.SendNfDiscoveryToNrf("testNRFUri", "UDM", "AUSF", ¶m) + val, _ := ausfContext.GetSelf().NfStatusSubscriptions.Load(parameters[i].nfInstanceId) + assert.Equal(t, val, parameters[i].subscriptionId, "Correct Subscription ID is not stored in the AUSF context.") + assert.Equal(t, parameters[i].expectedError, err, "SendNfDiscoveryToNrf is failed.") + // Subscription is created. + assert.Equal(t, parameters[i].expectedCallCountSendCreateSubscription, callCountSendCreateSubscription, "Subscription is not created for NF instance.") + callCountSendCreateSubscription = 0 + }) + } +} + +func TestCreateSubscriptionFail(t *testing.T) { + fmt.Printf("test cases for CreateSubscription") + udmProfile := models.NfProfile{ + UdmInfo: &models.UdmInfo{ + RoutingIndicators: []string{}, + }, + NfInstanceId: "84343-4343-43-434-343", + NfType: "UDM", + NfStatus: "REGISTERED", + } + nfInstances := []models.NfProfile{ + udmProfile, + } + searchResult := models.SearchResult{ + ValidityPeriod: 7, + NfInstances: nfInstances, + NrfSupportedFeatures: "", + } + emptySearchResult := models.SearchResult{} + nrfSubscriptionData := models.NrfSubscriptionData{ + NfStatusNotificationUri: "https://:0/nausf-callback/v1/nf-status-notify", + ReqNfType: "AUSF", + SubscriptionId: "", + } + emptyNrfSubscriptionData := models.NrfSubscriptionData{} + stringReader := strings.NewReader("successful!") + stringReadCloser := io.NopCloser(stringReader) + httpResponseTemporaryDirect := http.Response{ + Status: "307 Temporary Direct", + StatusCode: 307, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: stringReadCloser, + } + httpResponseSuccess := http.Response{ + Status: "200 OK", + StatusCode: 200, + Proto: "HTTP/1.0", + ProtoMajor: 1, + ProtoMinor: 0, + Body: stringReadCloser, + } + serverErrorProblem := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "Server Error", + Detail: "", + } + callCountSendCreateSubscription := 0 + origStoreApiSearchNFInstances := consumer.StoreApiSearchNFInstances + origCreateSubscription := consumer.CreateSubscription + defer func() { + consumer.StoreApiSearchNFInstances = origStoreApiSearchNFInstances + consumer.CreateSubscription = origCreateSubscription + }() + // NRF caching is disabled + ausfContext.GetSelf().EnableNrfCaching = false + param := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ + ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NUDM_UEAU}), + } + parameters := []struct { + httpResponse http.Response + expectedSubscriptionId any + subscriptionError error + expectedError error + subscriptionProblem *models.ProblemDetails + nrfSubscriptionData models.NrfSubscriptionData + searchResult models.SearchResult + testName string + result string + expectedCallCountSendCreateSubscription int + }{ + { + httpResponseTemporaryDirect, + nil, + nil, + errors.New("temporary redirect for non NRF consumer"), + nil, + emptyNrfSubscriptionData, + emptySearchResult, + "Store Api returns HTTP code 307", + "Subscription is not created", + 0, + }, + { + httpResponseSuccess, + "", + nil, + nil, + &serverErrorProblem, + emptyNrfSubscriptionData, + searchResult, + "NF instances are found in Store Api subscription but create subscription reports problem", + "Subscription request is sent but problem is reported", + 1, + }, + { + httpResponseSuccess, + "", + errors.New("SendCreateSubscription request failed"), + errors.New("SendCreateSubscription request failed"), + nil, + emptyNrfSubscriptionData, + searchResult, + "NF instances are found in Store Api subscription but create subscription reports error", + "Subscription request is sent but error is reported", + 1, + }, + { + httpResponseSuccess, + "", + nil, + nil, + nil, + nrfSubscriptionData, + searchResult, + "NF instances are found in Store Api subscription subscription is created but nrfSubData does not have Subscription ID", + "SubscriptionId is not stored in NfStatusSubscriptions", + 1, + }, + } + for i := range parameters { + t.Run(fmt.Sprintf("CreateSubscription testname %v result %v", parameters[i].testName, parameters[i].result), func(t *testing.T) { + consumer.StoreApiSearchNFInstances = func(*Nnrf_NFDiscovery.NFInstancesStoreApiService, context.Context, models.NfType, models.NfType, *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, *http.Response, error) { + fmt.Printf("test SearchNFInstances called") + return parameters[i].searchResult, ¶meters[i].httpResponse, nil + } + + consumer.CreateSubscription = func(nrfUri string, nrfSubscriptionData models.NrfSubscriptionData) (nrfSubData models.NrfSubscriptionData, problemDetails *models.ProblemDetails, err error) { + fmt.Printf("test SendCreateSubscription called") + callCountSendCreateSubscription++ + return parameters[i].nrfSubscriptionData, parameters[i].subscriptionProblem, parameters[i].subscriptionError + } + _, err := consumer.SendNfDiscoveryToNrf("testNRFUri", "UDM", "AUSF", ¶m) + val, _ := ausfContext.GetSelf().NfStatusSubscriptions.Load(udmProfile.NfInstanceId) + assert.Equal(t, val, parameters[i].expectedSubscriptionId, "Correct Subscription ID is not stored in the AUSF context.") + assert.Equal(t, parameters[i].expectedError, err, "SendNfDiscoveryToNrf is failed.") + assert.Equal(t, parameters[i].expectedCallCountSendCreateSubscription, callCountSendCreateSubscription, "Subscription is not created for NF instance.") + callCountSendCreateSubscription = 0 + ausfContext.GetSelf().NfStatusSubscriptions.Delete(udmProfile.NfInstanceId) + }) + } +} + +func TestNfSubscriptionStatusNotify(t *testing.T) { + fmt.Printf("test cases for NfSubscriptionStatusNotify") + callCountSendRemoveSubscription := 0 + callCountNRFCacheRemoveNfProfileFromNrfCache := 0 + origSendRemoveSubscription := consumer.SendRemoveSubscription + origNRFCacheRemoveNfProfileFromNrfCache := producer.NRFCacheRemoveNfProfileFromNrfCache + defer func() { + consumer.SendRemoveSubscription = origSendRemoveSubscription + producer.NRFCacheRemoveNfProfileFromNrfCache = origNRFCacheRemoveNfProfileFromNrfCache + }() + consumer.SendRemoveSubscription = func(subscriptionId string) (problemDetails *models.ProblemDetails, err error) { + fmt.Printf("test SendRemoveSubscription called") + callCountSendRemoveSubscription++ + return nil, nil + } + producer.NRFCacheRemoveNfProfileFromNrfCache = func(nfInstanceId string) bool { + fmt.Printf("test NRFCacheRemoveNfProfileFromNrfCache called") + callCountNRFCacheRemoveNfProfileFromNrfCache++ + return true + } + udmProfile := models.NfProfileNotificationData{ + UdmInfo: &models.UdmInfo{ + RoutingIndicators: []string{}, + }, + 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) { + ausfContext.GetSelf().EnableNrfCaching = parameters[i].enableNrfCaching + ausfContext.GetSelf().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 := producer.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 + ausfContext.GetSelf().NfStatusSubscriptions.Delete(parameters[i].nfInstanceIdForSubscription) + }) + } +} + +func TestMain(m *testing.M) { + setupTest() + exitVal := m.Run() + os.Exit(exitVal) +} diff --git a/callback/api_nf_subscribe_notify.go b/callback/api_nf_subscribe_notify.go new file mode 100644 index 0000000..6bcd596 --- /dev/null +++ b/callback/api_nf_subscribe_notify.go @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2022 Infosys Limited +// SPDX-FileCopyrightText: 2024 Canonical Ltd. +// Copyright 2019 free5GC.org +// +// SPDX-License-Identifier: Apache-2.0 +// + +package callback + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/omec-project/ausf/logger" + "github.com/omec-project/ausf/producer" + "github.com/omec-project/openapi" + "github.com/omec-project/openapi/models" + "github.com/omec-project/util/httpwrapper" +) + +func HTTPNfSubscriptionStatusNotify(c *gin.Context) { + var nfSubscriptionStatusNotification models.NotificationData + + requestBody, err := c.GetRawData() + if err != nil { + logger.CallbackLog.Errorf("get Request Body error: %+v", err) + problemDetail := models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + c.JSON(http.StatusInternalServerError, problemDetail) + return + } + + err = openapi.Deserialize(&nfSubscriptionStatusNotification, requestBody, "application/json") + if err != nil { + problemDetail := "[Request Body] " + err.Error() + rsp := models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: problemDetail, + } + logger.CallbackLog.Errorln(problemDetail) + c.JSON(http.StatusBadRequest, rsp) + return + } + + req := httpwrapper.NewRequest(c.Request, nfSubscriptionStatusNotification) + + rsp := producer.HandleNfSubscriptionStatusNotify(req) + + responseBody, err := openapi.Serialize(rsp.Body, "application/json") + if err != nil { + logger.CallbackLog.Errorln(err) + problemDetails := models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + Detail: err.Error(), + } + c.JSON(http.StatusInternalServerError, problemDetails) + } else if rsp.Body != nil { + c.Data(rsp.Status, "application/json", responseBody) + } +} diff --git a/callback/router.go b/callback/router.go new file mode 100644 index 0000000..c31c34a --- /dev/null +++ b/callback/router.go @@ -0,0 +1,71 @@ +// Copyright 2019 free5GC.org +// +// SPDX-License-Identifier: Apache-2.0 +// + +package callback + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/omec-project/ausf/logger" + loggerUtil "github.com/omec-project/util/logger" +) + +// Route is the information for every URI. +type Route struct { + // HandlerFunc is the handler function of this route. + HandlerFunc gin.HandlerFunc + // Name is the name of this Route. + Name string + // Method is the string for the HTTP method, ex: GET, POST etc.. + Method string + // Pattern is the pattern of the URI. + Pattern string +} + +// Routes is the list of the generated Route. +type Routes []Route + +// NewRouter returns a new router. +func NewRouter() *gin.Engine { + router := loggerUtil.NewGinWithLogrus(logger.GinLog) + AddService(router) + return router +} + +func AddService(engine *gin.Engine) *gin.RouterGroup { + group := engine.Group("/nausf-callback/v1") + + for _, route := range routes { + switch route.Method { + case "GET": + group.GET(route.Pattern, route.HandlerFunc) + case "POST": + group.POST(route.Pattern, route.HandlerFunc) + case "PUT": + group.PUT(route.Pattern, route.HandlerFunc) + case "PATCH": + group.PATCH(route.Pattern, route.HandlerFunc) + case "DELETE": + group.DELETE(route.Pattern, route.HandlerFunc) + } + } + return group +} + +// Index is the index handler. +func Index(c *gin.Context) { + c.String(http.StatusOK, "Hello World!") +} + +var routes = Routes{ + { + HTTPNfSubscriptionStatusNotify, + "NfStatusNotify", + strings.ToUpper("Post"), + "/nf-status-notify", + }, +} diff --git a/consumer/nf_discovery.go b/consumer/nf_discovery.go index a5fb16d..f887cf5 100644 --- a/consumer/nf_discovery.go +++ b/consumer/nf_discovery.go @@ -1,5 +1,5 @@ // Copyright 2019 free5GC.org -// +// SPDX-FileCopyrightText: 2024 Canonical Ltd. // SPDX-License-Identifier: Apache-2.0 // @@ -10,29 +10,67 @@ import ( "fmt" "net/http" + ausfContext "github.com/omec-project/ausf/context" "github.com/omec-project/ausf/logger" "github.com/omec-project/openapi/Nnrf_NFDiscovery" "github.com/omec-project/openapi/models" + nrfCache "github.com/omec-project/openapi/nrfcache" +) + +var ( + CreateSubscription = SendCreateSubscription + NRFCacheSearchNFInstances = nrfCache.SearchNFInstances + StoreApiClient = &Nnrf_NFDiscovery.APIClient{} + StoreApiSearchNFInstances = (*Nnrf_NFDiscovery.NFInstancesStoreApiService).SearchNFInstances ) -func SendSearchNFInstances(nrfUri string, targetNfType, requestNfType models.NfType, - param Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (*models.SearchResult, error) { +var SendSearchNFInstances = func(nrfUri string, targetNfType, requestNfType models.NfType, + param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { + if ausfContext.GetSelf().EnableNrfCaching { + return NRFCacheSearchNFInstances(nrfUri, targetNfType, requestNfType, param) + } else { + return SendNfDiscoveryToNrf(nrfUri, targetNfType, requestNfType, param) + } +} + +var SendNfDiscoveryToNrf = func(nrfUri string, targetNfType, requestNfType models.NfType, + param *Nnrf_NFDiscovery.SearchNFInstancesParamOpts) (models.SearchResult, error) { + // Set client and set url configuration := Nnrf_NFDiscovery.NewConfiguration() configuration.SetBasePath(nrfUri) client := Nnrf_NFDiscovery.NewAPIClient(configuration) - result, rsp, rspErr := client.NFInstancesStoreApi.SearchNFInstances(context.TODO(), - targetNfType, requestNfType, ¶m) - if rspErr != nil { - return nil, fmt.Errorf("NFInstancesStoreApi Response error: %+w", rspErr) + result, res, err := StoreApiSearchNFInstances(client.NFInstancesStoreApi, context.TODO(), targetNfType, requestNfType, param) + if res != nil && res.StatusCode == http.StatusTemporaryRedirect { + err = fmt.Errorf("temporary redirect for non NRF consumer") } defer func() { - if rspCloseErr := rsp.Body.Close(); rspCloseErr != nil { - logger.ConsumerLog.Errorf("NFInstancesStoreApi Response cannot close: %v", rspCloseErr) + if bodyCloseErr := res.Body.Close(); bodyCloseErr != nil { + err = fmt.Errorf("SearchNFInstances' response body cannot close: %+w", bodyCloseErr) } }() - if rsp != nil && rsp.StatusCode == http.StatusTemporaryRedirect { - return nil, fmt.Errorf("temporary Redirect For Non NRF Consumer") + + ausfSelf := ausfContext.GetSelf() + + var nrfSubData models.NrfSubscriptionData + var problemDetails *models.ProblemDetails + for _, nfProfile := range result.NfInstances { + // checking whether the AUSF subscribed to this target nfinstanceid or not + if _, ok := ausfSelf.NfStatusSubscriptions.Load(nfProfile.NfInstanceId); !ok { + nrfSubscriptionData := models.NrfSubscriptionData{ + NfStatusNotificationUri: fmt.Sprintf("%s/nausf-callback/v1/nf-status-notify", ausfSelf.GetIPv4Uri()), + SubscrCond: &models.NfInstanceIdCond{NfInstanceId: nfProfile.NfInstanceId}, + ReqNfType: requestNfType, + } + nrfSubData, problemDetails, err = CreateSubscription(nrfUri, nrfSubscriptionData) + if problemDetails != nil { + logger.ConsumerLog.Errorf("SendCreateSubscription to NRF, Problem[%+v]", problemDetails) + } else if err != nil { + logger.ConsumerLog.Errorf("SendCreateSubscription Error[%+v]", err) + } + ausfSelf.NfStatusSubscriptions.Store(nfProfile.NfInstanceId, nrfSubData.SubscriptionId) + } } - return &result, nil + + return result, err } diff --git a/consumer/nf_management.go b/consumer/nf_management.go index 8257363..9ced257 100644 --- a/consumer/nf_management.go +++ b/consumer/nf_management.go @@ -1,5 +1,5 @@ // Copyright 2019 free5GC.org -// +// SPDX-FileCopyrightText: 2024 Canonical Ltd. // SPDX-License-Identifier: Apache-2.0 // @@ -12,14 +12,14 @@ import ( "strings" "time" - ausf_context "github.com/omec-project/ausf/context" + ausfContext "github.com/omec-project/ausf/context" "github.com/omec-project/ausf/logger" "github.com/omec-project/openapi" "github.com/omec-project/openapi/Nnrf_NFManagement" "github.com/omec-project/openapi/models" ) -func BuildNFInstance(ausfContext *ausf_context.AUSFContext) (profile models.NfProfile, err error) { +func BuildNFInstance(ausfContext *ausfContext.AUSFContext) (profile models.NfProfile, err error) { profile.NfInstanceId = ausfContext.NfId profile.NfType = models.NfType_AUSF profile.NfStatus = models.NfStatus_REGISTERED @@ -83,7 +83,7 @@ var SendRegisterNFInstance = func(nrfUri, nfInstanceId string, profile models.Nf func SendDeregisterNFInstance() (*models.ProblemDetails, error) { logger.AppLog.Infof("Send Deregister NFInstance") - ausfSelf := ausf_context.GetSelf() + ausfSelf := ausfContext.GetSelf() // Set client and set url configuration := Nnrf_NFManagement.NewConfiguration() configuration.SetBasePath(ausfSelf.NrfUri) @@ -111,7 +111,7 @@ func SendDeregisterNFInstance() (*models.ProblemDetails, error) { var SendUpdateNFInstance = func(patchItem []models.PatchItem) (nfProfile models.NfProfile, problemDetails *models.ProblemDetails, err error) { logger.ConsumerLog.Debugf("Send Update NFInstance") - ausfSelf := ausf_context.GetSelf() + ausfSelf := ausfContext.GetSelf() configuration := Nnrf_NFManagement.NewConfiguration() configuration.SetBasePath(ausfSelf.NrfUri) client := Nnrf_NFManagement.NewAPIClient(configuration) @@ -137,3 +137,63 @@ var SendUpdateNFInstance = func(patchItem []models.PatchItem) (nfProfile models. } return } + +var SendCreateSubscription = func(nrfUri string, nrfSubscriptionData models.NrfSubscriptionData) (nrfSubData models.NrfSubscriptionData, problemDetails *models.ProblemDetails, err error) { + logger.ConsumerLog.Debugf("send Create Subscription") + + // Set client and set url + configuration := Nnrf_NFManagement.NewConfiguration() + configuration.SetBasePath(nrfUri) + client := Nnrf_NFManagement.NewAPIClient(configuration) + + var res *http.Response + nrfSubData, res, err = client.SubscriptionsCollectionApi.CreateSubscription(context.TODO(), nrfSubscriptionData) + if err == nil { + return + } else if res != nil { + defer func() { + if resCloseErr := res.Body.Close(); resCloseErr != nil { + logger.ConsumerLog.Errorf("SendCreateSubscription response cannot close: %+v", resCloseErr) + } + }() + if res.Status != err.Error() { + logger.ConsumerLog.Errorf("SendCreateSubscription received error response: %v", res.Status) + return + } + problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + problemDetails = &problem + } else { + err = openapi.ReportError("server no response") + } + return +} + +var SendRemoveSubscription = func(subscriptionId string) (problemDetails *models.ProblemDetails, err error) { + logger.ConsumerLog.Infoln("send Remove Subscription") + + ausfSelf := ausfContext.GetSelf() + // Set client and set url + configuration := Nnrf_NFManagement.NewConfiguration() + configuration.SetBasePath(ausfSelf.NrfUri) + client := Nnrf_NFManagement.NewAPIClient(configuration) + var res *http.Response + + res, err = client.SubscriptionIDDocumentApi.RemoveSubscription(context.Background(), subscriptionId) + if err == nil { + return + } else if res != nil { + defer func() { + if bodyCloseErr := res.Body.Close(); bodyCloseErr != nil { + err = fmt.Errorf("RemoveSubscription's response body cannot close: %w", bodyCloseErr) + } + }() + if res.Status != err.Error() { + return + } + problem := err.(openapi.GenericOpenAPIError).Model().(models.ProblemDetails) + problemDetails = &problem + } else { + err = openapi.ReportError("server no response") + } + return +} diff --git a/context/ausf_context_init.go b/context/ausf_context_init.go index d3d5909..3bdfb69 100644 --- a/context/ausf_context_init.go +++ b/context/ausf_context_init.go @@ -10,9 +10,9 @@ import ( "fmt" "os" "strconv" + "time" "github.com/google/uuid" - "github.com/omec-project/ausf/factory" "github.com/omec-project/ausf/logger" "github.com/omec-project/ausf/util" @@ -83,6 +83,14 @@ func InitAusfContext(context *AUSFContext) { if roc != "true" { context.PlmnList = append(context.PlmnList, configuration.PlmnSupportList...) } + context.EnableNrfCaching = configuration.EnableNrfCaching + if configuration.EnableNrfCaching { + if configuration.NrfCacheEvictionInterval == 0 { + context.NrfCacheEvictionInterval = time.Duration(900) // 15 mins + } else { + context.NrfCacheEvictionInterval = time.Duration(configuration.NrfCacheEvictionInterval) + } + } // context.NfService context.NfService = make(map[models.ServiceName]models.NfService) diff --git a/context/context.go b/context/context.go index 0d43fa3..208e51d 100644 --- a/context/context.go +++ b/context/context.go @@ -1,35 +1,40 @@ // Copyright 2019 free5GC.org -// +// SPDX-FileCopyrightText: 2024 Canonical Ltd. // SPDX-License-Identifier: Apache-2.0 // package context import ( + "fmt" "regexp" "sync" + "time" "github.com/omec-project/ausf/logger" "github.com/omec-project/openapi/models" ) type AUSFContext struct { - suciSupiMap sync.Map - UePool sync.Map - snRegex *regexp.Regexp - NfId string - GroupID string - RegisterIPv4 string - BindingIPv4 string - Url string - NrfUri string - UdmUeauUrl string - UriScheme models.UriScheme - Key string - PEM string - NfService map[models.ServiceName]models.NfService - PlmnList []models.PlmnId - SBIPort int + suciSupiMap sync.Map + UePool sync.Map + NfStatusSubscriptions sync.Map // map[NfInstanceID]models.NrfSubscriptionData.SubscriptionId + snRegex *regexp.Regexp + NfId string + GroupID string + RegisterIPv4 string + BindingIPv4 string + Url string + NrfUri string + UdmUeauUrl string + UriScheme models.UriScheme + Key string + PEM string + NfService map[models.ServiceName]models.NfService + PlmnList []models.PlmnId + SBIPort int + EnableNrfCaching bool + NrfCacheEvictionInterval time.Duration } type AusfUeContext struct { @@ -102,6 +107,10 @@ func GetAusfUeContext(ref string) *AusfUeContext { return ausfUeContext } +func (context *AUSFContext) GetIPv4Uri() string { + return fmt.Sprintf("%s://%s:%d", context.UriScheme, context.RegisterIPv4, context.SBIPort) +} + func AddSuciSupiPairToMap(supiOrSuci string, supi string) { newPair := new(SuciSupiMap) newPair.SupiOrSuci = supiOrSuci @@ -133,6 +142,6 @@ func GetSelf() *AUSFContext { return &ausfContext } -func (a *AUSFContext) GetSelfID() string { - return a.NfId +func (context *AUSFContext) GetSelfID() string { + return context.NfId } diff --git a/factory/config.go b/factory/config.go index 18c3593..98059da 100644 --- a/factory/config.go +++ b/factory/config.go @@ -36,12 +36,14 @@ const ( ) type Configuration struct { - Sbi *Sbi `yaml:"sbi,omitempty"` - ServiceNameList []string `yaml:"serviceNameList,omitempty"` - NrfUri string `yaml:"nrfUri,omitempty"` - WebuiUri string `yaml:"webuiUri"` - GroupId string `yaml:"groupId,omitempty"` - PlmnSupportList []models.PlmnId `yaml:"plmnSupportList,omitempty"` + Sbi *Sbi `yaml:"sbi,omitempty"` + ServiceNameList []string `yaml:"serviceNameList,omitempty"` + NrfUri string `yaml:"nrfUri,omitempty"` + WebuiUri string `yaml:"webuiUri"` + GroupId string `yaml:"groupId,omitempty"` + PlmnSupportList []models.PlmnId `yaml:"plmnSupportList,omitempty"` + EnableNrfCaching bool `yaml:"enableNrfCaching"` + NrfCacheEvictionInterval int `yaml:"nrfCacheEvictionInterval,omitempty"` } type Sbi struct { diff --git a/go.mod b/go.mod index d6bf210..310560e 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/google/gopacket v1.1.19 github.com/google/uuid v1.6.0 github.com/omec-project/config5g v1.4.2 - github.com/omec-project/openapi v1.2.0 + github.com/omec-project/openapi v1.2.1 github.com/omec-project/util v1.1.0 github.com/prometheus/client_golang v1.19.1 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 6b14095..e32bb33 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/omec-project/config5g v1.4.2 h1:Mujr5RVeSyPOBXxFhH0FUqwuES+WwnY3ltt3ULByeYQ= github.com/omec-project/config5g v1.4.2/go.mod h1:r2v1CX9+Hye5SwQNieeSFEpDutGF5g9mLrIK+Ot9P5Q= -github.com/omec-project/openapi v1.2.0 h1:7Wvi0HLvhvxMyQtqGcqtMCPC/0QCGAFP5htrXCfWxRc= -github.com/omec-project/openapi v1.2.0/go.mod h1:hjU13MB1m9MHTko87JfsUNCdeD6/m6VkNZDD8Vq5U9M= +github.com/omec-project/openapi v1.2.1 h1:7ccFadoGfoqZq4sw7twXatbRGmkg4pARe6sWmCVVmrs= +github.com/omec-project/openapi v1.2.1/go.mod h1:hjU13MB1m9MHTko87JfsUNCdeD6/m6VkNZDD8Vq5U9M= github.com/omec-project/util v1.1.0 h1:TUuLmzqTLChIEXQlK9g5Ihgmw4FUm/UJnjfu0wT8Gz0= github.com/omec-project/util v1.1.0/go.mod h1:BEv8nCokB4j0fgAQ6VVkKuQ2PSP3DJMEmz25pFMw5X8= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/logger/logger.go b/logger/logger.go index 058c8a3..cdff723 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -22,6 +22,8 @@ var ( Auth5gAkaComfirmLog *logrus.Entry EapAuthComfirmLog *logrus.Entry HandlerLog *logrus.Entry + CallbackLog *logrus.Entry + ProducerLog *logrus.Entry ContextLog *logrus.Entry ConsumerLog *logrus.Entry GinLog *logrus.Entry @@ -47,6 +49,8 @@ func init() { Auth5gAkaComfirmLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "5gAkaAuth"}) EapAuthComfirmLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "EapAkaAuth"}) HandlerLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "Handler"}) + CallbackLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "Callback"}) + ProducerLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "Producer"}) ContextLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "ctx"}) ConsumerLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "Consumer"}) GinLog = log.WithFields(logrus.Fields{"component": "AUSF", "category": "GIN"}) diff --git a/producer/callback.go b/producer/callback.go new file mode 100644 index 0000000..642871e --- /dev/null +++ b/producer/callback.go @@ -0,0 +1,73 @@ +// Copyright 2019 free5GC.org +// +// SPDX-License-Identifier: Apache-2.0 +// + +package producer + +import ( + "net/http" + "strings" + + "github.com/omec-project/ausf/consumer" + ausfContext "github.com/omec-project/ausf/context" + "github.com/omec-project/ausf/logger" + "github.com/omec-project/openapi/models" + nrfCache "github.com/omec-project/openapi/nrfcache" + "github.com/omec-project/util/httpwrapper" +) + +var NRFCacheRemoveNfProfileFromNrfCache = nrfCache.RemoveNfProfileFromNrfCache + +func HandleNfSubscriptionStatusNotify(request *httpwrapper.Request) *httpwrapper.Response { + logger.ProducerLog.Traceln("handle NF Status Notify") + + notificationData := request.Body.(models.NotificationData) + + problemDetails := NfSubscriptionStatusNotifyProcedure(notificationData) + if problemDetails != nil { + return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) + } else { + return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) + } +} + +func NfSubscriptionStatusNotifyProcedure(notificationData models.NotificationData) *models.ProblemDetails { + logger.ProducerLog.Debugf("NfSubscriptionStatusNotify: %+v", notificationData) + + if notificationData.Event == "" || notificationData.NfInstanceUri == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "MANDATORY_IE_MISSING", // Defined in TS 29.510 6.1.6.2.17 + Detail: "Missing IE [Event]/[NfInstanceUri] in NotificationData", + } + return problemDetails + } + 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 AUSF to do nf discovery and get the updated nf profile from the NRF. + if notificationData.Event == models.NotificationEventType_DEREGISTERED { + if ausfContext.GetSelf().EnableNrfCaching { + ok := NRFCacheRemoveNfProfileFromNrfCache(nfInstanceId) + logger.ProducerLog.Tracef("nfinstance %v deleted from cache: %v", nfInstanceId, ok) + } + if subscriptionId, ok := ausfContext.GetSelf().NfStatusSubscriptions.Load(nfInstanceId); ok { + logger.ConsumerLog.Debugf("SubscriptionId of nfInstance %v is %v", nfInstanceId, subscriptionId.(string)) + problemDetails, err := consumer.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") + ausfContext.GetSelf().NfStatusSubscriptions.Delete(nfInstanceId) + } + } else { + logger.ProducerLog.Infof("nfinstance %v not found in map", nfInstanceId) + } + } + + return nil +} diff --git a/producer/functions.go b/producer/functions.go index 52689af..5ffe976 100644 --- a/producer/functions.go +++ b/producer/functions.go @@ -272,12 +272,12 @@ func ConstructEapNoTypePkt(code radius.EapCode, pktID uint8) string { return base64.StdEncoding.EncodeToString(b) } -func getUdmUrl(nrfUri string) string { +func GetUdmUrl(nrfUri string) string { udmUrl := "https://localhost:29503" // default nfDiscoverParam := Nnrf_NFDiscovery.SearchNFInstancesParamOpts{ ServiceNames: optional.NewInterface([]models.ServiceName{models.ServiceName_NUDM_UEAU}), } - res, err := consumer.SendSearchNFInstances(nrfUri, models.NfType_UDM, models.NfType_AUSF, nfDiscoverParam) + res, err := consumer.SendSearchNFInstances(nrfUri, models.NfType_UDM, models.NfType_AUSF, &nfDiscoverParam) if err != nil { logger.UeAuthPostLog.Errorln("[Search UDM UEAU] ", err.Error()) } else if len(res.NfInstances) > 0 { diff --git a/producer/ue_authentication.go b/producer/ue_authentication.go index 73aa18f..a358953 100644 --- a/producer/ue_authentication.go +++ b/producer/ue_authentication.go @@ -136,7 +136,7 @@ func UeAuthPostRequestProcedure(updateAuthenticationInfo models.AuthenticationIn authInfoReq.ResynchronizationInfo = updateAuthenticationInfo.ResynchronizationInfo } - udmUrl := getUdmUrl(self.NrfUri) + udmUrl := GetUdmUrl(self.NrfUri) client := createClientToUdmUeau(udmUrl) authInfoResult, rsp, err := client.GenerateAuthDataApi.GenerateAuthData(context.Background(), supiOrSuci, authInfoReq) if err != nil { diff --git a/service/init.go b/service/init.go index 66239d5..bd62fdf 100644 --- a/service/init.go +++ b/service/init.go @@ -16,6 +16,7 @@ import ( "syscall" "time" + "github.com/omec-project/ausf/callback" "github.com/omec-project/ausf/consumer" "github.com/omec-project/ausf/context" "github.com/omec-project/ausf/factory" @@ -26,6 +27,7 @@ import ( "github.com/omec-project/config5g/proto/client" protos "github.com/omec-project/config5g/proto/sdcoreConfig" "github.com/omec-project/openapi/models" + nrfCache "github.com/omec-project/openapi/nrfcache" "github.com/omec-project/util/http2_util" logger_util "github.com/omec-project/util/logger" "github.com/omec-project/util/path_util" @@ -225,18 +227,24 @@ func (ausf *AUSF) Start() { router := logger_util.NewGinWithLogrus(logger.GinLog) ueauthentication.AddService(router) + callback.AddService(router) go metrics.InitMetrics() context.Init() self := context.GetSelf() - // Register to NRF - go ausf.registerNF() ausfLogPath := util.AusfLogPath addr := fmt.Sprintf("%s:%d", self.BindingIPv4, self.SBIPort) + if self.EnableNrfCaching { + initLog.Infoln("Enable NRF caching feature") + nrfCache.InitNrfCaching(self.NrfCacheEvictionInterval*time.Second, consumer.SendNfDiscoveryToNrf) + } + // Register to NRF + go ausf.RegisterNF() + signalChannel := make(chan os.Signal, 1) signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) go func() { @@ -406,22 +414,39 @@ func (ausf *AUSF) UpdateNF() { KeepAliveTimer = time.AfterFunc(time.Duration(heartBeatTimer)*time.Second, ausf.UpdateNF) } -func (ausf *AUSF) registerNF() { +func (ausf *AUSF) RegisterNF() { for msg := range ConfigPodTrigger { - initLog.Infof("Minimum configuration from config pod available %v", msg) - self := context.GetSelf() - profile, err := consumer.BuildNFInstance(self) - if err != nil { - initLog.Error("Build AUSF Profile Error") - } - var prof models.NfProfile - prof, _, self.NfId, err = consumer.SendRegisterNFInstance(self.NrfUri, self.NfId, profile) - if err != nil { - initLog.Errorf("AUSF register to NRF Error[%s]", err.Error()) + if msg { + initLog.Infof("Minimum configuration from config pod available %v", msg) + self := context.GetSelf() + profile, err := consumer.BuildNFInstance(self) + if err != nil { + initLog.Error("Build AUSF Profile Error") + } + var prof models.NfProfile + prof, _, self.NfId, err = consumer.SendRegisterNFInstance(self.NrfUri, self.NfId, profile) + if err != nil { + initLog.Errorf("AUSF register to NRF Error[%s]", err.Error()) + } else { + //stop keepAliveTimer if its running + ausf.StartKeepAliveTimer(prof) + logger.CfgLog.Infof("Sent Register NF Instance with updated profile") + } } else { - //stop keepAliveTimer if its running - ausf.StartKeepAliveTimer(prof) - logger.CfgLog.Infof("Sent Register NF Instance with updated profile") + // stopping keepAlive timer + KeepAliveTimerMutex.Lock() + ausf.StopKeepAliveTimer() + KeepAliveTimerMutex.Unlock() + initLog.Infof("AUSF is not having Minimum Config to Register/Update to NRF") + problemDetails, err := consumer.SendDeregisterNFInstance() + if problemDetails != nil { + initLog.Errorf("AUSF Deregister Instance to NRF failed, Problem: [+%v]", problemDetails) + } + if err != nil { + initLog.Errorf("AUSF Deregister Instance to NRF Error[%s]", err.Error()) + } else { + logger.InitLog.Infof("Deregister from NRF successfully") + } } } }