From e8eac9121e19a352a11e7933efcae77b210e482a Mon Sep 17 00:00:00 2001 From: newb1er Date: Wed, 24 Apr 2024 17:52:25 +0800 Subject: [PATCH 01/32] refactor: sbi server --- cmd/main.go | 2 +- internal/sbi/server.go | 129 +++++++++++++++++++++++++++++++++++++++++ pkg/service/init.go | 110 +++++++++++++++++------------------ 3 files changed, 185 insertions(+), 56 deletions(-) create mode 100644 internal/sbi/server.go diff --git a/cmd/main.go b/cmd/main.go index 2a9a6f1..da046d6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -56,7 +56,7 @@ func action(cliCtx *cli.Context) error { } factory.NssfConfig = cfg - nssf, err := service.NewApp(cfg) + nssf, err := service.NewApp(cfg, tlsKeyLogPath) if err != nil { return err } diff --git a/internal/sbi/server.go b/internal/sbi/server.go new file mode 100644 index 0000000..b9323be --- /dev/null +++ b/internal/sbi/server.go @@ -0,0 +1,129 @@ +package sbi + +import ( + "context" + "fmt" + "net/http" + "sync" + "time" + + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/sbi/nssaiavailability" + "github.com/free5gc/nssf/internal/sbi/nsselection" + "github.com/free5gc/nssf/pkg/factory" + "github.com/free5gc/util/httpwrapper" + logger_util "github.com/free5gc/util/logger" + "github.com/gin-gonic/gin" +) + +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + +type Server struct { + Nssf + + httpServer *http.Server + router *gin.Engine +} + +func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { + router := newRouter() + server, err := bindRouter(nssf, router, tlsKeyLogPath) + + if err != nil { + logger.SBILog.Errorf("bind Router Error: %+v", err) + panic("Server initialization failed") + } + + return &Server{ + Nssf: nssf, + httpServer: server, + router: router, + } +} + +func (s *Server) Run(wg *sync.WaitGroup) { + logger.SBILog.Info("Starting server...") + + wg.Add(1) + go func() { + defer wg.Done() + + err := s.serve() + if err != http.ErrServerClosed { + logger.SBILog.Panicf("HTTP server setup failed: %+v", err) + } + }() +} + +func (s *Server) Shutdown() { + s.shutdownHttpServer() +} + +func (s *Server) shutdownHttpServer() { + const shutdownTimeout time.Duration = 2 * time.Second + + if s.httpServer == nil { + return + } + + shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + + err := s.httpServer.Shutdown(shutdownCtx) + if err != nil { + logger.SBILog.Errorf("HTTP server shutdown failed: %+v", err) + } +} + +func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { + sbiConfig := nssf.Config().Configuration.Sbi + bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) + + return httpwrapper.NewHttp2Server(bindAddr, tlsKeyLogPath, router) +} + +func newRouter() *gin.Engine { + router := logger_util.NewGinWithLogrus(logger.GinLog) + + nssaiavailability.AddService(router) + nsselection.AddService(router) + + return router +} + +func (s *Server) unsecureServe() error { + return s.httpServer.ListenAndServe() +} + +func (s *Server) secureServe() error { + sbiConfig := s.Nssf.Config().Configuration.Sbi + + pemPath := sbiConfig.Tls.Pem + if pemPath == "" { + pemPath = factory.NssfDefaultCertPemPath + } + + keyPath := sbiConfig.Tls.Key + if keyPath == "" { + keyPath = factory.NssfDefaultPrivateKeyPath + } + + return s.httpServer.ListenAndServeTLS(pemPath, keyPath) +} + +func (s *Server) serve() error { + sbiConfig := s.Nssf.Config().Configuration.Sbi + + switch sbiConfig.Scheme { + case "http": + return s.unsecureServe() + case "https": + return s.secureServe() + default: + return fmt.Errorf("invalid SBI scheme: %s", sbiConfig.Scheme) + } +} diff --git a/pkg/service/init.go b/pkg/service/init.go index 84aab10..b0ecd9f 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -10,36 +10,48 @@ import ( "os" "os/signal" "runtime/debug" + "sync" "syscall" "github.com/sirupsen/logrus" nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" - "github.com/free5gc/nssf/internal/sbi/nssaiavailability" - "github.com/free5gc/nssf/internal/sbi/nsselection" "github.com/free5gc/nssf/pkg/factory" - "github.com/free5gc/util/httpwrapper" - logger_util "github.com/free5gc/util/logger" ) type NssfApp struct { cfg *factory.Config nssfCtx *nssf_context.NSSFContext + + sbiServer *sbi.Server + wg sync.WaitGroup } -func NewApp(cfg *factory.Config) (*NssfApp, error) { - nssf := &NssfApp{cfg: cfg} +func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { + nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} nssf.SetLogEnable(cfg.GetLogEnable()) nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) + sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) + nssf.sbiServer = sbiServer + nssf_context.Init() nssf.nssfCtx = nssf_context.GetSelf() return nssf, nil } +func (a *NssfApp) Config() *factory.Config { + return a.cfg +} + +func (a *NssfApp) Context() *nssf_context.NSSFContext { + return a.nssfCtx +} + func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { @@ -82,36 +94,34 @@ func (a *NssfApp) SetReportCaller(reportCaller bool) { logger.Log.SetReportCaller(reportCaller) } -func (a *NssfApp) Start(tlsKeyLogPath string) { - logger.InitLog.Infoln("Server started") - - router := logger_util.NewGinWithLogrus(logger.GinLog) +func (a *NssfApp) registerToNrf() error { + nssfContext := a.nssfCtx - nssaiavailability.AddService(router) - nsselection.AddService(router) - - pemPath := factory.NssfDefaultCertPemPath - keyPath := factory.NssfDefaultPrivateKeyPath - sbi := factory.NssfConfig.Configuration.Sbi - if sbi.Tls != nil { - pemPath = sbi.Tls.Pem - keyPath = sbi.Tls.Key + profile, err := consumer.BuildNFProfile(nssfContext) + if err != nil { + return fmt.Errorf("failed to build NSSF profile") } - self := a.nssfCtx - nssf_context.InitNssfContext() - addr := fmt.Sprintf("%s:%d", self.BindingIPv4, self.SBIPort) - - // Register to NRF - profile, err := consumer.BuildNFProfile(self) + _, nssfContext.NfId, err = consumer.SendRegisterNFInstance(nssfContext.NrfUri, nssfContext.NfId, profile) if err != nil { - logger.InitLog.Error("Failed to build NSSF profile") + return fmt.Errorf("failed to register NSSF to NRF: %s", err.Error()) } - _, self.NfId, err = consumer.SendRegisterNFInstance(self.NrfUri, self.NfId, profile) - if err != nil { - logger.InitLog.Errorf("Failed to register NSSF to NRF: %s", err.Error()) + + return nil +} + +func (a *NssfApp) deregisterFromNrf() { + problemDetails, err := consumer.SendDeregisterNFInstance() + if problemDetails != nil { + logger.InitLog.Errorf("Deregister NF instance Failed Problem[%+v]", problemDetails) + } else if err != nil { + logger.InitLog.Errorf("Deregister NF instance Error[%+v]", err) + } else { + logger.InitLog.Infof("Deregister from NRF successfully") } +} +func (a *NssfApp) addSigTermHandler() { signalChannel := make(chan os.Signal, 1) signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) go func() { @@ -126,41 +136,31 @@ func (a *NssfApp) Start(tlsKeyLogPath string) { a.Terminate() os.Exit(0) }() +} - server, err := httpwrapper.NewHttp2Server(addr, tlsKeyLogPath, router) - - if server == nil { - logger.InitLog.Errorf("Initialize HTTP server failed: %+v", err) - return - } +func (a *NssfApp) Start(tlsKeyLogPath string) { + logger.InitLog.Infoln("Server started") + err := a.registerToNrf() if err != nil { - logger.InitLog.Warnf("Initialize HTTP server: +%v", err) + logger.InitLog.Errorf("Register to NRF failed: %+v", err) } - serverScheme := factory.NssfConfig.Configuration.Sbi.Scheme - if serverScheme == "http" { - err = server.ListenAndServe() - } else if serverScheme == "https" { - err = server.ListenAndServeTLS(pemPath, keyPath) - } + // Gracefull deregister when panic + defer func() { + if p := recover(); p != nil { + logger.InitLog.Errorf("panic: %v\n%s", p, string(debug.Stack())) + a.deregisterFromNrf() + } + }() - if err != nil { - logger.InitLog.Fatalf("HTTP server setup failed: %+v", err) - } + a.sbiServer.Run(&a.wg) + a.addSigTermHandler() } func (nssf *NssfApp) Terminate() { logger.InitLog.Infof("Terminating NSSF...") - // deregister with NRF - problemDetails, err := consumer.SendDeregisterNFInstance() - if problemDetails != nil { - logger.InitLog.Errorf("Deregister NF instance Failed Problem[%+v]", problemDetails) - } else if err != nil { - logger.InitLog.Errorf("Deregister NF instance Error[%+v]", err) - } else { - logger.InitLog.Infof("Deregister from NRF successfully") - } - + nssf.deregisterFromNrf() + nssf.sbiServer.Shutdown() logger.InitLog.Infof("NSSF terminated") } From 821003910026af457a1c282f4b890d74163ff307 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 13:40:02 +0800 Subject: [PATCH 02/32] refactor: processor as route handler --- .../api_nf_instance_id_document.go | 139 --------------- .../nssaiavailability_store.go | 10 +- .../nssaiavailability_subscription.go | 6 +- internal/sbi/nssaiavailability/routers.go | 124 ------------- .../api_network_slice_information_document.go | 41 ----- .../network_slice_information_document.go | 2 +- .../nsselection_for_pdu_session.go | 2 +- .../nsselection_for_registration.go | 2 +- internal/sbi/nsselection/routers.go | 92 ---------- internal/sbi/nssf.go | 13 ++ .../network_slice_information_document.go | 33 ++++ .../sbi/processor/nf_instance_id_document.go | 168 ++++++++++++++++++ internal/sbi/processor/processor.go | 23 +++ .../subscription_id_document.go} | 36 ++-- .../subscriptions_collection.go} | 27 +-- .../sbi/producer/nf_instance_id_document.go | 83 --------- .../sbi/producer/subscription_id_document.go | 38 ---- .../sbi/producer/subscriptions_collection.go | 41 ----- internal/sbi/server/api_nssaiavailability.go | 60 +++++++ internal/sbi/server/api_nsselection.go | 28 +++ internal/sbi/server/router.go | 38 ++++ internal/sbi/{ => server}/server.go | 42 +++-- pkg/service/init.go | 16 +- 23 files changed, 436 insertions(+), 628 deletions(-) delete mode 100644 internal/sbi/nssaiavailability/api_nf_instance_id_document.go rename internal/sbi/{producer => nssaiavailability}/nssaiavailability_store.go (94%) rename internal/sbi/{producer => nssaiavailability}/nssaiavailability_subscription.go (93%) delete mode 100644 internal/sbi/nssaiavailability/routers.go delete mode 100644 internal/sbi/nsselection/api_network_slice_information_document.go rename internal/sbi/{producer => nsselection}/network_slice_information_document.go (99%) rename internal/sbi/{producer => nsselection}/nsselection_for_pdu_session.go (99%) rename internal/sbi/{producer => nsselection}/nsselection_for_registration.go (99%) delete mode 100644 internal/sbi/nsselection/routers.go create mode 100644 internal/sbi/nssf.go create mode 100644 internal/sbi/processor/network_slice_information_document.go create mode 100644 internal/sbi/processor/nf_instance_id_document.go create mode 100644 internal/sbi/processor/processor.go rename internal/sbi/{nssaiavailability/api_subscription_id_document.go => processor/subscription_id_document.go} (53%) rename internal/sbi/{nssaiavailability/api_subscriptions_collection.go => processor/subscriptions_collection.go} (64%) delete mode 100644 internal/sbi/producer/nf_instance_id_document.go delete mode 100644 internal/sbi/producer/subscription_id_document.go delete mode 100644 internal/sbi/producer/subscriptions_collection.go create mode 100644 internal/sbi/server/api_nssaiavailability.go create mode 100644 internal/sbi/server/api_nsselection.go create mode 100644 internal/sbi/server/router.go rename internal/sbi/{ => server}/server.go (73%) diff --git a/internal/sbi/nssaiavailability/api_nf_instance_id_document.go b/internal/sbi/nssaiavailability/api_nf_instance_id_document.go deleted file mode 100644 index ac1f874..0000000 --- a/internal/sbi/nssaiavailability/api_nf_instance_id_document.go +++ /dev/null @@ -1,139 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package nssaiavailability - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/plugin" - "github.com/free5gc/nssf/internal/sbi/producer" - "github.com/free5gc/openapi" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -func HTTPNSSAIAvailabilityDelete(c *gin.Context) { - req := httpwrapper.NewRequest(c.Request, nil) - req.Params["nfId"] = c.Params.ByName("nfId") - - rsp := producer.HandleNSSAIAvailabilityDelete(req) - - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NssaiavailLog.Errorln(err) - problemDetails := models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), - } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) - } -} - -func HTTPNSSAIAvailabilityPatch(c *gin.Context) { - var nssaiAvailabilityUpdateInfo plugin.PatchDocument - - requestBody, err := c.GetRawData() - if err != nil { - problemDetail := models.ProblemDetails{ - Title: "System failure", - Status: http.StatusInternalServerError, - Detail: err.Error(), - Cause: "SYSTEM_FAILURE", - } - logger.NssaiavailLog.Errorf("Get Request Body error: %+v", err) - c.JSON(http.StatusInternalServerError, problemDetail) - return - } - - err = openapi.Deserialize(&nssaiAvailabilityUpdateInfo, requestBody, "application/json") - if err != nil { - problemDetail := "[Request Body] " + err.Error() - rsp := models.ProblemDetails{ - Title: "Malformed request syntax", - Status: http.StatusBadRequest, - Detail: problemDetail, - } - logger.NssaiavailLog.Errorln(problemDetail) - c.JSON(http.StatusBadRequest, rsp) - return - } - - req := httpwrapper.NewRequest(c.Request, nssaiAvailabilityUpdateInfo) - req.Params["nfId"] = c.Params.ByName("nfId") - - rsp := producer.HandleNSSAIAvailabilityPatch(req) - - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NssaiavailLog.Errorln(err) - problemDetails := models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), - } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) - } -} - -func HTTPNSSAIAvailabilityPut(c *gin.Context) { - var nssaiAvailabilityInfo models.NssaiAvailabilityInfo - - requestBody, err := c.GetRawData() - if err != nil { - problemDetail := models.ProblemDetails{ - Title: "System failure", - Status: http.StatusInternalServerError, - Detail: err.Error(), - Cause: "SYSTEM_FAILURE", - } - logger.NssaiavailLog.Errorf("Get Request Body error: %+v", err) - c.JSON(http.StatusInternalServerError, problemDetail) - return - } - - err = openapi.Deserialize(&nssaiAvailabilityInfo, requestBody, "application/json") - if err != nil { - problemDetail := "[Request Body] " + err.Error() - rsp := models.ProblemDetails{ - Title: "Malformed request syntax", - Status: http.StatusBadRequest, - Detail: problemDetail, - } - logger.NssaiavailLog.Errorln(problemDetail) - c.JSON(http.StatusBadRequest, rsp) - return - } - - req := httpwrapper.NewRequest(c.Request, nssaiAvailabilityInfo) - req.Params["nfId"] = c.Params.ByName("nfId") - - rsp := producer.HandleNSSAIAvailabilityPut(req) - - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NssaiavailLog.Errorln(err) - problemDetails := models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), - } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) - } -} diff --git a/internal/sbi/producer/nssaiavailability_store.go b/internal/sbi/nssaiavailability/nssaiavailability_store.go similarity index 94% rename from internal/sbi/producer/nssaiavailability_store.go rename to internal/sbi/nssaiavailability/nssaiavailability_store.go index 73aa784..609c1a0 100644 --- a/internal/sbi/producer/nssaiavailability_store.go +++ b/internal/sbi/nssaiavailability/nssaiavailability_store.go @@ -4,7 +4,7 @@ * NSSF NSSAI Availability Service */ -package producer +package nssaiavailability import ( "bytes" @@ -22,8 +22,7 @@ import ( "github.com/free5gc/openapi/models" ) -// NSSAIAvailability DELETE method -func NSSAIAvailabilityDeleteProcedure(nfId string) *models.ProblemDetails { +func NfInstanceDelete(nfId string) *models.ProblemDetails { var problemDetails *models.ProblemDetails for i, amfConfig := range factory.NssfConfig.Configuration.AmfList { if amfConfig.NfId == nfId { @@ -42,8 +41,7 @@ func NSSAIAvailabilityDeleteProcedure(nfId string) *models.ProblemDetails { return problemDetails } -// NSSAIAvailability PATCH method -func NSSAIAvailabilityPatchProcedure(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId string) ( +func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId string) ( *models.AuthorizedNssaiAvailabilityInfo, *models.ProblemDetails, ) { var ( @@ -152,7 +150,7 @@ func NSSAIAvailabilityPatchProcedure(nssaiAvailabilityUpdateInfo plugin.PatchDoc } // NSSAIAvailability PUT method -func NSSAIAvailabilityPutProcedure(nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId string) ( +func NfInstanceUpdate(nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId string) ( *models.AuthorizedNssaiAvailabilityInfo, *models.ProblemDetails, ) { var ( diff --git a/internal/sbi/producer/nssaiavailability_subscription.go b/internal/sbi/nssaiavailability/nssaiavailability_subscription.go similarity index 93% rename from internal/sbi/producer/nssaiavailability_subscription.go rename to internal/sbi/nssaiavailability/nssaiavailability_subscription.go index 769d801..0d37329 100644 --- a/internal/sbi/producer/nssaiavailability_subscription.go +++ b/internal/sbi/nssaiavailability/nssaiavailability_subscription.go @@ -4,7 +4,7 @@ * NSSF NSSAI Availability Service */ -package producer +package nssaiavailability import ( "fmt" @@ -43,7 +43,7 @@ func getUnusedSubscriptionID() (string, error) { } // NSSAIAvailability subscription POST method -func NSSAIAvailabilityPostProcedure(createData models.NssfEventSubscriptionCreateData) ( +func SubscriptionCreate(createData models.NssfEventSubscriptionCreateData) ( *models.NssfEventSubscriptionCreatedData, *models.ProblemDetails, ) { var ( @@ -80,7 +80,7 @@ func NSSAIAvailabilityPostProcedure(createData models.NssfEventSubscriptionCreat return response, nil } -func NSSAIAvailabilityUnsubscribeProcedure(subscriptionId string) *models.ProblemDetails { +func SubscriptionUnsubscribe(subscriptionId string) *models.ProblemDetails { var problemDetails *models.ProblemDetails factory.NssfConfig.Lock() diff --git a/internal/sbi/nssaiavailability/routers.go b/internal/sbi/nssaiavailability/routers.go deleted file mode 100644 index 4cccca1..0000000 --- a/internal/sbi/nssaiavailability/routers.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package nssaiavailability - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/util" - "github.com/free5gc/nssf/pkg/factory" - "github.com/free5gc/openapi/models" - logger_util "github.com/free5gc/util/logger" -) - -// Route is the information for every URI. -type Route struct { - // 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 - // HandlerFunc is the handler function of this route. - HandlerFunc gin.HandlerFunc -} - -// Routes is the list of the generated Route. -type Routes []Route - -// NewRouter returns a new router. -func NewRouter() *gin.Engine { - router := logger_util.NewGinWithLogrus(logger.GinLog) - AddService(router) - return router -} - -func AddService(engine *gin.Engine) *gin.RouterGroup { - group := engine.Group(factory.NssfNssaiavailResUriPrefix) - - routerAuthorizationCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSAIAVAILABILITY) - group.Use(func(c *gin.Context) { - routerAuthorizationCheck.Check(c, nssf_context.GetSelf()) - }) - - 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 "DELETE": - group.DELETE(route.Pattern, route.HandlerFunc) - case "PATCH": - group.PATCH(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{ - { - "Index", - "GET", - "/", - Index, - }, - - { - "NSSAIAvailabilityDelete", - strings.ToUpper("Delete"), - "/nssai-availability/:nfId", - HTTPNSSAIAvailabilityDelete, - }, - - { - "NSSAIAvailabilityPatch", - strings.ToUpper("Patch"), - "/nssai-availability/:nfId", - HTTPNSSAIAvailabilityPatch, - }, - - { - "NSSAIAvailabilityPut", - strings.ToUpper("Put"), - "/nssai-availability/:nfId", - HTTPNSSAIAvailabilityPut, - }, - - // Regular expressions for route matching should be unique in Gin package - // 'subscriptions' would conflict with existing wildcard ':nfId' - // Simply replace 'subscriptions' with ':nfId' and check if ':nfId' is 'subscriptions' in handler function - { - "NSSAIAvailabilityUnsubscribe", - strings.ToUpper("Delete"), - // "/nssai-availability/subscriptions/:subscriptionId", - "/nssai-availability/:nfId/:subscriptionId", - HTTPNSSAIAvailabilityUnsubscribe, - }, - - { - "NSSAIAvailabilityPost", - strings.ToUpper("Post"), - "/nssai-availability/subscriptions", - HTTPNSSAIAvailabilityPost, - }, -} diff --git a/internal/sbi/nsselection/api_network_slice_information_document.go b/internal/sbi/nsselection/api_network_slice_information_document.go deleted file mode 100644 index 7b60808..0000000 --- a/internal/sbi/nsselection/api_network_slice_information_document.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * NSSF NS Selection - * - * NSSF Network Slice Selection Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package nsselection - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/producer" - "github.com/free5gc/openapi" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -func HTTPNetworkSliceInformationDocument(c *gin.Context) { - req := httpwrapper.NewRequest(c.Request, nil) - - rsp := producer.HandleNSSelectionGet(req) - - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NsselLog.Errorln(err) - problemDetails := models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), - } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) - } -} diff --git a/internal/sbi/producer/network_slice_information_document.go b/internal/sbi/nsselection/network_slice_information_document.go similarity index 99% rename from internal/sbi/producer/network_slice_information_document.go rename to internal/sbi/nsselection/network_slice_information_document.go index 8421226..7b9968d 100644 --- a/internal/sbi/producer/network_slice_information_document.go +++ b/internal/sbi/nsselection/network_slice_information_document.go @@ -7,7 +7,7 @@ * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ -package producer +package nsselection import ( "encoding/json" diff --git a/internal/sbi/producer/nsselection_for_pdu_session.go b/internal/sbi/nsselection/nsselection_for_pdu_session.go similarity index 99% rename from internal/sbi/producer/nsselection_for_pdu_session.go rename to internal/sbi/nsselection/nsselection_for_pdu_session.go index b4dddff..dd1d392 100644 --- a/internal/sbi/producer/nsselection_for_pdu_session.go +++ b/internal/sbi/nsselection/nsselection_for_pdu_session.go @@ -4,7 +4,7 @@ * NSSF Network Slice Selection Service */ -package producer +package nsselection import ( "fmt" diff --git a/internal/sbi/producer/nsselection_for_registration.go b/internal/sbi/nsselection/nsselection_for_registration.go similarity index 99% rename from internal/sbi/producer/nsselection_for_registration.go rename to internal/sbi/nsselection/nsselection_for_registration.go index 460492f..9a09479 100644 --- a/internal/sbi/producer/nsselection_for_registration.go +++ b/internal/sbi/nsselection/nsselection_for_registration.go @@ -4,7 +4,7 @@ * NSSF Network Slice Selection Service */ -package producer +package nsselection import ( "net/http" diff --git a/internal/sbi/nsselection/routers.go b/internal/sbi/nsselection/routers.go deleted file mode 100644 index f5f0b7c..0000000 --- a/internal/sbi/nsselection/routers.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - * NSSF NS Selection - * - * NSSF Network Slice Selection Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package nsselection - -import ( - "net/http" - "strings" - - "github.com/gin-gonic/gin" - - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/util" - "github.com/free5gc/nssf/pkg/factory" - "github.com/free5gc/openapi/models" - logger_util "github.com/free5gc/util/logger" -) - -// Route is the information for every URI. -type Route struct { - // 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 - // HandlerFunc is the handler function of this route. - HandlerFunc gin.HandlerFunc -} - -// Routes is the list of the generated Route. -type Routes []Route - -// NewRouter returns a new router. -func NewRouter() *gin.Engine { - router := logger_util.NewGinWithLogrus(logger.GinLog) - AddService(router) - return router -} - -func AddService(engine *gin.Engine) *gin.RouterGroup { - group := engine.Group(factory.NssfNsselectResUriPrefix) - - routerAuthorizationCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSELECTION) - group.Use(func(c *gin.Context) { - routerAuthorizationCheck.Check(c, nssf_context.GetSelf()) - }) - - 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 "DELETE": - group.DELETE(route.Pattern, route.HandlerFunc) - case "PATCH": - group.PATCH(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{ - { - "Index", - "GET", - "/", - Index, - }, - - { - "NSSelectionGet", - strings.ToUpper("Get"), - "/network-slice-information", - HTTPNetworkSliceInformationDocument, - }, -} diff --git a/internal/sbi/nssf.go b/internal/sbi/nssf.go new file mode 100644 index 0000000..dafe39e --- /dev/null +++ b/internal/sbi/nssf.go @@ -0,0 +1,13 @@ +package sbi + +import ( + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/internal/sbi/processor" + "github.com/free5gc/nssf/pkg/factory" +) + +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext + Processor() *processor.Processor +} diff --git a/internal/sbi/processor/network_slice_information_document.go b/internal/sbi/processor/network_slice_information_document.go new file mode 100644 index 0000000..2cd1f47 --- /dev/null +++ b/internal/sbi/processor/network_slice_information_document.go @@ -0,0 +1,33 @@ +/* + * NSSF NS Selection + * + * NSSF Network Slice Selection Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package processor + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/sbi/nsselection" +) + +func (p *Processor) HandleNetworkSliceInformationGet(c *gin.Context) { + logger.NsselLog.Infof("Handle NSSelectionGet") + + query := c.Request.URL.Query() + authorizedNetworkSliceInfo, problemDetails := nsselection.NSSelectionGetProcedure(query) + + if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return + } + + c.JSON(http.StatusOK, authorizedNetworkSliceInfo) +} diff --git a/internal/sbi/processor/nf_instance_id_document.go b/internal/sbi/processor/nf_instance_id_document.go new file mode 100644 index 0000000..ca41dcb --- /dev/null +++ b/internal/sbi/processor/nf_instance_id_document.go @@ -0,0 +1,168 @@ +/* + * NSSF NSSAI Availability + * + * NSSF NSSAI Availability Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) + */ + +package processor + +import ( + "net/http" + + "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/plugin" + "github.com/free5gc/nssf/internal/sbi/nssaiavailability" + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" + "github.com/gin-gonic/gin" +) + +// HandleNSSAIAvailabilityDelete - Deletes an already existing S-NSSAIs per TA +// provided by the NF service consumer (e.g AMF) +func (p *Processor) HandleNSSAIAvailabilityDelete(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityDelete") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + problemDetails := nssaiavailability.NfInstanceDelete(nfId) + + if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return + } + + c.Status(http.StatusNoContent) +} + +// HandleNSSAIAvailabilityPatch - Updates an already existing S-NSSAIs per TA +// provided by the NF service consumer (e.g AMF) +func (p *Processor) HandleNSSAIAvailabilityPatch(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPatch") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + var patchDocument plugin.PatchDocument + + requestBody, err := c.GetRawData() + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + } + + c.JSON(http.StatusInternalServerError, problemDetails) + return + } + + if err = openapi.Deserialize(&patchDocument, requestBody, "application/json"); err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + logger.SBILog.Errorf("Error deserializing patch document: %+v", err) + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + // TODO: Request NfProfile of NfId from NRF + // Check if NfId is valid AMF and obtain AMF Set ID + // If NfId is invalid, return ProblemDetails with code 404 Not Found + // If NF consumer is not authorized to update NSSAI availability, return ProblemDetails with code 403 Forbidden + + info, problemDetails := nssaiavailability.NfInstancePatch(patchDocument, nfId) + + if info != nil { + c.JSON(http.StatusOK, info) + return + } else if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return + } + + problemDetails = &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + c.JSON(http.StatusForbidden, problemDetails) +} + +// HandleNSSAIAvailabilityPut - Updates/replaces the NSSF +// with the S-NSSAIs the NF service consumer (e.g AMF) supports per TA +func (p *Processor) HandleNSSAIAvailabilityPut(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPut") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + var nssaiAvailabilityInfo models.NssaiAvailabilityInfo + data, err := c.GetRawData() + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + } + + c.JSON(http.StatusInternalServerError, problemDetails) + return + } + + if err = openapi.Deserialize(&nssaiAvailabilityInfo, data, "application/json"); err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + logger.SBILog.Errorf("Error deserializing NSSAI availability info: %+v", err) + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + authorizedNssaiAvailabilityInfo, problemDetails := nssaiavailability.NfInstanceUpdate(nssaiAvailabilityInfo, nfId) + + if authorizedNssaiAvailabilityInfo != nil { + c.JSON(http.StatusOK, authorizedNssaiAvailabilityInfo) + return + } else if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return + } + + problemDetails = &models.ProblemDetails{ + Status: http.StatusForbidden, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + c.JSON(http.StatusForbidden, problemDetails) +} diff --git a/internal/sbi/processor/processor.go b/internal/sbi/processor/processor.go new file mode 100644 index 0000000..f5fd4ef --- /dev/null +++ b/internal/sbi/processor/processor.go @@ -0,0 +1,23 @@ +package processor + +import ( + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/pkg/factory" +) + +// TODO: Resolve the circular dependency between internal/sbi/processor/processor.go and internal/sbi/nssf.go +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext + Processor() *Processor +} + +type Processor struct { + Nssf +} + +func NewProcessor(nssf Nssf) *Processor { + return &Processor{ + Nssf: nssf, + } +} diff --git a/internal/sbi/nssaiavailability/api_subscription_id_document.go b/internal/sbi/processor/subscription_id_document.go similarity index 53% rename from internal/sbi/nssaiavailability/api_subscription_id_document.go rename to internal/sbi/processor/subscription_id_document.go index f0ffa94..2e8de46 100644 --- a/internal/sbi/nssaiavailability/api_subscription_id_document.go +++ b/internal/sbi/processor/subscription_id_document.go @@ -7,7 +7,7 @@ * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ -package nssaiavailability +package processor import ( "net/http" @@ -15,13 +15,12 @@ import ( "github.com/gin-gonic/gin" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/producer" - "github.com/free5gc/openapi" + "github.com/free5gc/nssf/internal/sbi/nssaiavailability" "github.com/free5gc/openapi/models" "github.com/free5gc/util/httpwrapper" ) -func HTTPNSSAIAvailabilityUnsubscribe(c *gin.Context) { +func (p *Processor) HTTPNSSAIAvailabilityUnsubscribe(c *gin.Context) { // Due to conflict of route matching, 'subscriptions' in the route is replaced with the existing wildcard ':nfId' nfID := c.Param("nfId") if nfID != "subscriptions" { @@ -33,18 +32,23 @@ func HTTPNSSAIAvailabilityUnsubscribe(c *gin.Context) { req := httpwrapper.NewRequest(c.Request, nil) req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") - rsp := producer.HandleNSSAIAvailabilityUnsubscribe(req) - - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NssaiavailLog.Errorln(err) - problemDetails := models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), + subscriptionId := c.Params.ByName("subscriptionId") + if subscriptionId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) + + c.JSON(http.StatusBadRequest, problemDetails) + return + } + + problemDetails := nssaiavailability.SubscriptionUnsubscribe(subscriptionId) + + if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return } + + c.Status(http.StatusNoContent) } diff --git a/internal/sbi/nssaiavailability/api_subscriptions_collection.go b/internal/sbi/processor/subscriptions_collection.go similarity index 64% rename from internal/sbi/nssaiavailability/api_subscriptions_collection.go rename to internal/sbi/processor/subscriptions_collection.go index b724f68..978a34c 100644 --- a/internal/sbi/nssaiavailability/api_subscriptions_collection.go +++ b/internal/sbi/processor/subscriptions_collection.go @@ -7,7 +7,7 @@ * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ -package nssaiavailability +package processor import ( "net/http" @@ -15,13 +15,12 @@ import ( "github.com/gin-gonic/gin" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/producer" + "github.com/free5gc/nssf/internal/sbi/nssaiavailability" "github.com/free5gc/openapi" . "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" ) -func HTTPNSSAIAvailabilityPost(c *gin.Context) { +func (p *Processor) HTTPNSSAIAvailabilityPost(c *gin.Context) { var createData NssfEventSubscriptionCreateData requestBody, err := c.GetRawData() @@ -50,22 +49,14 @@ func HTTPNSSAIAvailabilityPost(c *gin.Context) { return } - req := httpwrapper.NewRequest(c.Request, createData) + createdData, problemDetails := nssaiavailability.SubscriptionCreate(createData) - rsp := producer.HandleNSSAIAvailabilityPost(req) + if problemDetails != nil { + c.JSON(int(problemDetails.Status), problemDetails) + return + } // TODO: Based on TS 29.531 5.3.2.3.1, add location header - responseBody, err := openapi.Serialize(rsp.Body, "application/json") - if err != nil { - logger.NssaiavailLog.Errorln(err) - problemDetails := ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - Detail: err.Error(), - } - c.JSON(http.StatusInternalServerError, problemDetails) - } else { - c.Data(rsp.Status, "application/json", responseBody) - } + c.JSON(http.StatusCreated, createdData) } diff --git a/internal/sbi/producer/nf_instance_id_document.go b/internal/sbi/producer/nf_instance_id_document.go deleted file mode 100644 index f79eca8..0000000 --- a/internal/sbi/producer/nf_instance_id_document.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package producer - -import ( - "net/http" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/plugin" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -// HandleNSSAIAvailabilityDelete - Deletes an already existing S-NSSAIs per TA -// provided by the NF service consumer (e.g AMF) -func HandleNSSAIAvailabilityDelete(request *httpwrapper.Request) *httpwrapper.Response { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityDelete") - - nfID := request.Params["nfId"] - - problemDetails := NSSAIAvailabilityDeleteProcedure(nfID) - - if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) -} - -// HandleNSSAIAvailabilityPatch - Updates an already existing S-NSSAIs per TA -// provided by the NF service consumer (e.g AMF) -func HandleNSSAIAvailabilityPatch(request *httpwrapper.Request) *httpwrapper.Response { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPatch") - - nssaiAvailabilityUpdateInfo := request.Body.(plugin.PatchDocument) - nfID := request.Params["nfId"] - - // TODO: Request NfProfile of NfId from NRF - // Check if NfId is valid AMF and obtain AMF Set ID - // If NfId is invalid, return ProblemDetails with code 404 Not Found - // If NF consumer is not authorized to update NSSAI availability, return ProblemDetails with code 403 Forbidden - - response, problemDetails := NSSAIAvailabilityPatchProcedure(nssaiAvailabilityUpdateInfo, nfID) - - if response != nil { - return httpwrapper.NewResponse(http.StatusOK, nil, response) - } else if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", - } - return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) -} - -// HandleNSSAIAvailabilityPut - Updates/replaces the NSSF -// with the S-NSSAIs the NF service consumer (e.g AMF) supports per TA -func HandleNSSAIAvailabilityPut(request *httpwrapper.Request) *httpwrapper.Response { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPut") - - nssaiAvailabilityInfo := request.Body.(models.NssaiAvailabilityInfo) - nfID := request.Params["nfId"] - - response, problemDetails := NSSAIAvailabilityPutProcedure(nssaiAvailabilityInfo, nfID) - - if response != nil { - return httpwrapper.NewResponse(http.StatusOK, nil, response) - } else if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", - } - return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) -} diff --git a/internal/sbi/producer/subscription_id_document.go b/internal/sbi/producer/subscription_id_document.go deleted file mode 100644 index 54d98a6..0000000 --- a/internal/sbi/producer/subscription_id_document.go +++ /dev/null @@ -1,38 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package producer - -import ( - "net/http" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -// HandleNSSAIAvailabilityUnsubscribe - Deletes an already existing NSSAI availability notification subscription -func HandleNSSAIAvailabilityUnsubscribe(request *httpwrapper.Request) *httpwrapper.Response { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityUnsubscribe") - - subscriptionID := request.Params["subscriptionId"] - - problemDetails := NSSAIAvailabilityUnsubscribeProcedure(subscriptionID) - - if problemDetails == nil { - return httpwrapper.NewResponse(http.StatusNoContent, nil, nil) - } else if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", - } - return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) -} diff --git a/internal/sbi/producer/subscriptions_collection.go b/internal/sbi/producer/subscriptions_collection.go deleted file mode 100644 index 048ae8d..0000000 --- a/internal/sbi/producer/subscriptions_collection.go +++ /dev/null @@ -1,41 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package producer - -import ( - "net/http" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -// HandleNSSAIAvailabilityPost - Creates subscriptions for notification about updates to NSSAI availability information -func HandleNSSAIAvailabilityPost(request *httpwrapper.Request) *httpwrapper.Response { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPost") - - createData := request.Body.(models.NssfEventSubscriptionCreateData) - - // TODO: If NF consumer is not authorized to update NSSAI availability, return ProblemDetails with code 403 Forbidden - - response, problemDetails := NSSAIAvailabilityPostProcedure(createData) - - if response != nil { - // TODO: Based on TS 29.531 5.3.2.3.1, add location header - return httpwrapper.NewResponse(http.StatusCreated, nil, response) - } else if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", - } - return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) -} diff --git a/internal/sbi/server/api_nssaiavailability.go b/internal/sbi/server/api_nssaiavailability.go new file mode 100644 index 0000000..dea7043 --- /dev/null +++ b/internal/sbi/server/api_nssaiavailability.go @@ -0,0 +1,60 @@ +package server + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func (s *Server) getNssaiAvailabilityRoutes() []Route { + return []Route{ + { + "Health Check", + strings.ToUpper("Get"), + "/", + func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, gin.H{"status": "Service Available"}) + }, + }, + + { + "NSSAIAvailabilityDelete", + strings.ToUpper("Delete"), + "/nssai-availability/:nfId", + s.Processor().HandleNSSAIAvailabilityDelete, + }, + + { + "NSSAIAvailabilityPatch", + strings.ToUpper("Patch"), + "/nssai-availability/:nfId", + s.Processor().HandleNSSAIAvailabilityPatch, + }, + + { + "NSSAIAvailabilityPut", + strings.ToUpper("Put"), + "/nssai-availability/:nfId", + s.Processor().HandleNSSAIAvailabilityPut, + }, + + // Regular expressions for route matching should be unique in Gin package + // 'subscriptions' would conflict with existing wildcard ':nfId' + // Simply replace 'subscriptions' with ':nfId' and check if ':nfId' is 'subscriptions' in handler function + { + "NSSAIAvailabilityUnsubscribe", + strings.ToUpper("Delete"), + // "/nssai-availability/subscriptions/:subscriptionId", + "/nssai-availability/:nfId/:subscriptionId", + s.Processor().HTTPNSSAIAvailabilityUnsubscribe, + }, + + { + "NSSAIAvailabilityPost", + strings.ToUpper("Post"), + "/nssai-availability/subscriptions", + s.Processor().HTTPNSSAIAvailabilityPost, + }, + } +} diff --git a/internal/sbi/server/api_nsselection.go b/internal/sbi/server/api_nsselection.go new file mode 100644 index 0000000..638f63e --- /dev/null +++ b/internal/sbi/server/api_nsselection.go @@ -0,0 +1,28 @@ +package server + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func (s *Server) getNsSelectionRoutes() []Route { + return []Route{ + { + "Helth Check", + strings.ToUpper("Get"), + "/", + func(ctx *gin.Context) { + ctx.JSON(http.StatusOK, gin.H{"status": "Service Available"}) + }, + }, + + { + "NSSelectionGet", + strings.ToUpper("Get"), + "/network-slice-information", + s.Processor().HandleNetworkSliceInformationGet, + }, + } +} diff --git a/internal/sbi/server/router.go b/internal/sbi/server/router.go new file mode 100644 index 0000000..b00efab --- /dev/null +++ b/internal/sbi/server/router.go @@ -0,0 +1,38 @@ +package server + +import ( + "github.com/gin-gonic/gin" +) + +// Route is the information for every URI. +type Route struct { + // 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 + // HandlerFunc is the handler function of this route. + HandlerFunc gin.HandlerFunc +} + +type RouteGroup interface { + AddService(engine *gin.Engine) *gin.RouterGroup +} + +func AddService(group *gin.RouterGroup, routes []Route) { + 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 "DELETE": + group.DELETE(route.Pattern, route.HandlerFunc) + case "PATCH": + group.PATCH(route.Pattern, route.HandlerFunc) + } + } +} diff --git a/internal/sbi/server.go b/internal/sbi/server/server.go similarity index 73% rename from internal/sbi/server.go rename to internal/sbi/server/server.go index b9323be..887305c 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server/server.go @@ -1,4 +1,4 @@ -package sbi +package server import ( "context" @@ -7,42 +7,35 @@ import ( "sync" "time" - nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/nssaiavailability" - "github.com/free5gc/nssf/internal/sbi/nsselection" + "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" "github.com/gin-gonic/gin" ) -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type Server struct { - Nssf + sbi.Nssf httpServer *http.Server router *gin.Engine } -func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { - router := newRouter() - server, err := bindRouter(nssf, router, tlsKeyLogPath) +func NewServer(nssf sbi.Nssf, tlsKeyLogPath string) *Server { + s := &Server{Nssf: nssf} + + s.router = newRouter(s) + + server, err := bindRouter(nssf, s.router, tlsKeyLogPath) + s.httpServer = server if err != nil { logger.SBILog.Errorf("bind Router Error: %+v", err) panic("Server initialization failed") } - return &Server{ - Nssf: nssf, - httpServer: server, - router: router, - } + return s } func (s *Server) Run(wg *sync.WaitGroup) { @@ -79,18 +72,23 @@ func (s *Server) shutdownHttpServer() { } } -func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { +func bindRouter(nssf sbi.Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { sbiConfig := nssf.Config().Configuration.Sbi bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) return httpwrapper.NewHttp2Server(bindAddr, tlsKeyLogPath, router) } -func newRouter() *gin.Engine { +func newRouter(s *Server) *gin.Engine { router := logger_util.NewGinWithLogrus(logger.GinLog) - nssaiavailability.AddService(router) - nsselection.AddService(router) + nssaiAvailabilityGroup := router.Group(factory.NssfNssaiavailResUriPrefix) + nssaiAvailabilityRoutes := s.getNssaiAvailabilityRoutes() + AddService(nssaiAvailabilityGroup, nssaiAvailabilityRoutes) + + nsSelectionGroup := router.Group(factory.NssfNsselectResUriPrefix) + nsSelectionRoutes := s.getNsSelectionRoutes() + AddService(nsSelectionGroup, nsSelectionRoutes) return router } diff --git a/pkg/service/init.go b/pkg/service/init.go index b0ecd9f..be633f6 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -19,6 +19,8 @@ import ( "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" + "github.com/free5gc/nssf/internal/sbi/processor" + "github.com/free5gc/nssf/internal/sbi/server" "github.com/free5gc/nssf/pkg/factory" ) @@ -26,17 +28,23 @@ type NssfApp struct { cfg *factory.Config nssfCtx *nssf_context.NSSFContext - sbiServer *sbi.Server wg sync.WaitGroup + sbiServer *server.Server + processor *processor.Processor } +var _ sbi.Nssf = &NssfApp{} + func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} nssf.SetLogEnable(cfg.GetLogEnable()) nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) - sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) + processor := processor.NewProcessor(nssf) + nssf.processor = processor + + sbiServer := server.NewServer(nssf, tlsKeyLogPath) nssf.sbiServer = sbiServer nssf_context.Init() @@ -52,6 +60,10 @@ func (a *NssfApp) Context() *nssf_context.NSSFContext { return a.nssfCtx } +func (a *NssfApp) Processor() *processor.Processor { + return a.processor +} + func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { From d866a36eadddc6b4f2d0c8a9c215548b90c5b54c Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 13:49:34 +0800 Subject: [PATCH 03/32] fix: nil dereference --- .../sbi/nssaiavailability/nssaiavailability_store.go | 10 +++++----- .../nssaiavailability_subscription.go | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/sbi/nssaiavailability/nssaiavailability_store.go b/internal/sbi/nssaiavailability/nssaiavailability_store.go index 609c1a0..0aeccbe 100644 --- a/internal/sbi/nssaiavailability/nssaiavailability_store.go +++ b/internal/sbi/nssaiavailability/nssaiavailability_store.go @@ -33,7 +33,7 @@ func NfInstanceDelete(nfId string) *models.ProblemDetails { } } - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusNotFound, Detail: fmt.Sprintf("AMF ID '%s' does not exist", nfId), @@ -81,7 +81,7 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri } factory.NssfConfig.RUnlock() if !hitAmf { - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusNotFound, Detail: fmt.Sprintf("AMF ID '%s' does not exist", nfId), @@ -108,7 +108,7 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri patch, err := jsonpatch.DecodePatch(patchJSON) if err != nil { - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.MALFORMED_REQUEST, Status: http.StatusBadRequest, Detail: err.Error(), @@ -118,7 +118,7 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri modified, err := patch.Apply(original) if err != nil { - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.INVALID_REQUEST, Status: http.StatusConflict, Detail: err.Error(), @@ -130,7 +130,7 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri err = json.Unmarshal(modified, &factory.NssfConfig.Configuration.AmfList[amfIdx].SupportedNssaiAvailabilityData) factory.NssfConfig.Unlock() if err != nil { - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.INVALID_REQUEST, Status: http.StatusBadRequest, Detail: err.Error(), diff --git a/internal/sbi/nssaiavailability/nssaiavailability_subscription.go b/internal/sbi/nssaiavailability/nssaiavailability_subscription.go index 0d37329..cc08f57 100644 --- a/internal/sbi/nssaiavailability/nssaiavailability_subscription.go +++ b/internal/sbi/nssaiavailability/nssaiavailability_subscription.go @@ -56,7 +56,7 @@ func SubscriptionCreate(createData models.NssfEventSubscriptionCreateData) ( if err != nil { logger.NssaiavailLog.Warnf(err.Error()) - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusNotFound, Detail: err.Error(), @@ -95,7 +95,7 @@ func SubscriptionUnsubscribe(subscriptionId string) *models.ProblemDetails { } // No specific subscription ID exists - *problemDetails = models.ProblemDetails{ + problemDetails = &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusNotFound, Detail: fmt.Sprintf("Subscription ID '%s' is not available", subscriptionId), From 99a72d3603b3336840c4cca76f4aa273b3957904 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 14:00:09 +0800 Subject: [PATCH 04/32] fix: ci complains --- internal/sbi/processor/nf_instance_id_document.go | 3 ++- internal/sbi/server/server.go | 3 ++- pkg/service/init.go | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/sbi/processor/nf_instance_id_document.go b/internal/sbi/processor/nf_instance_id_document.go index ca41dcb..2136608 100644 --- a/internal/sbi/processor/nf_instance_id_document.go +++ b/internal/sbi/processor/nf_instance_id_document.go @@ -12,12 +12,13 @@ package processor import ( "net/http" + "github.com/gin-gonic/gin" + "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/plugin" "github.com/free5gc/nssf/internal/sbi/nssaiavailability" "github.com/free5gc/openapi" "github.com/free5gc/openapi/models" - "github.com/gin-gonic/gin" ) // HandleNSSAIAvailabilityDelete - Deletes an already existing S-NSSAIs per TA diff --git a/internal/sbi/server/server.go b/internal/sbi/server/server.go index 887305c..0085541 100644 --- a/internal/sbi/server/server.go +++ b/internal/sbi/server/server.go @@ -7,12 +7,13 @@ import ( "sync" "time" + "github.com/gin-gonic/gin" + "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" - "github.com/gin-gonic/gin" ) type Server struct { diff --git a/pkg/service/init.go b/pkg/service/init.go index be633f6..125bad3 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -158,7 +158,7 @@ func (a *NssfApp) Start(tlsKeyLogPath string) { logger.InitLog.Errorf("Register to NRF failed: %+v", err) } - // Gracefull deregister when panic + // Graceful deregister when panic defer func() { if p := recover(); p != nil { logger.InitLog.Errorf("panic: %v\n%s", p, string(debug.Stack())) From 9bbfecff820025ccbad9cc1be9ca6b4962a14ebd Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 18:17:44 +0800 Subject: [PATCH 05/32] refactor: interface design --- .../sbi/{server => }/api_nssaiavailability.go | 2 +- internal/sbi/{server => }/api_nsselection.go | 2 +- internal/sbi/nssf.go | 13 ---------- internal/sbi/processor/processor.go | 2 -- internal/sbi/{server => }/router.go | 2 +- internal/sbi/{server => }/server.go | 26 ++++++++++++++----- pkg/service/init.go | 21 ++++++--------- 7 files changed, 31 insertions(+), 37 deletions(-) rename internal/sbi/{server => }/api_nssaiavailability.go (98%) rename internal/sbi/{server => }/api_nsselection.go (96%) delete mode 100644 internal/sbi/nssf.go rename internal/sbi/{server => }/router.go (98%) rename internal/sbi/{server => }/server.go (82%) diff --git a/internal/sbi/server/api_nssaiavailability.go b/internal/sbi/api_nssaiavailability.go similarity index 98% rename from internal/sbi/server/api_nssaiavailability.go rename to internal/sbi/api_nssaiavailability.go index dea7043..8f817b3 100644 --- a/internal/sbi/server/api_nssaiavailability.go +++ b/internal/sbi/api_nssaiavailability.go @@ -1,4 +1,4 @@ -package server +package sbi import ( "net/http" diff --git a/internal/sbi/server/api_nsselection.go b/internal/sbi/api_nsselection.go similarity index 96% rename from internal/sbi/server/api_nsselection.go rename to internal/sbi/api_nsselection.go index 638f63e..210247e 100644 --- a/internal/sbi/server/api_nsselection.go +++ b/internal/sbi/api_nsselection.go @@ -1,4 +1,4 @@ -package server +package sbi import ( "net/http" diff --git a/internal/sbi/nssf.go b/internal/sbi/nssf.go deleted file mode 100644 index dafe39e..0000000 --- a/internal/sbi/nssf.go +++ /dev/null @@ -1,13 +0,0 @@ -package sbi - -import ( - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/internal/sbi/processor" - "github.com/free5gc/nssf/pkg/factory" -) - -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext - Processor() *processor.Processor -} diff --git a/internal/sbi/processor/processor.go b/internal/sbi/processor/processor.go index f5fd4ef..0e2c659 100644 --- a/internal/sbi/processor/processor.go +++ b/internal/sbi/processor/processor.go @@ -5,11 +5,9 @@ import ( "github.com/free5gc/nssf/pkg/factory" ) -// TODO: Resolve the circular dependency between internal/sbi/processor/processor.go and internal/sbi/nssf.go type Nssf interface { Config() *factory.Config Context() *nssf_context.NSSFContext - Processor() *Processor } type Processor struct { diff --git a/internal/sbi/server/router.go b/internal/sbi/router.go similarity index 98% rename from internal/sbi/server/router.go rename to internal/sbi/router.go index b00efab..0e6288c 100644 --- a/internal/sbi/server/router.go +++ b/internal/sbi/router.go @@ -1,4 +1,4 @@ -package server +package sbi import ( "github.com/gin-gonic/gin" diff --git a/internal/sbi/server/server.go b/internal/sbi/server.go similarity index 82% rename from internal/sbi/server/server.go rename to internal/sbi/server.go index 0085541..76b88b2 100644 --- a/internal/sbi/server/server.go +++ b/internal/sbi/server.go @@ -1,4 +1,4 @@ -package server +package sbi import ( "context" @@ -9,22 +9,32 @@ import ( "github.com/gin-gonic/gin" + nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi" + "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" ) +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + type Server struct { - sbi.Nssf + Nssf httpServer *http.Server router *gin.Engine + processor *processor.Processor } -func NewServer(nssf sbi.Nssf, tlsKeyLogPath string) *Server { - s := &Server{Nssf: nssf} +func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { + s := &Server{ + Nssf: nssf, + processor: processor.NewProcessor(nssf), + } s.router = newRouter(s) @@ -39,6 +49,10 @@ func NewServer(nssf sbi.Nssf, tlsKeyLogPath string) *Server { return s } +func (s *Server) Processor() *processor.Processor { + return s.processor +} + func (s *Server) Run(wg *sync.WaitGroup) { logger.SBILog.Info("Starting server...") @@ -73,7 +87,7 @@ func (s *Server) shutdownHttpServer() { } } -func bindRouter(nssf sbi.Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { +func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { sbiConfig := nssf.Config().Configuration.Sbi bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) diff --git a/pkg/service/init.go b/pkg/service/init.go index 125bad3..68f94b5 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -19,21 +19,23 @@ import ( "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" - "github.com/free5gc/nssf/internal/sbi/processor" - "github.com/free5gc/nssf/internal/sbi/server" "github.com/free5gc/nssf/pkg/factory" ) +type App interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + type NssfApp struct { cfg *factory.Config nssfCtx *nssf_context.NSSFContext wg sync.WaitGroup - sbiServer *server.Server - processor *processor.Processor + sbiServer *sbi.Server } -var _ sbi.Nssf = &NssfApp{} +var _ App = &NssfApp{} func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} @@ -41,10 +43,7 @@ func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) - processor := processor.NewProcessor(nssf) - nssf.processor = processor - - sbiServer := server.NewServer(nssf, tlsKeyLogPath) + sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) nssf.sbiServer = sbiServer nssf_context.Init() @@ -60,10 +59,6 @@ func (a *NssfApp) Context() *nssf_context.NSSFContext { return a.nssfCtx } -func (a *NssfApp) Processor() *processor.Processor { - return a.processor -} - func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { From 6f939e0e3e6edca68d95a36985df4293fc09a981 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 19:09:37 +0800 Subject: [PATCH 06/32] test: nssaiavailability `NfInstanceDelete` --- go.mod | 1 + .../nssaiavailability_store_test.go | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 internal/sbi/nssaiavailability/nssaiavailability_store_test.go diff --git a/go.mod b/go.mod index 6623e22..987237a 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/free5gc/openapi v1.0.8 github.com/free5gc/util v1.0.6 github.com/gin-gonic/gin v1.9.1 + github.com/go-playground/assert/v2 v2.2.0 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 diff --git a/internal/sbi/nssaiavailability/nssaiavailability_store_test.go b/internal/sbi/nssaiavailability/nssaiavailability_store_test.go new file mode 100644 index 0000000..f007f3f --- /dev/null +++ b/internal/sbi/nssaiavailability/nssaiavailability_store_test.go @@ -0,0 +1,73 @@ +package nssaiavailability + +import ( + "fmt" + "net/http" + "testing" + + "github.com/free5gc/nssf/internal/util" + "github.com/free5gc/nssf/pkg/factory" +) + +func setup() { + // Set the default values for the factory.NssfConfig + factory.NssfConfig = &factory.Config{ + Configuration: &factory.Configuration{}, + } +} + +func TestMain(m *testing.M) { + // Run the tests + setup() + m.Run() +} + +func TestNfInstanceDelete(t *testing.T) { + // Create a sample AMF list + amfList := []factory.AmfConfig{ + { + NfId: "nf1", + }, + { + NfId: "nf2", + }, + { + NfId: "nf3", + }, + } + + // Set the sample AMF list in the factory.NssfConfig.Configuration + factory.NssfConfig.Configuration.AmfList = amfList + + // Test case 1: Delete an existing NF instance + nfIdToDelete := "nf2" + problemDetails := NfInstanceDelete(nfIdToDelete) + if problemDetails != nil { + t.Errorf("Expected problemDetails to be nil, got: %v", problemDetails) + } + + // Verify that the NF instance is deleted from the AMF list + for _, amfConfig := range factory.NssfConfig.Configuration.AmfList { + if amfConfig.NfId == nfIdToDelete { + t.Errorf("Expected NF instance '%s' to be deleted, but it still exists", nfIdToDelete) + } + } + + // Test case 2: Delete a non-existing NF instance + nfIdToDelete = "nf4" + expectedDetail := fmt.Sprintf("AMF ID '%s' does not exist", nfIdToDelete) + problemDetails = NfInstanceDelete(nfIdToDelete) + if problemDetails == nil { + t.Errorf("Expected problemDetails to be non-nil") + } else { + if problemDetails.Title != util.UNSUPPORTED_RESOURCE { + t.Errorf("Expected problemDetails.Title to be '%s', got: '%s'", util.UNSUPPORTED_RESOURCE, problemDetails.Title) + } + if problemDetails.Status != http.StatusNotFound { + t.Errorf("Expected problemDetails.Status to be %d, got: %d", http.StatusNotFound, problemDetails.Status) + } + if problemDetails.Detail != expectedDetail { + t.Errorf("Expected problemDetails.Detail to be '%s', got: '%s'", expectedDetail, problemDetails.Detail) + } + } +} From e9e39ad7d56ac98f5933a8a59911a6aeaba6a278 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 25 Apr 2024 19:18:59 +0800 Subject: [PATCH 07/32] feat: graceful shutdown sbi server --- cmd/main.go | 15 ++++++++++++++- go.mod | 1 - pkg/service/init.go | 40 ++++++++++++++++------------------------ 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index da046d6..752fdc9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,9 +1,12 @@ package main import ( + "context" "os" + "os/signal" "path/filepath" "runtime/debug" + "syscall" "github.com/urfave/cli" @@ -62,7 +65,17 @@ func action(cliCtx *cli.Context) error { } NSSF = nssf - nssf.Start(tlsKeyLogPath) + ctx, cancel := context.WithCancel(context.Background()) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + go func() { + <-sigCh // Wait for interrupt signal to gracefully shutdown + cancel() // Notify each goroutine and wait them stopped + }() + + nssf.Start(ctx) + nssf.Wait() return nil } diff --git a/go.mod b/go.mod index 987237a..6623e22 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/free5gc/openapi v1.0.8 github.com/free5gc/util v1.0.6 github.com/gin-gonic/gin v1.9.1 - github.com/go-playground/assert/v2 v2.2.0 github.com/google/uuid v1.3.0 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 diff --git a/pkg/service/init.go b/pkg/service/init.go index 68f94b5..71d8f99 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -5,13 +5,12 @@ package service import ( + "context" "fmt" "io" "os" - "os/signal" "runtime/debug" "sync" - "syscall" "github.com/sirupsen/logrus" @@ -128,24 +127,7 @@ func (a *NssfApp) deregisterFromNrf() { } } -func (a *NssfApp) addSigTermHandler() { - signalChannel := make(chan os.Signal, 1) - signal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM) - go func() { - defer func() { - if p := recover(); p != nil { - // Print stack for panic to log. Fatalf() will let program exit. - logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) - } - }() - - <-signalChannel - a.Terminate() - os.Exit(0) - }() -} - -func (a *NssfApp) Start(tlsKeyLogPath string) { +func (a *NssfApp) Start(ctx context.Context) { logger.InitLog.Infoln("Server started") err := a.registerToNrf() @@ -162,12 +144,22 @@ func (a *NssfApp) Start(tlsKeyLogPath string) { }() a.sbiServer.Run(&a.wg) - a.addSigTermHandler() + go a.listenShutdown(ctx) } -func (nssf *NssfApp) Terminate() { +func (a *NssfApp) listenShutdown(ctx context.Context) { + <-ctx.Done() + a.Terminate() +} + +func (a *NssfApp) Terminate() { logger.InitLog.Infof("Terminating NSSF...") - nssf.deregisterFromNrf() - nssf.sbiServer.Shutdown() + a.deregisterFromNrf() + a.sbiServer.Shutdown() + a.Wait() +} + +func (a *NssfApp) Wait() { + a.wg.Wait() logger.InitLog.Infof("NSSF terminated") } From 155556cd5b26046a16c2cadfb29db3b75dca7075 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 29 Apr 2024 13:50:24 +0800 Subject: [PATCH 08/32] fix: loop dependency on nssf app interface --- cmd/main.go | 4 ++- internal/repository/runtime.go | 26 +++++++++++++++++ internal/sbi/processor/processor.go | 16 +++------- internal/sbi/server.go | 25 +++++++--------- pkg/service/init.go | 45 ++++++++++------------------- 5 files changed, 58 insertions(+), 58 deletions(-) create mode 100644 internal/repository/runtime.go diff --git a/cmd/main.go b/cmd/main.go index 752fdc9..9d4faa6 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -11,6 +11,7 @@ import ( "github.com/urfave/cli" "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/nssf/pkg/service" logger_util "github.com/free5gc/util/logger" @@ -59,7 +60,8 @@ func action(cliCtx *cli.Context) error { } factory.NssfConfig = cfg - nssf, err := service.NewApp(cfg, tlsKeyLogPath) + runtimeRepo := repository.NewRuntimeRepository(cfg) + nssf, err := service.NewApp(runtimeRepo, tlsKeyLogPath) if err != nil { return err } diff --git a/internal/repository/runtime.go b/internal/repository/runtime.go new file mode 100644 index 0000000..1d0bbc9 --- /dev/null +++ b/internal/repository/runtime.go @@ -0,0 +1,26 @@ +package repository + +import ( + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/pkg/factory" +) + +type RuntimeRepository struct { + config *factory.Config + nssfCtx *nssf_context.NSSFContext +} + +func NewRuntimeRepository(cfg *factory.Config) *RuntimeRepository { + return &RuntimeRepository{ + config: cfg, + nssfCtx: nssf_context.GetSelf(), + } +} + +func (rr RuntimeRepository) Config() *factory.Config { + return rr.config +} + +func (rr RuntimeRepository) Context() *nssf_context.NSSFContext { + return rr.nssfCtx +} diff --git a/internal/sbi/processor/processor.go b/internal/sbi/processor/processor.go index 0e2c659..2b419f1 100644 --- a/internal/sbi/processor/processor.go +++ b/internal/sbi/processor/processor.go @@ -1,21 +1,13 @@ package processor import ( - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/pkg/factory" + "github.com/free5gc/nssf/internal/repository" ) -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type Processor struct { - Nssf + *repository.RuntimeRepository } -func NewProcessor(nssf Nssf) *Processor { - return &Processor{ - Nssf: nssf, - } +func NewProcessor(runtimeRepo *repository.RuntimeRepository) *Processor { + return &Processor{RuntimeRepository: runtimeRepo} } diff --git a/internal/sbi/server.go b/internal/sbi/server.go index 76b88b2..fbae466 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -9,36 +9,31 @@ import ( "github.com/gin-gonic/gin" - nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" ) -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type Server struct { - Nssf + *repository.RuntimeRepository httpServer *http.Server router *gin.Engine processor *processor.Processor } -func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { +func NewServer(runtimeRepo *repository.RuntimeRepository, tlsKeyLogPath string) *Server { s := &Server{ - Nssf: nssf, - processor: processor.NewProcessor(nssf), + RuntimeRepository: runtimeRepo, + processor: processor.NewProcessor(runtimeRepo), } s.router = newRouter(s) - server, err := bindRouter(nssf, s.router, tlsKeyLogPath) + server, err := bindRouter(runtimeRepo.Config(), s.router, tlsKeyLogPath) s.httpServer = server if err != nil { @@ -87,8 +82,8 @@ func (s *Server) shutdownHttpServer() { } } -func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { - sbiConfig := nssf.Config().Configuration.Sbi +func bindRouter(cfg *factory.Config, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { + sbiConfig := cfg.Configuration.Sbi bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) return httpwrapper.NewHttp2Server(bindAddr, tlsKeyLogPath, router) @@ -113,7 +108,7 @@ func (s *Server) unsecureServe() error { } func (s *Server) secureServe() error { - sbiConfig := s.Nssf.Config().Configuration.Sbi + sbiConfig := s.Config().Configuration.Sbi pemPath := sbiConfig.Tls.Pem if pemPath == "" { @@ -129,7 +124,7 @@ func (s *Server) secureServe() error { } func (s *Server) serve() error { - sbiConfig := s.Nssf.Config().Configuration.Sbi + sbiConfig := s.Config().Configuration.Sbi switch sbiConfig.Scheme { case "http": diff --git a/pkg/service/init.go b/pkg/service/init.go index 71d8f99..c5b4c6a 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -14,50 +14,35 @@ import ( "github.com/sirupsen/logrus" - nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" - "github.com/free5gc/nssf/pkg/factory" ) -type App interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type NssfApp struct { - cfg *factory.Config - nssfCtx *nssf_context.NSSFContext + *repository.RuntimeRepository wg sync.WaitGroup sbiServer *sbi.Server } -var _ App = &NssfApp{} +func NewApp(runtimeRepo *repository.RuntimeRepository, tlsKeyLogPath string) (*NssfApp, error) { + nssf := &NssfApp{ + RuntimeRepository: runtimeRepo, + wg: sync.WaitGroup{}, + } -func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { - nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} - nssf.SetLogEnable(cfg.GetLogEnable()) - nssf.SetLogLevel(cfg.GetLogLevel()) - nssf.SetReportCaller(cfg.GetLogReportCaller()) + nssf.SetLogEnable(runtimeRepo.Config().GetLogEnable()) + nssf.SetLogLevel(runtimeRepo.Config().GetLogLevel()) + nssf.SetReportCaller(runtimeRepo.Config().GetLogReportCaller()) - sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) + sbiServer := sbi.NewServer(runtimeRepo, tlsKeyLogPath) nssf.sbiServer = sbiServer - nssf_context.Init() - nssf.nssfCtx = nssf_context.GetSelf() return nssf, nil } -func (a *NssfApp) Config() *factory.Config { - return a.cfg -} - -func (a *NssfApp) Context() *nssf_context.NSSFContext { - return a.nssfCtx -} - func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { @@ -66,7 +51,7 @@ func (a *NssfApp) SetLogEnable(enable bool) { return } - a.cfg.SetLogEnable(enable) + a.Config().SetLogEnable(enable) if enable { logger.Log.SetOutput(os.Stderr) } else { @@ -86,7 +71,7 @@ func (a *NssfApp) SetLogLevel(level string) { return } - a.cfg.SetLogLevel(level) + a.Config().SetLogLevel(level) logger.Log.SetLevel(lvl) } @@ -96,12 +81,12 @@ func (a *NssfApp) SetReportCaller(reportCaller bool) { return } - a.cfg.SetLogReportCaller(reportCaller) + a.Config().SetLogReportCaller(reportCaller) logger.Log.SetReportCaller(reportCaller) } func (a *NssfApp) registerToNrf() error { - nssfContext := a.nssfCtx + nssfContext := a.Context() profile, err := consumer.BuildNFProfile(nssfContext) if err != nil { From fdee497273d242779b3e8ef580a83d15132cc3da Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 29 Apr 2024 13:57:50 +0800 Subject: [PATCH 09/32] fix: missing oauth check --- internal/sbi/server.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/internal/sbi/server.go b/internal/sbi/server.go index fbae466..11df077 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -12,7 +12,9 @@ import ( "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/internal/sbi/processor" + "github.com/free5gc/nssf/internal/util" "github.com/free5gc/nssf/pkg/factory" + "github.com/free5gc/openapi/models" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" ) @@ -93,10 +95,18 @@ func newRouter(s *Server) *gin.Engine { router := logger_util.NewGinWithLogrus(logger.GinLog) nssaiAvailabilityGroup := router.Group(factory.NssfNssaiavailResUriPrefix) + nssaiAvailabilityAuthCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSAIAVAILABILITY) + nssaiAvailabilityGroup.Use(func(c *gin.Context) { + nssaiAvailabilityAuthCheck.Check(c, s.Context()) + }) nssaiAvailabilityRoutes := s.getNssaiAvailabilityRoutes() AddService(nssaiAvailabilityGroup, nssaiAvailabilityRoutes) nsSelectionGroup := router.Group(factory.NssfNsselectResUriPrefix) + nsSelectionAuthCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSELECTION) + nsSelectionGroup.Use(func(c *gin.Context) { + nsSelectionAuthCheck.Check(c, s.Context()) + }) nsSelectionRoutes := s.getNsSelectionRoutes() AddService(nsSelectionGroup, nsSelectionRoutes) From 5838a20c70cf03b18bf15f332537d86d366fd0ba Mon Sep 17 00:00:00 2001 From: "CTFang@WireLab" Date: Mon, 29 Apr 2024 07:07:58 +0000 Subject: [PATCH 10/32] fix: Init NSSF context --- cmd/main.go | 2 ++ internal/context/context.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmd/main.go b/cmd/main.go index 9d4faa6..5f445a3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,6 +10,7 @@ import ( "github.com/urfave/cli" + nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/pkg/factory" @@ -61,6 +62,7 @@ func action(cliCtx *cli.Context) error { factory.NssfConfig = cfg runtimeRepo := repository.NewRuntimeRepository(cfg) + nssf_context.InitNssfContext() nssf, err := service.NewApp(runtimeRepo, tlsKeyLogPath) if err != nil { return err diff --git a/internal/context/context.go b/internal/context/context.go index 757e07e..a1a6004 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -66,6 +66,8 @@ type NSSFContext struct { // Initialize NSSF context with configuration factory func InitNssfContext() { + Init() + nssfConfig := factory.NssfConfig if nssfConfig.Configuration.NssfName != "" { nssfContext.Name = nssfConfig.Configuration.NssfName From cfe9ecd7aed97841765a250a766cdec980b91c01 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 29 Apr 2024 15:41:09 +0800 Subject: [PATCH 11/32] chore: change logger to align with the right context --- pkg/service/init.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/service/init.go b/pkg/service/init.go index c5b4c6a..d6b017d 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -113,11 +113,11 @@ func (a *NssfApp) deregisterFromNrf() { } func (a *NssfApp) Start(ctx context.Context) { - logger.InitLog.Infoln("Server started") - err := a.registerToNrf() if err != nil { - logger.InitLog.Errorf("Register to NRF failed: %+v", err) + logger.MainLog.Errorf("Register to NRF failed: %+v", err) + } else { + logger.MainLog.Infoln("Register to NRF successfully") } // Graceful deregister when panic @@ -138,7 +138,7 @@ func (a *NssfApp) listenShutdown(ctx context.Context) { } func (a *NssfApp) Terminate() { - logger.InitLog.Infof("Terminating NSSF...") + logger.MainLog.Infof("Terminating NSSF...") a.deregisterFromNrf() a.sbiServer.Shutdown() a.Wait() @@ -146,5 +146,5 @@ func (a *NssfApp) Terminate() { func (a *NssfApp) Wait() { a.wg.Wait() - logger.InitLog.Infof("NSSF terminated") + logger.MainLog.Infof("NSSF terminated") } From e6953ebc54a81e3dbd5a142106327b7b586ff6f3 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 29 Apr 2024 15:48:55 +0800 Subject: [PATCH 12/32] refactor: add services only when they are configured --- internal/sbi/server.go | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/internal/sbi/server.go b/internal/sbi/server.go index 11df077..cab5a68 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -94,21 +94,30 @@ func bindRouter(cfg *factory.Config, router *gin.Engine, tlsKeyLogPath string) ( func newRouter(s *Server) *gin.Engine { router := logger_util.NewGinWithLogrus(logger.GinLog) - nssaiAvailabilityGroup := router.Group(factory.NssfNssaiavailResUriPrefix) - nssaiAvailabilityAuthCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSAIAVAILABILITY) - nssaiAvailabilityGroup.Use(func(c *gin.Context) { - nssaiAvailabilityAuthCheck.Check(c, s.Context()) - }) - nssaiAvailabilityRoutes := s.getNssaiAvailabilityRoutes() - AddService(nssaiAvailabilityGroup, nssaiAvailabilityRoutes) - - nsSelectionGroup := router.Group(factory.NssfNsselectResUriPrefix) - nsSelectionAuthCheck := util.NewRouterAuthorizationCheck(models.ServiceName_NNSSF_NSSELECTION) - nsSelectionGroup.Use(func(c *gin.Context) { - nsSelectionAuthCheck.Check(c, s.Context()) - }) - nsSelectionRoutes := s.getNsSelectionRoutes() - AddService(nsSelectionGroup, nsSelectionRoutes) + for _, serviceName := range s.Config().Configuration.ServiceNameList { + switch serviceName { + case models.ServiceName_NNSSF_NSSAIAVAILABILITY: + nssaiAvailabilityGroup := router.Group(factory.NssfNssaiavailResUriPrefix) + nssaiAvailabilityGroup.Use(func(c *gin.Context) { + // oauth middleware + util.NewRouterAuthorizationCheck(serviceName).Check(c, s.Context()) + }) + nssaiAvailabilityRoutes := s.getNssaiAvailabilityRoutes() + AddService(nssaiAvailabilityGroup, nssaiAvailabilityRoutes) + + case models.ServiceName_NNSSF_NSSELECTION: + nsSelectionGroup := router.Group(factory.NssfNsselectResUriPrefix) + nsSelectionGroup.Use(func(c *gin.Context) { + // oauth middleware + util.NewRouterAuthorizationCheck(serviceName).Check(c, s.Context()) + }) + nsSelectionRoutes := s.getNsSelectionRoutes() + AddService(nsSelectionGroup, nsSelectionRoutes) + + default: + logger.SBILog.Warnf("Unsupported service name: %s", serviceName) + } + } return router } From 25fe2c1aca52e0d0b4460a30a74a89ce4044dda5 Mon Sep 17 00:00:00 2001 From: newb1er Date: Tue, 30 Apr 2024 15:36:13 +0800 Subject: [PATCH 13/32] Revert "fix: loop dependency on nssf app interface" This reverts commit 155556cd5b26046a16c2cadfb29db3b75dca7075. --- cmd/main.go | 6 +--- internal/repository/runtime.go | 26 ----------------- internal/sbi/processor/processor.go | 16 +++++++--- internal/sbi/server.go | 25 +++++++++------- pkg/service/init.go | 45 +++++++++++++++++++---------- 5 files changed, 58 insertions(+), 60 deletions(-) delete mode 100644 internal/repository/runtime.go diff --git a/cmd/main.go b/cmd/main.go index 5f445a3..752fdc9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,9 +10,7 @@ import ( "github.com/urfave/cli" - nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/nssf/pkg/service" logger_util "github.com/free5gc/util/logger" @@ -61,9 +59,7 @@ func action(cliCtx *cli.Context) error { } factory.NssfConfig = cfg - runtimeRepo := repository.NewRuntimeRepository(cfg) - nssf_context.InitNssfContext() - nssf, err := service.NewApp(runtimeRepo, tlsKeyLogPath) + nssf, err := service.NewApp(cfg, tlsKeyLogPath) if err != nil { return err } diff --git a/internal/repository/runtime.go b/internal/repository/runtime.go deleted file mode 100644 index 1d0bbc9..0000000 --- a/internal/repository/runtime.go +++ /dev/null @@ -1,26 +0,0 @@ -package repository - -import ( - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/pkg/factory" -) - -type RuntimeRepository struct { - config *factory.Config - nssfCtx *nssf_context.NSSFContext -} - -func NewRuntimeRepository(cfg *factory.Config) *RuntimeRepository { - return &RuntimeRepository{ - config: cfg, - nssfCtx: nssf_context.GetSelf(), - } -} - -func (rr RuntimeRepository) Config() *factory.Config { - return rr.config -} - -func (rr RuntimeRepository) Context() *nssf_context.NSSFContext { - return rr.nssfCtx -} diff --git a/internal/sbi/processor/processor.go b/internal/sbi/processor/processor.go index 2b419f1..0e2c659 100644 --- a/internal/sbi/processor/processor.go +++ b/internal/sbi/processor/processor.go @@ -1,13 +1,21 @@ package processor import ( - "github.com/free5gc/nssf/internal/repository" + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/pkg/factory" ) +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + type Processor struct { - *repository.RuntimeRepository + Nssf } -func NewProcessor(runtimeRepo *repository.RuntimeRepository) *Processor { - return &Processor{RuntimeRepository: runtimeRepo} +func NewProcessor(nssf Nssf) *Processor { + return &Processor{ + Nssf: nssf, + } } diff --git a/internal/sbi/server.go b/internal/sbi/server.go index cab5a68..4fb5163 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -9,8 +9,8 @@ import ( "github.com/gin-gonic/gin" + nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/internal/util" "github.com/free5gc/nssf/pkg/factory" @@ -19,23 +19,28 @@ import ( logger_util "github.com/free5gc/util/logger" ) +type Nssf interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + type Server struct { - *repository.RuntimeRepository + Nssf httpServer *http.Server router *gin.Engine processor *processor.Processor } -func NewServer(runtimeRepo *repository.RuntimeRepository, tlsKeyLogPath string) *Server { +func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { s := &Server{ - RuntimeRepository: runtimeRepo, - processor: processor.NewProcessor(runtimeRepo), + Nssf: nssf, + processor: processor.NewProcessor(nssf), } s.router = newRouter(s) - server, err := bindRouter(runtimeRepo.Config(), s.router, tlsKeyLogPath) + server, err := bindRouter(nssf, s.router, tlsKeyLogPath) s.httpServer = server if err != nil { @@ -84,8 +89,8 @@ func (s *Server) shutdownHttpServer() { } } -func bindRouter(cfg *factory.Config, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { - sbiConfig := cfg.Configuration.Sbi +func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { + sbiConfig := nssf.Config().Configuration.Sbi bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) return httpwrapper.NewHttp2Server(bindAddr, tlsKeyLogPath, router) @@ -127,7 +132,7 @@ func (s *Server) unsecureServe() error { } func (s *Server) secureServe() error { - sbiConfig := s.Config().Configuration.Sbi + sbiConfig := s.Nssf.Config().Configuration.Sbi pemPath := sbiConfig.Tls.Pem if pemPath == "" { @@ -143,7 +148,7 @@ func (s *Server) secureServe() error { } func (s *Server) serve() error { - sbiConfig := s.Config().Configuration.Sbi + sbiConfig := s.Nssf.Config().Configuration.Sbi switch sbiConfig.Scheme { case "http": diff --git a/pkg/service/init.go b/pkg/service/init.go index d6b017d..de4e517 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -14,35 +14,50 @@ import ( "github.com/sirupsen/logrus" + nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/repository" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" + "github.com/free5gc/nssf/pkg/factory" ) +type App interface { + Config() *factory.Config + Context() *nssf_context.NSSFContext +} + type NssfApp struct { - *repository.RuntimeRepository + cfg *factory.Config + nssfCtx *nssf_context.NSSFContext wg sync.WaitGroup sbiServer *sbi.Server } -func NewApp(runtimeRepo *repository.RuntimeRepository, tlsKeyLogPath string) (*NssfApp, error) { - nssf := &NssfApp{ - RuntimeRepository: runtimeRepo, - wg: sync.WaitGroup{}, - } +var _ App = &NssfApp{} - nssf.SetLogEnable(runtimeRepo.Config().GetLogEnable()) - nssf.SetLogLevel(runtimeRepo.Config().GetLogLevel()) - nssf.SetReportCaller(runtimeRepo.Config().GetLogReportCaller()) +func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { + nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} + nssf.SetLogEnable(cfg.GetLogEnable()) + nssf.SetLogLevel(cfg.GetLogLevel()) + nssf.SetReportCaller(cfg.GetLogReportCaller()) - sbiServer := sbi.NewServer(runtimeRepo, tlsKeyLogPath) + sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) nssf.sbiServer = sbiServer + nssf_context.Init() + nssf.nssfCtx = nssf_context.GetSelf() return nssf, nil } +func (a *NssfApp) Config() *factory.Config { + return a.cfg +} + +func (a *NssfApp) Context() *nssf_context.NSSFContext { + return a.nssfCtx +} + func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { @@ -51,7 +66,7 @@ func (a *NssfApp) SetLogEnable(enable bool) { return } - a.Config().SetLogEnable(enable) + a.cfg.SetLogEnable(enable) if enable { logger.Log.SetOutput(os.Stderr) } else { @@ -71,7 +86,7 @@ func (a *NssfApp) SetLogLevel(level string) { return } - a.Config().SetLogLevel(level) + a.cfg.SetLogLevel(level) logger.Log.SetLevel(lvl) } @@ -81,12 +96,12 @@ func (a *NssfApp) SetReportCaller(reportCaller bool) { return } - a.Config().SetLogReportCaller(reportCaller) + a.cfg.SetLogReportCaller(reportCaller) logger.Log.SetReportCaller(reportCaller) } func (a *NssfApp) registerToNrf() error { - nssfContext := a.Context() + nssfContext := a.nssfCtx profile, err := consumer.BuildNFProfile(nssfContext) if err != nil { From db71e0575b0ea6919919719c937412f3e7603fa3 Mon Sep 17 00:00:00 2001 From: newb1er Date: Wed, 8 May 2024 14:29:22 +0800 Subject: [PATCH 14/32] refactor: general app interface --- cmd/main.go | 14 +------------- internal/sbi/processor/processor.go | 14 ++++---------- internal/sbi/server.go | 19 +++++++------------ pkg/app/app.go | 18 ++++++++++++++++++ pkg/service/init.go | 29 +++++++++++++++++++---------- 5 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 pkg/app/app.go diff --git a/cmd/main.go b/cmd/main.go index 752fdc9..22de773 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,12 +1,9 @@ package main import ( - "context" "os" - "os/signal" "path/filepath" "runtime/debug" - "syscall" "github.com/urfave/cli" @@ -65,16 +62,7 @@ func action(cliCtx *cli.Context) error { } NSSF = nssf - ctx, cancel := context.WithCancel(context.Background()) - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) - - go func() { - <-sigCh // Wait for interrupt signal to gracefully shutdown - cancel() // Notify each goroutine and wait them stopped - }() - - nssf.Start(ctx) + nssf.Start() nssf.Wait() return nil diff --git a/internal/sbi/processor/processor.go b/internal/sbi/processor/processor.go index 0e2c659..00f435c 100644 --- a/internal/sbi/processor/processor.go +++ b/internal/sbi/processor/processor.go @@ -1,21 +1,15 @@ package processor import ( - nssf_context "github.com/free5gc/nssf/internal/context" - "github.com/free5gc/nssf/pkg/factory" + "github.com/free5gc/nssf/pkg/app" ) -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type Processor struct { - Nssf + app.NssfApp } -func NewProcessor(nssf Nssf) *Processor { +func NewProcessor(nssf app.NssfApp) *Processor { return &Processor{ - Nssf: nssf, + NssfApp: nssf, } } diff --git a/internal/sbi/server.go b/internal/sbi/server.go index 4fb5163..21cf2f1 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -9,32 +9,27 @@ import ( "github.com/gin-gonic/gin" - nssf_context "github.com/free5gc/nssf/internal/context" "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/internal/util" + "github.com/free5gc/nssf/pkg/app" "github.com/free5gc/nssf/pkg/factory" "github.com/free5gc/openapi/models" "github.com/free5gc/util/httpwrapper" logger_util "github.com/free5gc/util/logger" ) -type Nssf interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type Server struct { - Nssf + app.NssfApp httpServer *http.Server router *gin.Engine processor *processor.Processor } -func NewServer(nssf Nssf, tlsKeyLogPath string) *Server { +func NewServer(nssf app.NssfApp, tlsKeyLogPath string) *Server { s := &Server{ - Nssf: nssf, + NssfApp: nssf, processor: processor.NewProcessor(nssf), } @@ -89,7 +84,7 @@ func (s *Server) shutdownHttpServer() { } } -func bindRouter(nssf Nssf, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { +func bindRouter(nssf app.NssfApp, router *gin.Engine, tlsKeyLogPath string) (*http.Server, error) { sbiConfig := nssf.Config().Configuration.Sbi bindAddr := fmt.Sprintf("%s:%d", sbiConfig.BindingIPv4, sbiConfig.Port) @@ -132,7 +127,7 @@ func (s *Server) unsecureServe() error { } func (s *Server) secureServe() error { - sbiConfig := s.Nssf.Config().Configuration.Sbi + sbiConfig := s.Config().Configuration.Sbi pemPath := sbiConfig.Tls.Pem if pemPath == "" { @@ -148,7 +143,7 @@ func (s *Server) secureServe() error { } func (s *Server) serve() error { - sbiConfig := s.Nssf.Config().Configuration.Sbi + sbiConfig := s.Config().Configuration.Sbi switch sbiConfig.Scheme { case "http": diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 0000000..4bf56f5 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,18 @@ +package app + +import ( + nssf_context "github.com/free5gc/nssf/internal/context" + "github.com/free5gc/nssf/pkg/factory" +) + +type NssfApp interface { + SetLogEnable(enable bool) + SetLogLevel(level string) + SetReportCaller(reportCaller bool) + + Start() error + Terminate() + + Context() *nssf_context.NSSFContext + Config() *factory.Config +} diff --git a/pkg/service/init.go b/pkg/service/init.go index de4e517..c3d4bb2 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -9,8 +9,10 @@ import ( "fmt" "io" "os" + "os/signal" "runtime/debug" "sync" + "syscall" "github.com/sirupsen/logrus" @@ -18,14 +20,10 @@ import ( "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" + "github.com/free5gc/nssf/pkg/app" "github.com/free5gc/nssf/pkg/factory" ) -type App interface { - Config() *factory.Config - Context() *nssf_context.NSSFContext -} - type NssfApp struct { cfg *factory.Config nssfCtx *nssf_context.NSSFContext @@ -34,7 +32,7 @@ type NssfApp struct { sbiServer *sbi.Server } -var _ App = &NssfApp{} +var _ app.NssfApp = &NssfApp{} func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} @@ -127,24 +125,35 @@ func (a *NssfApp) deregisterFromNrf() { } } -func (a *NssfApp) Start(ctx context.Context) { +func (a *NssfApp) Start() error { + ctx, cancel := context.WithCancel(context.Background()) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + go func() { + <-sigCh // Wait for interrupt signal to gracefully shutdown + cancel() // Notify each goroutine and wait them stopped + }() + err := a.registerToNrf() if err != nil { - logger.MainLog.Errorf("Register to NRF failed: %+v", err) + return fmt.Errorf("register to NRF failed: %+v", err) } else { - logger.MainLog.Infoln("Register to NRF successfully") + logger.MainLog.Infoln("register to NRF successfully") } // Graceful deregister when panic defer func() { if p := recover(); p != nil { - logger.InitLog.Errorf("panic: %v\n%s", p, string(debug.Stack())) a.deregisterFromNrf() + logger.InitLog.Fatalf("panic: %v\n%s", p, string(debug.Stack())) } }() a.sbiServer.Run(&a.wg) go a.listenShutdown(ctx) + + return nil } func (a *NssfApp) listenShutdown(ctx context.Context) { From 01f38b7d85c4e854ce1c5ab0d355536e999aa821 Mon Sep 17 00:00:00 2001 From: newb1er Date: Wed, 8 May 2024 15:19:55 +0800 Subject: [PATCH 15/32] refactor: app statr interface --- pkg/app/app.go | 2 +- pkg/service/init.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 4bf56f5..6e47aba 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -10,7 +10,7 @@ type NssfApp interface { SetLogLevel(level string) SetReportCaller(reportCaller bool) - Start() error + Start() Terminate() Context() *nssf_context.NSSFContext diff --git a/pkg/service/init.go b/pkg/service/init.go index c3d4bb2..454d4fe 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -125,7 +125,7 @@ func (a *NssfApp) deregisterFromNrf() { } } -func (a *NssfApp) Start() error { +func (a *NssfApp) Start() { ctx, cancel := context.WithCancel(context.Background()) sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) @@ -137,7 +137,7 @@ func (a *NssfApp) Start() error { err := a.registerToNrf() if err != nil { - return fmt.Errorf("register to NRF failed: %+v", err) + logger.MainLog.Errorf("register to NRF failed: %+v", err) } else { logger.MainLog.Infoln("register to NRF successfully") } @@ -152,8 +152,6 @@ func (a *NssfApp) Start() error { a.sbiServer.Run(&a.wg) go a.listenShutdown(ctx) - - return nil } func (a *NssfApp) listenShutdown(ctx context.Context) { From 6d27b3aca489f58a0cb3dd082ed044db74915a2f Mon Sep 17 00:00:00 2001 From: newb1er Date: Wed, 8 May 2024 15:47:52 +0800 Subject: [PATCH 16/32] refactor: consumer --- internal/sbi/consumer/consumer.go | 25 +++++++++++++++++ .../{nf_management.go => nrf_service.go} | 27 ++++++++++--------- pkg/service/init.go | 27 ++++++++++++++----- 3 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 internal/sbi/consumer/consumer.go rename internal/sbi/consumer/{nf_management.go => nrf_service.go} (82%) diff --git a/internal/sbi/consumer/consumer.go b/internal/sbi/consumer/consumer.go new file mode 100644 index 0000000..780cf24 --- /dev/null +++ b/internal/sbi/consumer/consumer.go @@ -0,0 +1,25 @@ +package consumer + +import ( + "github.com/free5gc/nssf/pkg/app" + "github.com/free5gc/openapi/Nnrf_NFManagement" +) + +type Consumer struct { + app.NssfApp + + *NrfService +} + +func NewConsumer(nssf app.NssfApp) *Consumer { + configuration := Nnrf_NFManagement.NewConfiguration() + configuration.SetBasePath(nssf.Context().NrfUri) + nrfService := &NrfService{ + nrfNfMgmtClient: Nnrf_NFManagement.NewAPIClient(configuration), + } + + return &Consumer{ + NssfApp: nssf, + NrfService: nrfService, + } +} diff --git a/internal/sbi/consumer/nf_management.go b/internal/sbi/consumer/nrf_service.go similarity index 82% rename from internal/sbi/consumer/nf_management.go rename to internal/sbi/consumer/nrf_service.go index f6d4bdf..2593807 100644 --- a/internal/sbi/consumer/nf_management.go +++ b/internal/sbi/consumer/nrf_service.go @@ -20,7 +20,11 @@ import ( "github.com/free5gc/openapi/models" ) -func BuildNFProfile(context *nssf_context.NSSFContext) (profile models.NfProfile, err error) { +type NrfService struct { + nrfNfMgmtClient *Nnrf_NFManagement.APIClient +} + +func (ns *NrfService) buildNFProfile(context *nssf_context.NSSFContext) (profile models.NfProfile, err error) { profile.NfInstanceId = context.NfId profile.NfType = models.NfType_NSSF profile.NfStatus = models.NfStatus_REGISTERED @@ -36,12 +40,15 @@ func BuildNFProfile(context *nssf_context.NSSFContext) (profile models.NfProfile return } -func SendRegisterNFInstance(nrfUri, nfInstanceId string, profile models.NfProfile) ( +func (ns *NrfService) SendRegisterNFInstance(nssfCtx *nssf_context.NSSFContext) ( resourceNrfUri string, retrieveNfInstanceId string, err error, ) { - configuration := Nnrf_NFManagement.NewConfiguration() - configuration.SetBasePath(nrfUri) - apiClient := Nnrf_NFManagement.NewAPIClient(configuration) + nfInstanceId := nssfCtx.NfId + profile, err := ns.buildNFProfile(nssfCtx) + if err != nil { + return "", "", fmt.Errorf("failed to build nrf profile: %s", err.Error()) + } + apiClient := ns.nrfNfMgmtClient var res *http.Response var nf models.NfProfile @@ -88,7 +95,7 @@ func SendRegisterNFInstance(nrfUri, nfInstanceId string, profile models.NfProfil return resourceNrfUri, retrieveNfInstanceId, err } -func SendDeregisterNFInstance() (*models.ProblemDetails, error) { +func (ns *NrfService) SendDeregisterNFInstance(nfInstanceId string) (*models.ProblemDetails, error) { logger.ConsumerLog.Infof("Send Deregister NFInstance") var err error @@ -98,15 +105,11 @@ func SendDeregisterNFInstance() (*models.ProblemDetails, error) { return pd, err } - nssfSelf := nssf_context.GetSelf() - // Set client and set url - configuration := Nnrf_NFManagement.NewConfiguration() - configuration.SetBasePath(nssfSelf.NrfUri) - client := Nnrf_NFManagement.NewAPIClient(configuration) + client := ns.nrfNfMgmtClient var res *http.Response - res, err = client.NFInstanceIDDocumentApi.DeregisterNFInstance(ctx, nssfSelf.NfId) + res, err = client.NFInstanceIDDocumentApi.DeregisterNFInstance(ctx, nfInstanceId) if err == nil { return nil, err } else if res != nil { diff --git a/pkg/service/init.go b/pkg/service/init.go index 454d4fe..7b3ad04 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -20,6 +20,7 @@ import ( "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/sbi" "github.com/free5gc/nssf/internal/sbi/consumer" + "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/pkg/app" "github.com/free5gc/nssf/pkg/factory" ) @@ -30,6 +31,8 @@ type NssfApp struct { wg sync.WaitGroup sbiServer *sbi.Server + processor *processor.Processor + consumer *consumer.Consumer } var _ app.NssfApp = &NssfApp{} @@ -40,6 +43,12 @@ func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) + processor := processor.NewProcessor(nssf) + nssf.processor = processor + + consumer := consumer.NewConsumer(nssf) + nssf.consumer = consumer + sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) nssf.sbiServer = sbiServer @@ -56,6 +65,14 @@ func (a *NssfApp) Context() *nssf_context.NSSFContext { return a.nssfCtx } +func (a *NssfApp) Processor() *processor.Processor { + return a.processor +} + +func (a *NssfApp) Consumer() *consumer.Consumer { + return a.consumer +} + func (a *NssfApp) SetLogEnable(enable bool) { logger.MainLog.Infof("Log enable is set to [%v]", enable) if enable && logger.Log.Out == os.Stderr { @@ -101,12 +118,8 @@ func (a *NssfApp) SetReportCaller(reportCaller bool) { func (a *NssfApp) registerToNrf() error { nssfContext := a.nssfCtx - profile, err := consumer.BuildNFProfile(nssfContext) - if err != nil { - return fmt.Errorf("failed to build NSSF profile") - } - - _, nssfContext.NfId, err = consumer.SendRegisterNFInstance(nssfContext.NrfUri, nssfContext.NfId, profile) + var err error + _, nssfContext.NfId, err = a.consumer.SendRegisterNFInstance(nssfContext) if err != nil { return fmt.Errorf("failed to register NSSF to NRF: %s", err.Error()) } @@ -115,7 +128,7 @@ func (a *NssfApp) registerToNrf() error { } func (a *NssfApp) deregisterFromNrf() { - problemDetails, err := consumer.SendDeregisterNFInstance() + problemDetails, err := a.consumer.SendDeregisterNFInstance(a.nssfCtx.NfId) if problemDetails != nil { logger.InitLog.Errorf("Deregister NF instance Failed Problem[%+v]", problemDetails) } else if err != nil { From 8a74e48b6aced1d717d47abad37099ab4bbc0ab4 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 9 May 2024 19:27:51 +0800 Subject: [PATCH 17/32] fix: should not access nssf context before assignment --- pkg/service/init.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/service/init.go b/pkg/service/init.go index 7b3ad04..c405049 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -38,7 +38,9 @@ type NssfApp struct { var _ app.NssfApp = &NssfApp{} func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { - nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}} + nssf_context.Init() + + nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}, nssfCtx: nssf_context.GetSelf()} nssf.SetLogEnable(cfg.GetLogEnable()) nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) @@ -52,8 +54,6 @@ func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { sbiServer := sbi.NewServer(nssf, tlsKeyLogPath) nssf.sbiServer = sbiServer - nssf_context.Init() - nssf.nssfCtx = nssf_context.GetSelf() return nssf, nil } From 4488ffbe18b0b5c608ca1016558c55e5eb9e4f13 Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 9 May 2024 19:45:19 +0800 Subject: [PATCH 18/32] feat: register procedure graceful shutdown --- internal/sbi/consumer/nrf_service.go | 77 +++++++++++++++------------- pkg/service/init.go | 6 +-- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/internal/sbi/consumer/nrf_service.go b/internal/sbi/consumer/nrf_service.go index 2593807..58a96f4 100644 --- a/internal/sbi/consumer/nrf_service.go +++ b/internal/sbi/consumer/nrf_service.go @@ -40,7 +40,7 @@ func (ns *NrfService) buildNFProfile(context *nssf_context.NSSFContext) (profile return } -func (ns *NrfService) SendRegisterNFInstance(nssfCtx *nssf_context.NSSFContext) ( +func (ns *NrfService) SendRegisterNFInstance(ctx context.Context, nssfCtx *nssf_context.NSSFContext) ( resourceNrfUri string, retrieveNfInstanceId string, err error, ) { nfInstanceId := nssfCtx.NfId @@ -52,44 +52,51 @@ func (ns *NrfService) SendRegisterNFInstance(nssfCtx *nssf_context.NSSFContext) var res *http.Response var nf models.NfProfile - for { - nf, res, err = apiClient.NFInstanceIDDocumentApi.RegisterNFInstance(context.TODO(), nfInstanceId, profile) - if err != nil || res == nil { - // TODO : add log - logger.ConsumerLog.Errorf("NSSF register to NRF Error[%s]", err.Error()) - time.Sleep(2 * time.Second) - continue - } - defer func() { - if resCloseErr := res.Body.Close(); resCloseErr != nil { - logger.ConsumerLog.Errorf("NFInstanceIDDocumentApi response body cannot close: %+v", resCloseErr) + finish := false + for !finish { + select { + case <-ctx.Done(): + return "", "", fmt.Errorf("context done") + + default: + nf, res, err = apiClient.NFInstanceIDDocumentApi.RegisterNFInstance(ctx, nfInstanceId, profile) + if err != nil || res == nil { + // TODO : add log + logger.ConsumerLog.Errorf("NSSF register to NRF Error[%s]", err.Error()) + time.Sleep(2 * time.Second) + continue } - }() - status := res.StatusCode - if status == http.StatusOK { - // NFUpdate - break - } else if status == http.StatusCreated { - // NFRegister - resourceUri := res.Header.Get("Location") - resourceNrfUri = resourceUri[:strings.Index(resourceUri, "/nnrf-nfm/")] - retrieveNfInstanceId = resourceUri[strings.LastIndex(resourceUri, "/")+1:] + defer func() { + if resCloseErr := res.Body.Close(); resCloseErr != nil { + logger.ConsumerLog.Errorf("NFInstanceIDDocumentApi response body cannot close: %+v", resCloseErr) + } + }() + status := res.StatusCode + if status == http.StatusOK { + // NFUpdate + finish = true + } else if status == http.StatusCreated { + // NFRegister + resourceUri := res.Header.Get("Location") + resourceNrfUri = resourceUri[:strings.Index(resourceUri, "/nnrf-nfm/")] + retrieveNfInstanceId = resourceUri[strings.LastIndex(resourceUri, "/")+1:] - oauth2 := false - if nf.CustomInfo != nil { - v, ok := nf.CustomInfo["oauth2"].(bool) - if ok { - oauth2 = v - logger.MainLog.Infoln("OAuth2 setting receive from NRF:", oauth2) + oauth2 := false + if nf.CustomInfo != nil { + v, ok := nf.CustomInfo["oauth2"].(bool) + if ok { + oauth2 = v + logger.MainLog.Infoln("OAuth2 setting receive from NRF:", oauth2) + } } + nssf_context.GetSelf().OAuth2Required = oauth2 + if oauth2 && nssf_context.GetSelf().NrfCertPem == "" { + logger.CfgLog.Error("OAuth2 enable but no nrfCertPem provided in config.") + } + finish = true + } else { + fmt.Println("NRF return wrong status code", status) } - nssf_context.GetSelf().OAuth2Required = oauth2 - if oauth2 && nssf_context.GetSelf().NrfCertPem == "" { - logger.CfgLog.Error("OAuth2 enable but no nrfCertPem provided in config.") - } - break - } else { - fmt.Println("NRF return wrong status code", status) } } return resourceNrfUri, retrieveNfInstanceId, err diff --git a/pkg/service/init.go b/pkg/service/init.go index c405049..452ed64 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -115,11 +115,11 @@ func (a *NssfApp) SetReportCaller(reportCaller bool) { logger.Log.SetReportCaller(reportCaller) } -func (a *NssfApp) registerToNrf() error { +func (a *NssfApp) registerToNrf(ctx context.Context) error { nssfContext := a.nssfCtx var err error - _, nssfContext.NfId, err = a.consumer.SendRegisterNFInstance(nssfContext) + _, nssfContext.NfId, err = a.consumer.SendRegisterNFInstance(ctx, nssfContext) if err != nil { return fmt.Errorf("failed to register NSSF to NRF: %s", err.Error()) } @@ -148,7 +148,7 @@ func (a *NssfApp) Start() { cancel() // Notify each goroutine and wait them stopped }() - err := a.registerToNrf() + err := a.registerToNrf(ctx) if err != nil { logger.MainLog.Errorf("register to NRF failed: %+v", err) } else { From 7f4cf20042e8dd24ca4245905973d61653d3323c Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 9 May 2024 19:51:01 +0800 Subject: [PATCH 19/32] fix: init nssf context with wrong function --- pkg/service/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/service/init.go b/pkg/service/init.go index 452ed64..7a298d4 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -38,7 +38,7 @@ type NssfApp struct { var _ app.NssfApp = &NssfApp{} func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { - nssf_context.Init() + nssf_context.InitNssfContext() nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}, nssfCtx: nssf_context.GetSelf()} nssf.SetLogEnable(cfg.GetLogEnable()) From b00f843621c871ed82c0b5a7cbd698038793296c Mon Sep 17 00:00:00 2001 From: newb1er Date: Thu, 9 May 2024 20:08:34 +0800 Subject: [PATCH 20/32] refactor: align naming of route handler --- internal/sbi/api_nssaiavailability.go | 4 ++-- internal/sbi/processor/subscription_id_document.go | 2 +- internal/sbi/processor/subscriptions_collection.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/sbi/api_nssaiavailability.go b/internal/sbi/api_nssaiavailability.go index 8f817b3..2cc688b 100644 --- a/internal/sbi/api_nssaiavailability.go +++ b/internal/sbi/api_nssaiavailability.go @@ -47,14 +47,14 @@ func (s *Server) getNssaiAvailabilityRoutes() []Route { strings.ToUpper("Delete"), // "/nssai-availability/subscriptions/:subscriptionId", "/nssai-availability/:nfId/:subscriptionId", - s.Processor().HTTPNSSAIAvailabilityUnsubscribe, + s.Processor().HandleNSSAIAvailabilityUnsubscribeDelete, }, { "NSSAIAvailabilityPost", strings.ToUpper("Post"), "/nssai-availability/subscriptions", - s.Processor().HTTPNSSAIAvailabilityPost, + s.Processor().HandleNSSAIAvailabilityPost, }, } } diff --git a/internal/sbi/processor/subscription_id_document.go b/internal/sbi/processor/subscription_id_document.go index 2e8de46..cd87147 100644 --- a/internal/sbi/processor/subscription_id_document.go +++ b/internal/sbi/processor/subscription_id_document.go @@ -20,7 +20,7 @@ import ( "github.com/free5gc/util/httpwrapper" ) -func (p *Processor) HTTPNSSAIAvailabilityUnsubscribe(c *gin.Context) { +func (p *Processor) HandleNSSAIAvailabilityUnsubscribeDelete(c *gin.Context) { // Due to conflict of route matching, 'subscriptions' in the route is replaced with the existing wildcard ':nfId' nfID := c.Param("nfId") if nfID != "subscriptions" { diff --git a/internal/sbi/processor/subscriptions_collection.go b/internal/sbi/processor/subscriptions_collection.go index 978a34c..34723e8 100644 --- a/internal/sbi/processor/subscriptions_collection.go +++ b/internal/sbi/processor/subscriptions_collection.go @@ -20,7 +20,7 @@ import ( . "github.com/free5gc/openapi/models" ) -func (p *Processor) HTTPNSSAIAvailabilityPost(c *gin.Context) { +func (p *Processor) HandleNSSAIAvailabilityPost(c *gin.Context) { var createData NssfEventSubscriptionCreateData requestBody, err := c.GetRawData() From 0ed8569864cbb0b33c3256f56054cfc18113d162 Mon Sep 17 00:00:00 2001 From: newb1er Date: Fri, 10 May 2024 14:12:08 +0800 Subject: [PATCH 21/32] refactor: processor architecture that aligns with other nf repo --- go.mod | 1 + go.sum | 2 + internal/sbi/api_nssaiavailability.go | 185 ++++++++++- internal/sbi/api_nsselection.go | 11 +- .../nssaiavailability_store_test.go | 73 ----- .../network_slice_information_document.go | 162 ---------- .../nsselection_for_pdu_session.go | 135 -------- .../network_slice_information_document.go | 33 -- .../sbi/processor/nf_instance_id_document.go | 169 ---------- .../nssaiavailability_store.go | 41 ++- .../processor/nssaiavailability_store_test.go | 89 ++++++ .../nssaiavailability_subscription.go | 23 +- .../nsselection_network_slice_information.go} | 291 +++++++++++++++++- .../sbi/processor/subscription_id_document.go | 54 ---- .../sbi/processor/subscriptions_collection.go | 62 ---- internal/util/problem_json.go | 12 + internal/util/util.go | 1 + pkg/app/mock.go | 129 ++++++++ 18 files changed, 739 insertions(+), 734 deletions(-) delete mode 100644 internal/sbi/nssaiavailability/nssaiavailability_store_test.go delete mode 100644 internal/sbi/nsselection/network_slice_information_document.go delete mode 100644 internal/sbi/nsselection/nsselection_for_pdu_session.go delete mode 100644 internal/sbi/processor/network_slice_information_document.go delete mode 100644 internal/sbi/processor/nf_instance_id_document.go rename internal/sbi/{nssaiavailability => processor}/nssaiavailability_store.go (88%) create mode 100644 internal/sbi/processor/nssaiavailability_store_test.go rename internal/sbi/{nssaiavailability => processor}/nssaiavailability_subscription.go (85%) rename internal/sbi/{nsselection/nsselection_for_registration.go => processor/nsselection_network_slice_information.go} (65%) delete mode 100644 internal/sbi/processor/subscription_id_document.go delete mode 100644 internal/sbi/processor/subscriptions_collection.go create mode 100644 internal/util/problem_json.go create mode 100644 pkg/app/mock.go diff --git a/go.mod b/go.mod index 6623e22..b7e2fcb 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/urfave/cli v1.22.5 + go.uber.org/mock v0.4.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index bdefb50..1f01fc8 100644 --- a/go.sum +++ b/go.sum @@ -212,6 +212,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= diff --git a/internal/sbi/api_nssaiavailability.go b/internal/sbi/api_nssaiavailability.go index 2cc688b..a037c28 100644 --- a/internal/sbi/api_nssaiavailability.go +++ b/internal/sbi/api_nssaiavailability.go @@ -5,6 +5,12 @@ import ( "strings" "github.com/gin-gonic/gin" + + "github.com/free5gc/nssf/internal/logger" + "github.com/free5gc/nssf/internal/plugin" + "github.com/free5gc/nssf/internal/util" + "github.com/free5gc/openapi" + "github.com/free5gc/openapi/models" ) func (s *Server) getNssaiAvailabilityRoutes() []Route { @@ -22,21 +28,21 @@ func (s *Server) getNssaiAvailabilityRoutes() []Route { "NSSAIAvailabilityDelete", strings.ToUpper("Delete"), "/nssai-availability/:nfId", - s.Processor().HandleNSSAIAvailabilityDelete, + s.NSSAIAvailabilityDelete, }, { "NSSAIAvailabilityPatch", strings.ToUpper("Patch"), "/nssai-availability/:nfId", - s.Processor().HandleNSSAIAvailabilityPatch, + s.NSSAIAvailabilityPatch, }, { "NSSAIAvailabilityPut", strings.ToUpper("Put"), "/nssai-availability/:nfId", - s.Processor().HandleNSSAIAvailabilityPut, + s.NSSAIAvailabilityPut, }, // Regular expressions for route matching should be unique in Gin package @@ -47,14 +53,183 @@ func (s *Server) getNssaiAvailabilityRoutes() []Route { strings.ToUpper("Delete"), // "/nssai-availability/subscriptions/:subscriptionId", "/nssai-availability/:nfId/:subscriptionId", - s.Processor().HandleNSSAIAvailabilityUnsubscribeDelete, + s.NSSAIAvailabilityUnsubscribeDelete, }, { "NSSAIAvailabilityPost", strings.ToUpper("Post"), "/nssai-availability/subscriptions", - s.Processor().HandleNSSAIAvailabilityPost, + s.NSSAIAvailabilityPost, }, } } + +// NSSAIAvailabilityDelete - Deletes an already existing S-NSSAIs per TA +// provided by the NF service consumer (e.g AMF) +func (s *Server) NSSAIAvailabilityDelete(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityDelete") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + util.GinProblemJson(c, problemDetails) + return + } + + s.Processor().NssaiAvailabilityNfInstanceDelete(c, nfId) +} + +// NSSAIAvailabilityPatch - Updates an already existing S-NSSAIs per TA +// provided by the NF service consumer (e.g AMF) +func (s *Server) NSSAIAvailabilityPatch(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPatch") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + util.GinProblemJson(c, problemDetails) + return + } + + var patchDocument plugin.PatchDocument + + requestBody, err := c.GetRawData() + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + } + + util.GinProblemJson(c, problemDetails) + return + } + + if err = openapi.Deserialize(&patchDocument, requestBody, "application/json"); err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + logger.SBILog.Errorf("Error deserializing patch document: %+v", err) + util.GinProblemJson(c, problemDetails) + return + } + + // TODO: Request NfProfile of NfId from NRF + // Check if NfId is valid AMF and obtain AMF Set ID + // If NfId is invalid, return ProblemDetails with code 404 Not Found + // If NF consumer is not authorized to update NSSAI availability, return ProblemDetails with code 403 Forbidden + + s.Processor().NssaiAvailabilityNfInstancePatch(c, patchDocument, nfId) +} + +// NSSAIAvailabilityPut - Updates/replaces the NSSF +// with the S-NSSAIs the NF service consumer (e.g AMF) supports per TA +func (s *Server) NSSAIAvailabilityPut(c *gin.Context) { + logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPut") + + nfId := c.Params.ByName("nfId") + + if nfId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + util.GinProblemJson(c, problemDetails) + return + } + + var nssaiAvailabilityInfo models.NssaiAvailabilityInfo + data, err := c.GetRawData() + if err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusInternalServerError, + Cause: "SYSTEM_FAILURE", + } + + util.GinProblemJson(c, problemDetails) + return + } + + if err = openapi.Deserialize(&nssaiAvailabilityInfo, data, "application/json"); err != nil { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + logger.SBILog.Errorf("Error deserializing NSSAI availability info: %+v", err) + util.GinProblemJson(c, problemDetails) + return + } + + s.Processor().NssaiAvailabilityNfInstanceUpdate(c, nssaiAvailabilityInfo, nfId) +} + +func (s *Server) NSSAIAvailabilityPost(c *gin.Context) { + var createData models.NssfEventSubscriptionCreateData + + requestBody, err := c.GetRawData() + if err != nil { + problemDetail := &models.ProblemDetails{ + Title: "System failure", + Status: http.StatusInternalServerError, + Detail: err.Error(), + Cause: "SYSTEM_FAILURE", + } + logger.NssaiavailLog.Errorf("Get Request Body error: %+v", err) + + util.GinProblemJson(c, problemDetail) + return + } + + err = openapi.Deserialize(&createData, requestBody, "application/json") + if err != nil { + problemDetail := "[Request Body] " + err.Error() + rsp := &models.ProblemDetails{ + Title: "Malformed request syntax", + Status: http.StatusBadRequest, + Detail: problemDetail, + } + logger.NssaiavailLog.Errorln(problemDetail) + + util.GinProblemJson(c, rsp) + return + } + + s.Processor().NssaiAvailabilitySubscriptionCreate(c, createData) +} + +func (s *Server) NSSAIAvailabilityUnsubscribeDelete(c *gin.Context) { + // Due to conflict of route matching, 'subscriptions' in the route is replaced with the existing wildcard ':nfId' + nfID := c.Param("nfId") + if nfID != "subscriptions" { + c.JSON(http.StatusNotFound, gin.H{}) + logger.NssaiavailLog.Infof("404 Not Found") + return + } + + subscriptionId := c.Params.ByName("subscriptionId") + if subscriptionId == "" { + problemDetails := &models.ProblemDetails{ + Status: http.StatusBadRequest, + Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause + } + + util.GinProblemJson(c, problemDetails) + return + } + + s.Processor().NssaiAvailabilitySubscriptionUnsubscribe(c, subscriptionId) +} diff --git a/internal/sbi/api_nsselection.go b/internal/sbi/api_nsselection.go index 210247e..43e36c1 100644 --- a/internal/sbi/api_nsselection.go +++ b/internal/sbi/api_nsselection.go @@ -5,6 +5,8 @@ import ( "strings" "github.com/gin-gonic/gin" + + "github.com/free5gc/nssf/internal/logger" ) func (s *Server) getNsSelectionRoutes() []Route { @@ -22,7 +24,14 @@ func (s *Server) getNsSelectionRoutes() []Route { "NSSelectionGet", strings.ToUpper("Get"), "/network-slice-information", - s.Processor().HandleNetworkSliceInformationGet, + s.NetworkSliceInformationGet, }, } } + +func (s *Server) NetworkSliceInformationGet(c *gin.Context) { + logger.NsselLog.Infof("Handle NSSelectionGet") + + query := c.Request.URL.Query() + s.Processor().NSSelectionSliceInformationGet(c, query) +} diff --git a/internal/sbi/nssaiavailability/nssaiavailability_store_test.go b/internal/sbi/nssaiavailability/nssaiavailability_store_test.go deleted file mode 100644 index f007f3f..0000000 --- a/internal/sbi/nssaiavailability/nssaiavailability_store_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package nssaiavailability - -import ( - "fmt" - "net/http" - "testing" - - "github.com/free5gc/nssf/internal/util" - "github.com/free5gc/nssf/pkg/factory" -) - -func setup() { - // Set the default values for the factory.NssfConfig - factory.NssfConfig = &factory.Config{ - Configuration: &factory.Configuration{}, - } -} - -func TestMain(m *testing.M) { - // Run the tests - setup() - m.Run() -} - -func TestNfInstanceDelete(t *testing.T) { - // Create a sample AMF list - amfList := []factory.AmfConfig{ - { - NfId: "nf1", - }, - { - NfId: "nf2", - }, - { - NfId: "nf3", - }, - } - - // Set the sample AMF list in the factory.NssfConfig.Configuration - factory.NssfConfig.Configuration.AmfList = amfList - - // Test case 1: Delete an existing NF instance - nfIdToDelete := "nf2" - problemDetails := NfInstanceDelete(nfIdToDelete) - if problemDetails != nil { - t.Errorf("Expected problemDetails to be nil, got: %v", problemDetails) - } - - // Verify that the NF instance is deleted from the AMF list - for _, amfConfig := range factory.NssfConfig.Configuration.AmfList { - if amfConfig.NfId == nfIdToDelete { - t.Errorf("Expected NF instance '%s' to be deleted, but it still exists", nfIdToDelete) - } - } - - // Test case 2: Delete a non-existing NF instance - nfIdToDelete = "nf4" - expectedDetail := fmt.Sprintf("AMF ID '%s' does not exist", nfIdToDelete) - problemDetails = NfInstanceDelete(nfIdToDelete) - if problemDetails == nil { - t.Errorf("Expected problemDetails to be non-nil") - } else { - if problemDetails.Title != util.UNSUPPORTED_RESOURCE { - t.Errorf("Expected problemDetails.Title to be '%s', got: '%s'", util.UNSUPPORTED_RESOURCE, problemDetails.Title) - } - if problemDetails.Status != http.StatusNotFound { - t.Errorf("Expected problemDetails.Status to be %d, got: %d", http.StatusNotFound, problemDetails.Status) - } - if problemDetails.Detail != expectedDetail { - t.Errorf("Expected problemDetails.Detail to be '%s', got: '%s'", expectedDetail, problemDetails.Detail) - } - } -} diff --git a/internal/sbi/nsselection/network_slice_information_document.go b/internal/sbi/nsselection/network_slice_information_document.go deleted file mode 100644 index 7b9968d..0000000 --- a/internal/sbi/nsselection/network_slice_information_document.go +++ /dev/null @@ -1,162 +0,0 @@ -/* - * NSSF NS Selection - * - * NSSF Network Slice Selection Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package nsselection - -import ( - "encoding/json" - "fmt" - "net/http" - "net/url" - "strings" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/plugin" - "github.com/free5gc/nssf/internal/util" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -// Parse NSSelectionGet query parameter -func parseQueryParameter(query url.Values) (plugin.NsselectionQueryParameter, error) { - var ( - param plugin.NsselectionQueryParameter - err error - ) - - if query.Get("nf-type") != "" { - param.NfType = new(models.NfType) - *param.NfType = models.NfType(query.Get("nf-type")) - } - - param.NfId = query.Get("nf-id") - - if query.Get("slice-info-request-for-registration") != "" { - param.SliceInfoRequestForRegistration = new(models.SliceInfoForRegistration) - err = json.NewDecoder(strings.NewReader( - query.Get("slice-info-request-for-registration"))).Decode(param.SliceInfoRequestForRegistration) - if err != nil { - return param, err - } - } - - if query.Get("slice-info-request-for-pdu-session") != "" { - param.SliceInfoRequestForPduSession = new(models.SliceInfoForPduSession) - err = json.NewDecoder(strings.NewReader( - query.Get("slice-info-request-for-pdu-session"))).Decode(param.SliceInfoRequestForPduSession) - if err != nil { - return param, err - } - } - - if query.Get("home-plmn-id") != "" { - param.HomePlmnId = new(models.PlmnId) - err = json.NewDecoder(strings.NewReader(query.Get("home-plmn-id"))).Decode(param.HomePlmnId) - if err != nil { - return param, err - } - } - - if query.Get("tai") != "" { - param.Tai = new(models.Tai) - err = json.NewDecoder(strings.NewReader(query.Get("tai"))).Decode(param.Tai) - if err != nil { - return param, err - } - } - - if query.Get("supported-features") != "" { - param.SupportedFeatures = query.Get("supported-features") - } - - return param, err -} - -// Check if the NF service consumer is authorized -// TODO: Check if the NF service consumer is legal with local configuration, or possibly after querying NRF through -// `nf-id` e.g. Whether the V-NSSF is authorized -func checkNfServiceConsumer(nfType models.NfType) error { - if nfType != models.NfType_AMF && nfType != models.NfType_NSSF { - return fmt.Errorf("`nf-type`:'%s' is not authorized to retrieve the slice selection information", string(nfType)) - } - - return nil -} - -// NSSelectionGet - Retrieve the Network Slice Selection Information -func HandleNSSelectionGet(request *httpwrapper.Request) *httpwrapper.Response { - logger.NsselLog.Infof("Handle NSSelectionGet") - - query := request.Query - - response, problemDetails := NSSelectionGetProcedure(query) - - if response != nil { - return httpwrapper.NewResponse(http.StatusOK, nil, response) - } else if problemDetails != nil { - return httpwrapper.NewResponse(int(problemDetails.Status), nil, problemDetails) - } - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", - } - return httpwrapper.NewResponse(http.StatusForbidden, nil, problemDetails) -} - -func NSSelectionGetProcedure(query url.Values) (*models.AuthorizedNetworkSliceInfo, *models.ProblemDetails) { - var ( - status int - response *models.AuthorizedNetworkSliceInfo - problemDetails *models.ProblemDetails - ) - response = &models.AuthorizedNetworkSliceInfo{} - problemDetails = &models.ProblemDetails{} - - // TODO: Record request times of the NF service consumer and response with ProblemDetails of 429 Too Many Requests - // if the consumer has sent too many requests in a configured amount of time - // TODO: Check URI length and response with ProblemDetails of 414 URI Too Long if URI is too long - - // Parse query parameter - param, err := parseQueryParameter(query) - if err != nil { - // status = http.StatusBadRequest - problemDetails = &models.ProblemDetails{ - Title: util.MALFORMED_REQUEST, - Status: http.StatusBadRequest, - Detail: "[Query Parameter] " + err.Error(), - } - return nil, problemDetails - } - - // Check permission of NF service consumer - err = checkNfServiceConsumer(*param.NfType) - if err != nil { - // status = http.StatusForbidden - problemDetails = &models.ProblemDetails{ - Title: util.UNAUTHORIZED_CONSUMER, - Status: http.StatusForbidden, - Detail: err.Error(), - } - return nil, problemDetails - } - - if param.SliceInfoRequestForRegistration != nil { - // Network slice information is requested during the Registration procedure - status = nsselectionForRegistration(param, response, problemDetails) - } else { - // Network slice information is requested during the PDU session establishment procedure - status = nsselectionForPduSession(param, response, problemDetails) - } - - if status == http.StatusOK { - return response, problemDetails - } else { - return response, problemDetails - } -} diff --git a/internal/sbi/nsselection/nsselection_for_pdu_session.go b/internal/sbi/nsselection/nsselection_for_pdu_session.go deleted file mode 100644 index dd1d392..0000000 --- a/internal/sbi/nsselection/nsselection_for_pdu_session.go +++ /dev/null @@ -1,135 +0,0 @@ -/* - * NSSF NS Selection - * - * NSSF Network Slice Selection Service - */ - -package nsselection - -import ( - "fmt" - "math/rand" - "net/http" - - "github.com/free5gc/nssf/internal/plugin" - "github.com/free5gc/nssf/internal/util" - "github.com/free5gc/openapi/models" -) - -func selectNsiInformation(nsiInformationList []models.NsiInformation) models.NsiInformation { - // TODO: Algorithm to select Network Slice Instance - // Take roaming indication into consideration - - // Randomly select a Network Slice Instance - idx := rand.Intn(len(nsiInformationList)) - return nsiInformationList[idx] -} - -// Network slice selection for PDU session -// The function is executed when the IE, `slice-info-for-pdu-session`, is provided in query parameters -func nsselectionForPduSession(param plugin.NsselectionQueryParameter, - authorizedNetworkSliceInfo *models.AuthorizedNetworkSliceInfo, - problemDetails *models.ProblemDetails, -) int { - var status int - if param.HomePlmnId != nil { - // Check whether UE's Home PLMN is supported when UE is a roamer - if !util.CheckSupportedHplmn(*param.HomePlmnId) { - authorizedNetworkSliceInfo.RejectedNssaiInPlmn = append( - authorizedNetworkSliceInfo.RejectedNssaiInPlmn, - *param.SliceInfoRequestForPduSession.SNssai) - - status = http.StatusOK - return status - } - } - - if param.Tai != nil { - // Check whether UE's current TA is supported when UE provides TAI - if !util.CheckSupportedTa(*param.Tai) { - authorizedNetworkSliceInfo.RejectedNssaiInTa = append( - authorizedNetworkSliceInfo.RejectedNssaiInTa, - *param.SliceInfoRequestForPduSession.SNssai) - - status = http.StatusOK - return status - } - } - - if param.Tai != nil && - !util.CheckSupportedSnssaiInPlmn(*param.SliceInfoRequestForPduSession.SNssai, *param.Tai.PlmnId) { - // Return ProblemDetails indicating S-NSSAI is not supported - // TODO: Based on TS 23.501 V15.2.0, if the Requested NSSAI includes an S-NSSAI that is not valid in the - // Serving PLMN, the NSSF may derive the Configured NSSAI for Serving PLMN - *problemDetails = models.ProblemDetails{ - Title: util.UNSUPPORTED_RESOURCE, - Status: http.StatusForbidden, - Detail: "S-NSSAI in Requested NSSAI is not supported in PLMN", - Cause: "SNSSAI_NOT_SUPPORTED", - } - - status = http.StatusForbidden - return status - } - - if param.HomePlmnId != nil { - if param.SliceInfoRequestForPduSession.RoamingIndication == models.RoamingIndication_NON_ROAMING { - problemDetail := "`home-plmn-id` is provided, which contradicts `roamingIndication`:'NON_ROAMING'" - *problemDetails = models.ProblemDetails{ - Title: util.INVALID_REQUEST, - Status: http.StatusBadRequest, - Detail: problemDetail, - InvalidParams: []models.InvalidParam{ - { - Param: "home-plmn-id", - Reason: problemDetail, - }, - }, - } - - status = http.StatusBadRequest - return status - } - } else { - if param.SliceInfoRequestForPduSession.RoamingIndication != models.RoamingIndication_NON_ROAMING { - problemDetail := fmt.Sprintf("`home-plmn-id` is not provided, which contradicts `roamingIndication`:'%s'", - string(param.SliceInfoRequestForPduSession.RoamingIndication)) - *problemDetails = models.ProblemDetails{ - Title: util.INVALID_REQUEST, - Status: http.StatusBadRequest, - Detail: problemDetail, - InvalidParams: []models.InvalidParam{ - { - Param: "home-plmn-id", - Reason: problemDetail, - }, - }, - } - - status = http.StatusBadRequest - return status - } - } - - if param.Tai != nil && !util.CheckSupportedSnssaiInTa(*param.SliceInfoRequestForPduSession.SNssai, *param.Tai) { - // Requested S-NSSAI does not supported in UE's current TA - // Add it to Rejected NSSAI in TA - authorizedNetworkSliceInfo.RejectedNssaiInTa = append( - authorizedNetworkSliceInfo.RejectedNssaiInTa, - *param.SliceInfoRequestForPduSession.SNssai) - status = http.StatusOK - return status - } - - nsiInformationList := util.GetNsiInformationListFromConfig(*param.SliceInfoRequestForPduSession.SNssai) - - if len(nsiInformationList) == 0 { - *authorizedNetworkSliceInfo = models.AuthorizedNetworkSliceInfo{} - } else { - nsiInformation := selectNsiInformation(nsiInformationList) - authorizedNetworkSliceInfo.NsiInformation = new(models.NsiInformation) - *authorizedNetworkSliceInfo.NsiInformation = nsiInformation - } - - return http.StatusOK -} diff --git a/internal/sbi/processor/network_slice_information_document.go b/internal/sbi/processor/network_slice_information_document.go deleted file mode 100644 index 2cd1f47..0000000 --- a/internal/sbi/processor/network_slice_information_document.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - * NSSF NS Selection - * - * NSSF Network Slice Selection Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package processor - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/nsselection" -) - -func (p *Processor) HandleNetworkSliceInformationGet(c *gin.Context) { - logger.NsselLog.Infof("Handle NSSelectionGet") - - query := c.Request.URL.Query() - authorizedNetworkSliceInfo, problemDetails := nsselection.NSSelectionGetProcedure(query) - - if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - c.JSON(http.StatusOK, authorizedNetworkSliceInfo) -} diff --git a/internal/sbi/processor/nf_instance_id_document.go b/internal/sbi/processor/nf_instance_id_document.go deleted file mode 100644 index 2136608..0000000 --- a/internal/sbi/processor/nf_instance_id_document.go +++ /dev/null @@ -1,169 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package processor - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/plugin" - "github.com/free5gc/nssf/internal/sbi/nssaiavailability" - "github.com/free5gc/openapi" - "github.com/free5gc/openapi/models" -) - -// HandleNSSAIAvailabilityDelete - Deletes an already existing S-NSSAIs per TA -// provided by the NF service consumer (e.g AMF) -func (p *Processor) HandleNSSAIAvailabilityDelete(c *gin.Context) { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityDelete") - - nfId := c.Params.ByName("nfId") - - if nfId == "" { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - problemDetails := nssaiavailability.NfInstanceDelete(nfId) - - if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - c.Status(http.StatusNoContent) -} - -// HandleNSSAIAvailabilityPatch - Updates an already existing S-NSSAIs per TA -// provided by the NF service consumer (e.g AMF) -func (p *Processor) HandleNSSAIAvailabilityPatch(c *gin.Context) { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPatch") - - nfId := c.Params.ByName("nfId") - - if nfId == "" { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - var patchDocument plugin.PatchDocument - - requestBody, err := c.GetRawData() - if err != nil { - problemDetails := &models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - } - - c.JSON(http.StatusInternalServerError, problemDetails) - return - } - - if err = openapi.Deserialize(&patchDocument, requestBody, "application/json"); err != nil { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - logger.SBILog.Errorf("Error deserializing patch document: %+v", err) - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - // TODO: Request NfProfile of NfId from NRF - // Check if NfId is valid AMF and obtain AMF Set ID - // If NfId is invalid, return ProblemDetails with code 404 Not Found - // If NF consumer is not authorized to update NSSAI availability, return ProblemDetails with code 403 Forbidden - - info, problemDetails := nssaiavailability.NfInstancePatch(patchDocument, nfId) - - if info != nil { - c.JSON(http.StatusOK, info) - return - } else if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - c.JSON(http.StatusForbidden, problemDetails) -} - -// HandleNSSAIAvailabilityPut - Updates/replaces the NSSF -// with the S-NSSAIs the NF service consumer (e.g AMF) supports per TA -func (p *Processor) HandleNSSAIAvailabilityPut(c *gin.Context) { - logger.NssaiavailLog.Infof("Handle NSSAIAvailabilityPut") - - nfId := c.Params.ByName("nfId") - - if nfId == "" { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - var nssaiAvailabilityInfo models.NssaiAvailabilityInfo - data, err := c.GetRawData() - if err != nil { - problemDetails := &models.ProblemDetails{ - Status: http.StatusInternalServerError, - Cause: "SYSTEM_FAILURE", - } - - c.JSON(http.StatusInternalServerError, problemDetails) - return - } - - if err = openapi.Deserialize(&nssaiAvailabilityInfo, data, "application/json"); err != nil { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - logger.SBILog.Errorf("Error deserializing NSSAI availability info: %+v", err) - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - authorizedNssaiAvailabilityInfo, problemDetails := nssaiavailability.NfInstanceUpdate(nssaiAvailabilityInfo, nfId) - - if authorizedNssaiAvailabilityInfo != nil { - c.JSON(http.StatusOK, authorizedNssaiAvailabilityInfo) - return - } else if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - problemDetails = &models.ProblemDetails{ - Status: http.StatusForbidden, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - c.JSON(http.StatusForbidden, problemDetails) -} diff --git a/internal/sbi/nssaiavailability/nssaiavailability_store.go b/internal/sbi/processor/nssaiavailability_store.go similarity index 88% rename from internal/sbi/nssaiavailability/nssaiavailability_store.go rename to internal/sbi/processor/nssaiavailability_store.go index 0aeccbe..2011e55 100644 --- a/internal/sbi/nssaiavailability/nssaiavailability_store.go +++ b/internal/sbi/processor/nssaiavailability_store.go @@ -4,7 +4,7 @@ * NSSF NSSAI Availability Service */ -package nssaiavailability +package processor import ( "bytes" @@ -14,6 +14,7 @@ import ( "reflect" jsonpatch "github.com/evanphx/json-patch" + "github.com/gin-gonic/gin" "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/plugin" @@ -22,14 +23,16 @@ import ( "github.com/free5gc/openapi/models" ) -func NfInstanceDelete(nfId string) *models.ProblemDetails { +func (p *Processor) NssaiAvailabilityNfInstanceDelete(c *gin.Context, nfId string) { var problemDetails *models.ProblemDetails for i, amfConfig := range factory.NssfConfig.Configuration.AmfList { if amfConfig.NfId == nfId { factory.NssfConfig.Configuration.AmfList = append( factory.NssfConfig.Configuration.AmfList[:i], factory.NssfConfig.Configuration.AmfList[i+1:]...) - return nil + + c.Status(http.StatusNoContent) + return } } @@ -38,11 +41,12 @@ func NfInstanceDelete(nfId string) *models.ProblemDetails { Status: http.StatusNotFound, Detail: fmt.Sprintf("AMF ID '%s' does not exist", nfId), } - return problemDetails + util.GinProblemJson(c, problemDetails) } -func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId string) ( - *models.AuthorizedNssaiAvailabilityInfo, *models.ProblemDetails, +func (p *Processor) NssaiAvailabilityNfInstancePatch( + c *gin.Context, + nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId string, ) { var ( response *models.AuthorizedNssaiAvailabilityInfo = &models.AuthorizedNssaiAvailabilityInfo{} @@ -86,7 +90,8 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri Status: http.StatusNotFound, Detail: fmt.Sprintf("AMF ID '%s' does not exist", nfId), } - return nil, problemDetails + util.GinProblemJson(c, problemDetails) + return } // TODO: Check if returned HTTP status codes or problem details are proper when errors occur @@ -113,7 +118,8 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri Status: http.StatusBadRequest, Detail: err.Error(), } - return nil, problemDetails + util.GinProblemJson(c, problemDetails) + return } modified, err := patch.Apply(original) @@ -123,7 +129,8 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri Status: http.StatusConflict, Detail: err.Error(), } - return nil, problemDetails + util.GinProblemJson(c, problemDetails) + return } factory.NssfConfig.Lock() @@ -135,7 +142,8 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri Status: http.StatusBadRequest, Detail: err.Error(), } - return nil, problemDetails + util.GinProblemJson(c, problemDetails) + return } // Return all authorized NSSAI availability information @@ -146,12 +154,13 @@ func NfInstancePatch(nssaiAvailabilityUpdateInfo plugin.PatchDocument, nfId stri // TODO: Return authorized NSSAI availability information of updated TAI only - return response, nil + c.JSON(http.StatusOK, response) } // NSSAIAvailability PUT method -func NfInstanceUpdate(nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId string) ( - *models.AuthorizedNssaiAvailabilityInfo, *models.ProblemDetails, +func (p *Processor) NssaiAvailabilityNfInstanceUpdate( + c *gin.Context, + nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId string, ) { var ( response *models.AuthorizedNssaiAvailabilityInfo = &models.AuthorizedNssaiAvailabilityInfo{} @@ -166,7 +175,9 @@ func NfInstanceUpdate(nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId s Detail: "S-NSSAI in Requested NSSAI is not supported in PLMN", Cause: "SNSSAI_NOT_SUPPORTED", } - return nil, problemDetails + + util.GinProblemJson(c, problemDetails) + return } } @@ -215,5 +226,5 @@ func NfInstanceUpdate(nssaiAvailabilityInfo models.NssaiAvailabilityInfo, nfId s } } - return response, problemDetails + c.JSON(http.StatusOK, response) } diff --git a/internal/sbi/processor/nssaiavailability_store_test.go b/internal/sbi/processor/nssaiavailability_store_test.go new file mode 100644 index 0000000..1ab784b --- /dev/null +++ b/internal/sbi/processor/nssaiavailability_store_test.go @@ -0,0 +1,89 @@ +package processor + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "go.uber.org/mock/gomock" + + "github.com/free5gc/nssf/internal/util" + "github.com/free5gc/nssf/pkg/app" + "github.com/free5gc/nssf/pkg/factory" + "github.com/free5gc/openapi/models" +) + +func setup() { + // Set the default values for the factory.NssfConfig + factory.NssfConfig = &factory.Config{ + Configuration: &factory.Configuration{}, + } +} + +func TestMain(m *testing.M) { + // Run the tests + setup() + m.Run() +} + +func TestNfInstanceDelete(t *testing.T) { + mockNssfApp := app.NewMockNssfApp(gomock.NewController(t)) + processor := NewProcessor(mockNssfApp) + httpRecorder := httptest.NewRecorder() + c, _ := gin.CreateTestContext(httpRecorder) + + // Create a sample AMF list + amfList := []factory.AmfConfig{ + { + NfId: "nf1", + }, + { + NfId: "nf2", + }, + { + NfId: "nf3", + }, + } + + // Set the sample AMF list in the factory.NssfConfig.Configuration + factory.NssfConfig.Configuration.AmfList = amfList + + // Test case 1: Delete an existing NF instance + nfIdToDelete := "nf2" + processor.NssaiAvailabilityNfInstanceDelete(c, nfIdToDelete) + if c.Writer.Status() != http.StatusNoContent { + t.Errorf("Expected status code %d, got: %d", http.StatusNoContent, httpRecorder.Code) + } + + // Verify that the NF instance is deleted from the AMF list + for _, amfConfig := range factory.NssfConfig.Configuration.AmfList { + if amfConfig.NfId == nfIdToDelete { + t.Errorf("Expected NF instance '%s' to be deleted, but it still exists", nfIdToDelete) + } + } + + // Test case 2: Delete a non-existing NF instance + nfIdToDelete = "nf4" + expectedDetail := fmt.Sprintf("AMF ID '%s' does not exist", nfIdToDelete) + processor.NssaiAvailabilityNfInstanceDelete(c, nfIdToDelete) + if httpRecorder.Code != http.StatusNotFound { + t.Errorf("Expected status code %d, got: %d", http.StatusNotFound, httpRecorder.Code) + } + + var problemDetails models.ProblemDetails + if err := json.Unmarshal(httpRecorder.Body.Bytes(), &problemDetails); err != nil { + t.Errorf("Error unmarshalling response body: %v", err) + } + if problemDetails.Title != util.UNSUPPORTED_RESOURCE { + t.Errorf("Expected problemDetails.Title to be '%s', got: '%s'", util.UNSUPPORTED_RESOURCE, problemDetails.Title) + } + if problemDetails.Status != http.StatusNotFound { + t.Errorf("Expected problemDetails.Status to be %d, got: %d", http.StatusNotFound, problemDetails.Status) + } + if problemDetails.Detail != expectedDetail { + t.Errorf("Expected problemDetails.Detail to be '%s', got: '%s'", expectedDetail, problemDetails.Detail) + } +} diff --git a/internal/sbi/nssaiavailability/nssaiavailability_subscription.go b/internal/sbi/processor/nssaiavailability_subscription.go similarity index 85% rename from internal/sbi/nssaiavailability/nssaiavailability_subscription.go rename to internal/sbi/processor/nssaiavailability_subscription.go index cc08f57..8e65ebe 100644 --- a/internal/sbi/nssaiavailability/nssaiavailability_subscription.go +++ b/internal/sbi/processor/nssaiavailability_subscription.go @@ -4,7 +4,7 @@ * NSSF NSSAI Availability Service */ -package nssaiavailability +package processor import ( "fmt" @@ -13,6 +13,8 @@ import ( "strconv" "time" + "github.com/gin-gonic/gin" + "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/util" "github.com/free5gc/nssf/pkg/factory" @@ -43,8 +45,9 @@ func getUnusedSubscriptionID() (string, error) { } // NSSAIAvailability subscription POST method -func SubscriptionCreate(createData models.NssfEventSubscriptionCreateData) ( - *models.NssfEventSubscriptionCreatedData, *models.ProblemDetails, +func (p *Processor) NssaiAvailabilitySubscriptionCreate( + c *gin.Context, + createData models.NssfEventSubscriptionCreateData, ) { var ( response *models.NssfEventSubscriptionCreatedData = &models.NssfEventSubscriptionCreatedData{} @@ -61,7 +64,9 @@ func SubscriptionCreate(createData models.NssfEventSubscriptionCreateData) ( Status: http.StatusNotFound, Detail: err.Error(), } - return nil, problemDetails + + util.GinProblemJson(c, problemDetails) + return } subscription.SubscriptionId = tempID @@ -77,10 +82,10 @@ func SubscriptionCreate(createData models.NssfEventSubscriptionCreateData) ( } response.AuthorizedNssaiAvailabilityData = util.AuthorizeOfTaListFromConfig(subscription.SubscriptionData.TaiList) - return response, nil + c.JSON(http.StatusOK, response) } -func SubscriptionUnsubscribe(subscriptionId string) *models.ProblemDetails { +func (p *Processor) NssaiAvailabilitySubscriptionUnsubscribe(c *gin.Context, subscriptionId string) { var problemDetails *models.ProblemDetails factory.NssfConfig.Lock() @@ -90,7 +95,8 @@ func SubscriptionUnsubscribe(subscriptionId string) *models.ProblemDetails { factory.NssfConfig.Subscriptions = append(factory.NssfConfig.Subscriptions[:i], factory.NssfConfig.Subscriptions[i+1:]...) - return nil + c.Status(http.StatusNoContent) + return } } @@ -100,5 +106,6 @@ func SubscriptionUnsubscribe(subscriptionId string) *models.ProblemDetails { Status: http.StatusNotFound, Detail: fmt.Sprintf("Subscription ID '%s' is not available", subscriptionId), } - return problemDetails + + util.GinProblemJson(c, problemDetails) } diff --git a/internal/sbi/nsselection/nsselection_for_registration.go b/internal/sbi/processor/nsselection_network_slice_information.go similarity index 65% rename from internal/sbi/nsselection/nsselection_for_registration.go rename to internal/sbi/processor/nsselection_network_slice_information.go index 9a09479..39adc72 100644 --- a/internal/sbi/nsselection/nsselection_for_registration.go +++ b/internal/sbi/processor/nsselection_network_slice_information.go @@ -2,12 +2,22 @@ * NSSF NS Selection * * NSSF Network Slice Selection Service + * + * API version: 1.0.0 + * Generated by: OpenAPI Generator (https://openapi-generator.tech) */ -package nsselection +package processor import ( + "encoding/json" + "fmt" + "math/rand" "net/http" + "net/url" + "strings" + + "github.com/gin-gonic/gin" "github.com/free5gc/nssf/internal/logger" "github.com/free5gc/nssf/internal/plugin" @@ -16,6 +26,133 @@ import ( "github.com/free5gc/openapi/models" ) +// Parse NSSelectionGet query parameter +func parseQueryParameter(query url.Values) (plugin.NsselectionQueryParameter, error) { + var ( + param plugin.NsselectionQueryParameter + err error + ) + + if query.Get("nf-type") != "" { + param.NfType = new(models.NfType) + *param.NfType = models.NfType(query.Get("nf-type")) + } + + param.NfId = query.Get("nf-id") + + if query.Get("slice-info-request-for-registration") != "" { + param.SliceInfoRequestForRegistration = new(models.SliceInfoForRegistration) + err = json.NewDecoder(strings.NewReader( + query.Get("slice-info-request-for-registration"))).Decode(param.SliceInfoRequestForRegistration) + if err != nil { + return param, err + } + } + + if query.Get("slice-info-request-for-pdu-session") != "" { + param.SliceInfoRequestForPduSession = new(models.SliceInfoForPduSession) + err = json.NewDecoder(strings.NewReader( + query.Get("slice-info-request-for-pdu-session"))).Decode(param.SliceInfoRequestForPduSession) + if err != nil { + return param, err + } + } + + if query.Get("home-plmn-id") != "" { + param.HomePlmnId = new(models.PlmnId) + err = json.NewDecoder(strings.NewReader(query.Get("home-plmn-id"))).Decode(param.HomePlmnId) + if err != nil { + return param, err + } + } + + if query.Get("tai") != "" { + param.Tai = new(models.Tai) + err = json.NewDecoder(strings.NewReader(query.Get("tai"))).Decode(param.Tai) + if err != nil { + return param, err + } + } + + if query.Get("supported-features") != "" { + param.SupportedFeatures = query.Get("supported-features") + } + + return param, err +} + +// Check if the NF service consumer is authorized +// TODO: Check if the NF service consumer is legal with local configuration, or possibly after querying NRF through +// `nf-id` e.g. Whether the V-NSSF is authorized +func checkNfServiceConsumer(nfType models.NfType) error { + if nfType != models.NfType_AMF && nfType != models.NfType_NSSF { + return fmt.Errorf("`nf-type`:'%s' is not authorized to retrieve the slice selection information", string(nfType)) + } + + return nil +} + +func (p *Processor) NSSelectionSliceInformationGet(c *gin.Context, query url.Values) { + var ( + status int + response *models.AuthorizedNetworkSliceInfo + problemDetails *models.ProblemDetails + ) + + // TODO: Record request times of the NF service consumer and response with ProblemDetails of 429 Too Many Requests + // if the consumer has sent too many requests in a configured amount of time + // TODO: Check URI length and response with ProblemDetails of 414 URI Too Long if URI is too long + + // Parse query parameter + param, err := parseQueryParameter(query) + if err != nil { + // status = http.StatusBadRequest + problemDetails = &models.ProblemDetails{ + Title: util.MALFORMED_REQUEST, + Status: http.StatusBadRequest, + Detail: "[Query Parameter] " + err.Error(), + } + util.GinProblemJson(c, problemDetails) + return + } + + // Check permission of NF service consumer + err = checkNfServiceConsumer(*param.NfType) + if err != nil { + // status = http.StatusForbidden + problemDetails = &models.ProblemDetails{ + Title: util.UNAUTHORIZED_CONSUMER, + Status: http.StatusForbidden, + Detail: err.Error(), + } + util.GinProblemJson(c, problemDetails) + return + } + + if param.SliceInfoRequestForRegistration != nil { + // Network slice information is requested during the Registration procedure + status, response, problemDetails = nsselectionForRegistration(param) + } else { + // Network slice information is requested during the PDU session establishment procedure + status, response, problemDetails = nsselectionForPduSession(param) + } + + if problemDetails != nil { + util.GinProblemJson(c, problemDetails) + return + } + + if response == nil { + util.GinProblemJson(c, &models.ProblemDetails{ + Title: util.INTERNAL_ERROR, + Status: http.StatusInternalServerError, + }) + return + } + + c.JSON(status, response) +} + // Set Allowed NSSAI with Subscribed S-NSSAI(s) which are marked as default S-NSSAI(s) func useDefaultSubscribedSnssai( param plugin.NsselectionQueryParameter, authorizedNetworkSliceInfo *models.AuthorizedNetworkSliceInfo, @@ -162,10 +299,11 @@ func setConfiguredNssai( // Network slice selection for registration // The function is executed when the IE, `slice-info-request-for-registration`, is provided in query parameters -func nsselectionForRegistration(param plugin.NsselectionQueryParameter, - authorizedNetworkSliceInfo *models.AuthorizedNetworkSliceInfo, - problemDetails *models.ProblemDetails, -) int { +func nsselectionForRegistration(param plugin.NsselectionQueryParameter) ( + int, *models.AuthorizedNetworkSliceInfo, *models.ProblemDetails, +) { + authorizedNetworkSliceInfo := &models.AuthorizedNetworkSliceInfo{} + var status int if param.HomePlmnId != nil { // Check whether UE's Home PLMN is supported when UE is a roamer @@ -175,7 +313,7 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, param.SliceInfoRequestForRegistration.RequestedNssai...) status = http.StatusOK - return status + return status, authorizedNetworkSliceInfo, nil } } @@ -187,7 +325,7 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, param.SliceInfoRequestForRegistration.RequestedNssai...) status = http.StatusOK - return status + return status, authorizedNetworkSliceInfo, nil } } @@ -198,22 +336,22 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, // for S-NSSAIs in both `sNssaiForMapping` and `subscribedSnssai` if present if param.HomePlmnId == nil { - problemDetail := "[Query Parameter] `home-plmn-id` should be provided" + + detail := "[Query Parameter] `home-plmn-id` should be provided" + " when requesting VPLMN specific mapped S-NSSAI values" - *problemDetails = models.ProblemDetails{ + problemDetails := &models.ProblemDetails{ Title: util.INVALID_REQUEST, Status: http.StatusBadRequest, - Detail: problemDetail, + Detail: detail, InvalidParams: []models.InvalidParam{ { Param: "home-plmn-id", - Reason: problemDetail, + Reason: detail, }, }, } status = http.StatusBadRequest - return status + return status, nil, problemDetails } mappingOfSnssai := util.GetMappingOfPlmnFromConfig(*param.HomePlmnId) @@ -286,12 +424,12 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, } status = http.StatusOK - return status + return status, authorizedNetworkSliceInfo, nil } else { logger.NsselLog.Warnf("No S-NSSAI mapping of UE's HPLMN %+v in NSSF configuration", *param.HomePlmnId) status = http.StatusOK - return status + return status, authorizedNetworkSliceInfo, nil } } @@ -306,7 +444,7 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, // Return ProblemDetails indicating S-NSSAI is not supported // TODO: Based on TS 23.501 V15.2.0, if the Requested NSSAI includes an S-NSSAI that is not valid in the // Serving PLMN, the NSSF may derive the Configured NSSAI for Serving PLMN - *problemDetails = models.ProblemDetails{ + problemDetails := &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusForbidden, Detail: "S-NSSAI in Requested NSSAI is not supported in PLMN", @@ -314,7 +452,7 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, } status = http.StatusForbidden - return status + return status, nil, problemDetails } // Check if any Requested S-NSSAIs is present in Subscribed S-NSSAIs @@ -435,5 +573,124 @@ func nsselectionForRegistration(param plugin.NsselectionQueryParameter, } status = http.StatusOK - return status + return status, authorizedNetworkSliceInfo, nil +} + +func selectNsiInformation(nsiInformationList []models.NsiInformation) models.NsiInformation { + // TODO: Algorithm to select Network Slice Instance + // Take roaming indication into consideration + + // Randomly select a Network Slice Instance + idx := rand.Intn(len(nsiInformationList)) + return nsiInformationList[idx] +} + +// Network slice selection for PDU session +// The function is executed when the IE, `slice-info-for-pdu-session`, is provided in query parameters +func nsselectionForPduSession(param plugin.NsselectionQueryParameter) ( + int, *models.AuthorizedNetworkSliceInfo, *models.ProblemDetails, +) { + var status int + authorizedNetworkSliceInfo := &models.AuthorizedNetworkSliceInfo{} + + if param.HomePlmnId != nil { + // Check whether UE's Home PLMN is supported when UE is a roamer + if !util.CheckSupportedHplmn(*param.HomePlmnId) { + authorizedNetworkSliceInfo.RejectedNssaiInPlmn = append( + authorizedNetworkSliceInfo.RejectedNssaiInPlmn, + *param.SliceInfoRequestForPduSession.SNssai) + + status = http.StatusOK + return status, authorizedNetworkSliceInfo, nil + } + } + + if param.Tai != nil { + // Check whether UE's current TA is supported when UE provides TAI + if !util.CheckSupportedTa(*param.Tai) { + authorizedNetworkSliceInfo.RejectedNssaiInTa = append( + authorizedNetworkSliceInfo.RejectedNssaiInTa, + *param.SliceInfoRequestForPduSession.SNssai) + + status = http.StatusOK + return status, authorizedNetworkSliceInfo, nil + } + } + + if param.Tai != nil && + !util.CheckSupportedSnssaiInPlmn(*param.SliceInfoRequestForPduSession.SNssai, *param.Tai.PlmnId) { + // Return ProblemDetails indicating S-NSSAI is not supported + // TODO: Based on TS 23.501 V15.2.0, if the Requested NSSAI includes an S-NSSAI that is not valid in the + // Serving PLMN, the NSSF may derive the Configured NSSAI for Serving PLMN + problemDetails := &models.ProblemDetails{ + Title: util.UNSUPPORTED_RESOURCE, + Status: http.StatusForbidden, + Detail: "S-NSSAI in Requested NSSAI is not supported in PLMN", + Cause: "SNSSAI_NOT_SUPPORTED", + } + + status = http.StatusForbidden + return status, nil, problemDetails + } + + if param.HomePlmnId != nil { + if param.SliceInfoRequestForPduSession.RoamingIndication == models.RoamingIndication_NON_ROAMING { + detail := "`home-plmn-id` is provided, which contradicts `roamingIndication`:'NON_ROAMING'" + problemDetails := &models.ProblemDetails{ + Title: util.INVALID_REQUEST, + Status: http.StatusBadRequest, + Detail: detail, + InvalidParams: []models.InvalidParam{ + { + Param: "home-plmn-id", + Reason: detail, + }, + }, + } + + status = http.StatusBadRequest + return status, nil, problemDetails + } + } else { + if param.SliceInfoRequestForPduSession.RoamingIndication != models.RoamingIndication_NON_ROAMING { + detail := fmt.Sprintf("`home-plmn-id` is not provided, which contradicts `roamingIndication`:'%s'", + string(param.SliceInfoRequestForPduSession.RoamingIndication)) + problemDetails := &models.ProblemDetails{ + Title: util.INVALID_REQUEST, + Status: http.StatusBadRequest, + Detail: detail, + InvalidParams: []models.InvalidParam{ + { + Param: "home-plmn-id", + Reason: detail, + }, + }, + } + + status = http.StatusBadRequest + return status, nil, problemDetails + } + } + + if param.Tai != nil && !util.CheckSupportedSnssaiInTa(*param.SliceInfoRequestForPduSession.SNssai, *param.Tai) { + // Requested S-NSSAI does not supported in UE's current TA + // Add it to Rejected NSSAI in TA + authorizedNetworkSliceInfo.RejectedNssaiInTa = append( + authorizedNetworkSliceInfo.RejectedNssaiInTa, + *param.SliceInfoRequestForPduSession.SNssai) + status = http.StatusOK + return status, authorizedNetworkSliceInfo, nil + } + + nsiInformationList := util.GetNsiInformationListFromConfig(*param.SliceInfoRequestForPduSession.SNssai) + + if len(nsiInformationList) == 0 { + *authorizedNetworkSliceInfo = models.AuthorizedNetworkSliceInfo{} + } else { + nsiInformation := selectNsiInformation(nsiInformationList) + authorizedNetworkSliceInfo.NsiInformation = new(models.NsiInformation) + *authorizedNetworkSliceInfo.NsiInformation = nsiInformation + } + + return http.StatusOK, authorizedNetworkSliceInfo, nil } diff --git a/internal/sbi/processor/subscription_id_document.go b/internal/sbi/processor/subscription_id_document.go deleted file mode 100644 index cd87147..0000000 --- a/internal/sbi/processor/subscription_id_document.go +++ /dev/null @@ -1,54 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package processor - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/nssaiavailability" - "github.com/free5gc/openapi/models" - "github.com/free5gc/util/httpwrapper" -) - -func (p *Processor) HandleNSSAIAvailabilityUnsubscribeDelete(c *gin.Context) { - // Due to conflict of route matching, 'subscriptions' in the route is replaced with the existing wildcard ':nfId' - nfID := c.Param("nfId") - if nfID != "subscriptions" { - c.JSON(http.StatusNotFound, gin.H{}) - logger.NssaiavailLog.Infof("404 Not Found") - return - } - - req := httpwrapper.NewRequest(c.Request, nil) - req.Params["subscriptionId"] = c.Params.ByName("subscriptionId") - - subscriptionId := c.Params.ByName("subscriptionId") - if subscriptionId == "" { - problemDetails := &models.ProblemDetails{ - Status: http.StatusBadRequest, - Cause: "UNSPECIFIED", // TODO: Check if this is the correct cause - } - - c.JSON(http.StatusBadRequest, problemDetails) - return - } - - problemDetails := nssaiavailability.SubscriptionUnsubscribe(subscriptionId) - - if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - c.Status(http.StatusNoContent) -} diff --git a/internal/sbi/processor/subscriptions_collection.go b/internal/sbi/processor/subscriptions_collection.go deleted file mode 100644 index 34723e8..0000000 --- a/internal/sbi/processor/subscriptions_collection.go +++ /dev/null @@ -1,62 +0,0 @@ -/* - * NSSF NSSAI Availability - * - * NSSF NSSAI Availability Service - * - * API version: 1.0.0 - * Generated by: OpenAPI Generator (https://openapi-generator.tech) - */ - -package processor - -import ( - "net/http" - - "github.com/gin-gonic/gin" - - "github.com/free5gc/nssf/internal/logger" - "github.com/free5gc/nssf/internal/sbi/nssaiavailability" - "github.com/free5gc/openapi" - . "github.com/free5gc/openapi/models" -) - -func (p *Processor) HandleNSSAIAvailabilityPost(c *gin.Context) { - var createData NssfEventSubscriptionCreateData - - requestBody, err := c.GetRawData() - if err != nil { - problemDetail := ProblemDetails{ - Title: "System failure", - Status: http.StatusInternalServerError, - Detail: err.Error(), - Cause: "SYSTEM_FAILURE", - } - logger.NssaiavailLog.Errorf("Get Request Body error: %+v", err) - c.JSON(http.StatusInternalServerError, problemDetail) - return - } - - err = openapi.Deserialize(&createData, requestBody, "application/json") - if err != nil { - problemDetail := "[Request Body] " + err.Error() - rsp := ProblemDetails{ - Title: "Malformed request syntax", - Status: http.StatusBadRequest, - Detail: problemDetail, - } - logger.NssaiavailLog.Errorln(problemDetail) - c.JSON(http.StatusBadRequest, rsp) - return - } - - createdData, problemDetails := nssaiavailability.SubscriptionCreate(createData) - - if problemDetails != nil { - c.JSON(int(problemDetails.Status), problemDetails) - return - } - - // TODO: Based on TS 29.531 5.3.2.3.1, add location header - - c.JSON(http.StatusCreated, createdData) -} diff --git a/internal/util/problem_json.go b/internal/util/problem_json.go new file mode 100644 index 0000000..6efdc0c --- /dev/null +++ b/internal/util/problem_json.go @@ -0,0 +1,12 @@ +package util + +import ( + "github.com/gin-gonic/gin" + + "github.com/free5gc/openapi/models" +) + +func GinProblemJson(c *gin.Context, problemDetails *models.ProblemDetails) { + c.JSON(int(problemDetails.Status), problemDetails) + c.Writer.Header().Set("Content-Type", "application/problem+json") +} diff --git a/internal/util/util.go b/internal/util/util.go index 5ad2e7b..4abb10d 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -17,6 +17,7 @@ import ( // Title in Problem Details for NSSF HTTP APIs const ( + INTERNAL_ERROR = "Internal server error" INVALID_REQUEST = "Invalid request message framing" MALFORMED_REQUEST = "Malformed request syntax" UNAUTHORIZED_CONSUMER = "Unauthorized NF service consumer" diff --git a/pkg/app/mock.go b/pkg/app/mock.go new file mode 100644 index 0000000..e918625 --- /dev/null +++ b/pkg/app/mock.go @@ -0,0 +1,129 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: pkg/app/app.go +// +// Generated by this command: +// +// mockgen -source=pkg/app/app.go -package=app +// + +// Package app is a generated GoMock package. +package app + +import ( + reflect "reflect" + + context "github.com/free5gc/nssf/internal/context" + factory "github.com/free5gc/nssf/pkg/factory" + gomock "go.uber.org/mock/gomock" +) + +// MockNssfApp is a mock of NssfApp interface. +type MockNssfApp struct { + ctrl *gomock.Controller + recorder *MockNssfAppMockRecorder +} + +// MockNssfAppMockRecorder is the mock recorder for MockNssfApp. +type MockNssfAppMockRecorder struct { + mock *MockNssfApp +} + +// NewMockNssfApp creates a new mock instance. +func NewMockNssfApp(ctrl *gomock.Controller) *MockNssfApp { + mock := &MockNssfApp{ctrl: ctrl} + mock.recorder = &MockNssfAppMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNssfApp) EXPECT() *MockNssfAppMockRecorder { + return m.recorder +} + +// Config mocks base method. +func (m *MockNssfApp) Config() *factory.Config { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Config") + ret0, _ := ret[0].(*factory.Config) + return ret0 +} + +// Config indicates an expected call of Config. +func (mr *MockNssfAppMockRecorder) Config() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Config", reflect.TypeOf((*MockNssfApp)(nil).Config)) +} + +// Context mocks base method. +func (m *MockNssfApp) Context() *context.NSSFContext { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(*context.NSSFContext) + return ret0 +} + +// Context indicates an expected call of Context. +func (mr *MockNssfAppMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockNssfApp)(nil).Context)) +} + +// SetLogEnable mocks base method. +func (m *MockNssfApp) SetLogEnable(enable bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetLogEnable", enable) +} + +// SetLogEnable indicates an expected call of SetLogEnable. +func (mr *MockNssfAppMockRecorder) SetLogEnable(enable any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogEnable", reflect.TypeOf((*MockNssfApp)(nil).SetLogEnable), enable) +} + +// SetLogLevel mocks base method. +func (m *MockNssfApp) SetLogLevel(level string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetLogLevel", level) +} + +// SetLogLevel indicates an expected call of SetLogLevel. +func (mr *MockNssfAppMockRecorder) SetLogLevel(level any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogLevel", reflect.TypeOf((*MockNssfApp)(nil).SetLogLevel), level) +} + +// SetReportCaller mocks base method. +func (m *MockNssfApp) SetReportCaller(reportCaller bool) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetReportCaller", reportCaller) +} + +// SetReportCaller indicates an expected call of SetReportCaller. +func (mr *MockNssfAppMockRecorder) SetReportCaller(reportCaller any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReportCaller", reflect.TypeOf((*MockNssfApp)(nil).SetReportCaller), reportCaller) +} + +// Start mocks base method. +func (m *MockNssfApp) Start() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Start") +} + +// Start indicates an expected call of Start. +func (mr *MockNssfAppMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockNssfApp)(nil).Start)) +} + +// Terminate mocks base method. +func (m *MockNssfApp) Terminate() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Terminate") +} + +// Terminate indicates an expected call of Terminate. +func (mr *MockNssfAppMockRecorder) Terminate() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Terminate", reflect.TypeOf((*MockNssfApp)(nil).Terminate)) +} From 4700f688699d6726300f7e7881cc9a6e8236d84a Mon Sep 17 00:00:00 2001 From: newb1er Date: Fri, 10 May 2024 21:12:19 +0800 Subject: [PATCH 22/32] refactor: nssf context init function --- internal/context/context.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/context/context.go b/internal/context/context.go index a1a6004..ac4aff6 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -66,13 +66,13 @@ type NSSFContext struct { // Initialize NSSF context with configuration factory func InitNssfContext() { - Init() - nssfConfig := factory.NssfConfig if nssfConfig.Configuration.NssfName != "" { nssfContext.Name = nssfConfig.Configuration.NssfName } + nssfContext.NfId = uuid.New().String() + nssfContext.Name = "NSSF" nssfContext.UriScheme = nssfConfig.Configuration.Sbi.Scheme nssfContext.RegisterIPv4 = nssfConfig.Configuration.Sbi.RegisterIPv4 nssfContext.SBIPort = nssfConfig.Configuration.Sbi.Port From d666c0b535afc2c7f035f6ea4889a3d8cb660e00 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 20 May 2024 16:09:53 +0800 Subject: [PATCH 23/32] fix: duplicate processor --- pkg/service/init.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/service/init.go b/pkg/service/init.go index 7a298d4..724da34 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -45,9 +45,6 @@ func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) - processor := processor.NewProcessor(nssf) - nssf.processor = processor - consumer := consumer.NewConsumer(nssf) nssf.consumer = consumer From 18cbb37ac1444385dcb7329f37292165438c46a6 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 20 May 2024 16:12:03 +0800 Subject: [PATCH 24/32] fix: magic number --- internal/context/context.go | 6 ++++-- internal/sbi/consumer/nrf_service.go | 3 ++- internal/util/util.go | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/internal/context/context.go b/internal/context/context.go index ac4aff6..9f7f624 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -22,6 +22,8 @@ import ( "github.com/free5gc/openapi/oauth" ) +const NRF_PORT = 29510 + var nssfContext = NSSFContext{} // Initialize NSSF context with default value @@ -40,7 +42,7 @@ func Init() { } nssfContext.NfService = initNfService(serviceName, "1.0.0") - nssfContext.NrfUri = fmt.Sprintf("%s://%s:%d", models.UriScheme_HTTPS, nssfContext.RegisterIPv4, 29510) + nssfContext.NrfUri = fmt.Sprintf("%s://%s:%d", models.UriScheme_HTTPS, nssfContext.RegisterIPv4, NRF_PORT) } type NFContext interface { @@ -93,7 +95,7 @@ func InitNssfContext() { nssfContext.NrfUri = nssfConfig.Configuration.NrfUri } else { logger.InitLog.Warn("NRF Uri is empty! Using localhost as NRF IPv4 address.") - nssfContext.NrfUri = fmt.Sprintf("%s://%s:%d", nssfContext.UriScheme, "127.0.0.1", 29510) + nssfContext.NrfUri = fmt.Sprintf("%s://%s:%d", nssfContext.UriScheme, "127.0.0.1", NRF_PORT) } nssfContext.NrfCertPem = nssfConfig.Configuration.NrfCertPem nssfContext.SupportedPlmnList = nssfConfig.Configuration.SupportedPlmnList diff --git a/internal/sbi/consumer/nrf_service.go b/internal/sbi/consumer/nrf_service.go index 58a96f4..8c821ab 100644 --- a/internal/sbi/consumer/nrf_service.go +++ b/internal/sbi/consumer/nrf_service.go @@ -63,7 +63,8 @@ func (ns *NrfService) SendRegisterNFInstance(ctx context.Context, nssfCtx *nssf_ if err != nil || res == nil { // TODO : add log logger.ConsumerLog.Errorf("NSSF register to NRF Error[%s]", err.Error()) - time.Sleep(2 * time.Second) + const retryInterval = 2 * time.Second + time.Sleep(retryInterval) continue } defer func() { diff --git a/internal/util/util.go b/internal/util/util.go index 4abb10d..5591f75 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -394,7 +394,8 @@ func AddAllowedSnssai(allowedSnssai models.AllowedSnssai, accessType models.Acce for i := range authorizedNetworkSliceInfo.AllowedNssaiList { if authorizedNetworkSliceInfo.AllowedNssaiList[i].AccessType == accessType { hitAllowedNssai = true - if len(authorizedNetworkSliceInfo.AllowedNssaiList[i].AllowedSnssaiList) == 8 { + const MAX_ALLOWED_SNSSAI = 8 + if len(authorizedNetworkSliceInfo.AllowedNssaiList[i].AllowedSnssaiList) == MAX_ALLOWED_SNSSAI { logger.UtilLog.Infof("Unable to add a new Allowed S-NSSAI since already eight S-NSSAIs in Allowed NSSAI") } else { authorizedNetworkSliceInfo.AllowedNssaiList[i].AllowedSnssaiList = append( From 54d751074291dc7a5cc629258a905bf11ecb14f7 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 20 May 2024 16:13:16 +0800 Subject: [PATCH 25/32] fix: possibly -1 in index range --- internal/sbi/consumer/nrf_service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sbi/consumer/nrf_service.go b/internal/sbi/consumer/nrf_service.go index 8c821ab..8700df9 100644 --- a/internal/sbi/consumer/nrf_service.go +++ b/internal/sbi/consumer/nrf_service.go @@ -79,7 +79,7 @@ func (ns *NrfService) SendRegisterNFInstance(ctx context.Context, nssfCtx *nssf_ } else if status == http.StatusCreated { // NFRegister resourceUri := res.Header.Get("Location") - resourceNrfUri = resourceUri[:strings.Index(resourceUri, "/nnrf-nfm/")] + resourceNrfUri, _, _ = strings.Cut(resourceUri, "/nnrf-nfm/") retrieveNfInstanceId = resourceUri[strings.LastIndex(resourceUri, "/")+1:] oauth2 := false From f0b8da489aaec4a89b5e93f4c30f00c0b4675ee6 Mon Sep 17 00:00:00 2001 From: newb1er Date: Mon, 20 May 2024 16:13:37 +0800 Subject: [PATCH 26/32] fix: linter complains --- internal/sbi/processor/nssaiavailability_store_test.go | 5 +++-- internal/sbi/processor/nssaiavailability_subscription.go | 2 +- internal/util/router_auth_check_test.go | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/internal/sbi/processor/nssaiavailability_store_test.go b/internal/sbi/processor/nssaiavailability_store_test.go index 1ab784b..ca915fb 100644 --- a/internal/sbi/processor/nssaiavailability_store_test.go +++ b/internal/sbi/processor/nssaiavailability_store_test.go @@ -1,4 +1,4 @@ -package processor +package processor_test import ( "encoding/json" @@ -10,6 +10,7 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/mock/gomock" + "github.com/free5gc/nssf/internal/sbi/processor" "github.com/free5gc/nssf/internal/util" "github.com/free5gc/nssf/pkg/app" "github.com/free5gc/nssf/pkg/factory" @@ -31,7 +32,7 @@ func TestMain(m *testing.M) { func TestNfInstanceDelete(t *testing.T) { mockNssfApp := app.NewMockNssfApp(gomock.NewController(t)) - processor := NewProcessor(mockNssfApp) + processor := processor.NewProcessor(mockNssfApp) httpRecorder := httptest.NewRecorder() c, _ := gin.CreateTestContext(httpRecorder) diff --git a/internal/sbi/processor/nssaiavailability_subscription.go b/internal/sbi/processor/nssaiavailability_subscription.go index 8e65ebe..b08e2d9 100644 --- a/internal/sbi/processor/nssaiavailability_subscription.go +++ b/internal/sbi/processor/nssaiavailability_subscription.go @@ -36,7 +36,7 @@ func getUnusedSubscriptionID() (string, error) { if idx == math.MaxUint32 { return "", fmt.Errorf("No available subscription ID") } - idx = idx + 1 + idx++ } else { break } diff --git a/internal/util/router_auth_check_test.go b/internal/util/router_auth_check_test.go index 0d6ec07..e0ebc1e 100644 --- a/internal/util/router_auth_check_test.go +++ b/internal/util/router_auth_check_test.go @@ -1,4 +1,4 @@ -package util +package util_test import ( "net/http" @@ -8,6 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/pkg/errors" + "github.com/free5gc/nssf/internal/util" "github.com/free5gc/openapi/models" ) @@ -85,7 +86,7 @@ func TestRouterAuthorizationCheck_Check(t *testing.T) { var testService models.ServiceName = "testService" - rac := NewRouterAuthorizationCheck(testService) + rac := util.NewRouterAuthorizationCheck(testService) rac.Check(c, newMockNSSFContext()) if w.Code != tt.want.statusCode { t.Errorf("StatusCode should be %d, but got %d", tt.want.statusCode, w.Code) From 277815f83a57cb5d3366bf9788560a957c329239 Mon Sep 17 00:00:00 2001 From: newb1er Date: Tue, 4 Jun 2024 12:45:16 +0800 Subject: [PATCH 27/32] fix: instantiate processor in `NewApp()` --- internal/sbi/server.go | 14 ++++++++++---- pkg/service/init.go | 3 +++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/internal/sbi/server.go b/internal/sbi/server.go index 21cf2f1..88c67d3 100644 --- a/internal/sbi/server.go +++ b/internal/sbi/server.go @@ -19,18 +19,24 @@ import ( logger_util "github.com/free5gc/util/logger" ) -type Server struct { +type nssfApp interface { app.NssfApp + Processor() *processor.Processor +} + +type Server struct { + nssfApp + httpServer *http.Server router *gin.Engine processor *processor.Processor } -func NewServer(nssf app.NssfApp, tlsKeyLogPath string) *Server { +func NewServer(nssf nssfApp, tlsKeyLogPath string) *Server { s := &Server{ - NssfApp: nssf, - processor: processor.NewProcessor(nssf), + nssfApp: nssf, + processor: nssf.Processor(), } s.router = newRouter(s) diff --git a/pkg/service/init.go b/pkg/service/init.go index 724da34..7a298d4 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -45,6 +45,9 @@ func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) + processor := processor.NewProcessor(nssf) + nssf.processor = processor + consumer := consumer.NewConsumer(nssf) nssf.consumer = consumer From d1e1d5f3e955a3065e5a64774321e5d9ec8a0366 Mon Sep 17 00:00:00 2001 From: newb1er Date: Tue, 11 Jun 2024 14:19:43 +0800 Subject: [PATCH 28/32] chore: add comment --- internal/sbi/consumer/nrf_service.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/sbi/consumer/nrf_service.go b/internal/sbi/consumer/nrf_service.go index 8700df9..987be87 100644 --- a/internal/sbi/consumer/nrf_service.go +++ b/internal/sbi/consumer/nrf_service.go @@ -22,6 +22,7 @@ import ( type NrfService struct { nrfNfMgmtClient *Nnrf_NFManagement.APIClient + // NOTE: No mutex needed. One connection at a time. } func (ns *NrfService) buildNFProfile(context *nssf_context.NSSFContext) (profile models.NfProfile, err error) { From 5827322b06d6be9eba504746d473b893dc250e6c Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 12 Jun 2024 15:11:08 +0800 Subject: [PATCH 29/32] chore: fix typo --- internal/sbi/api_nsselection.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/sbi/api_nsselection.go b/internal/sbi/api_nsselection.go index 43e36c1..2d3fd51 100644 --- a/internal/sbi/api_nsselection.go +++ b/internal/sbi/api_nsselection.go @@ -12,7 +12,7 @@ import ( func (s *Server) getNsSelectionRoutes() []Route { return []Route{ { - "Helth Check", + "Health Check", strings.ToUpper("Get"), "/", func(ctx *gin.Context) { From d04c2e4d0cb321db79983f1ddf3d546fb6e970d3 Mon Sep 17 00:00:00 2001 From: newb1er Date: Fri, 14 Jun 2024 21:17:37 +0800 Subject: [PATCH 30/32] refactor: create context before `NewApp` --- cmd/main.go | 14 +++++++++++++- pkg/service/init.go | 25 ++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 22de773..67efd6a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,9 +1,12 @@ package main import ( + "context" "os" + "os/signal" "path/filepath" "runtime/debug" + "syscall" "github.com/urfave/cli" @@ -56,7 +59,16 @@ func action(cliCtx *cli.Context) error { } factory.NssfConfig = cfg - nssf, err := service.NewApp(cfg, tlsKeyLogPath) + ctx, cancel := context.WithCancel(context.Background()) + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + go func() { + <-sigCh // Wait for interrupt signal to gracefully shutdown + cancel() // Notify each goroutine and wait them stopped + }() + + nssf, err := service.NewApp(ctx, cfg, tlsKeyLogPath) if err != nil { return err } diff --git a/pkg/service/init.go b/pkg/service/init.go index 7a298d4..8518af8 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -9,10 +9,8 @@ import ( "fmt" "io" "os" - "os/signal" "runtime/debug" "sync" - "syscall" "github.com/sirupsen/logrus" @@ -29,6 +27,7 @@ type NssfApp struct { cfg *factory.Config nssfCtx *nssf_context.NSSFContext + ctx context.Context wg sync.WaitGroup sbiServer *sbi.Server processor *processor.Processor @@ -37,10 +36,15 @@ type NssfApp struct { var _ app.NssfApp = &NssfApp{} -func NewApp(cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { +func NewApp(ctx context.Context, cfg *factory.Config, tlsKeyLogPath string) (*NssfApp, error) { nssf_context.InitNssfContext() - nssf := &NssfApp{cfg: cfg, wg: sync.WaitGroup{}, nssfCtx: nssf_context.GetSelf()} + nssf := &NssfApp{ + cfg: cfg, + ctx: ctx, + wg: sync.WaitGroup{}, + nssfCtx: nssf_context.GetSelf(), + } nssf.SetLogEnable(cfg.GetLogEnable()) nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) @@ -139,16 +143,7 @@ func (a *NssfApp) deregisterFromNrf() { } func (a *NssfApp) Start() { - ctx, cancel := context.WithCancel(context.Background()) - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) - - go func() { - <-sigCh // Wait for interrupt signal to gracefully shutdown - cancel() // Notify each goroutine and wait them stopped - }() - - err := a.registerToNrf(ctx) + err := a.registerToNrf(a.ctx) if err != nil { logger.MainLog.Errorf("register to NRF failed: %+v", err) } else { @@ -164,7 +159,7 @@ func (a *NssfApp) Start() { }() a.sbiServer.Run(&a.wg) - go a.listenShutdown(ctx) + go a.listenShutdown(a.ctx) } func (a *NssfApp) listenShutdown(ctx context.Context) { From 0e29e658da55334072fc856b56eb8dcf279610ea Mon Sep 17 00:00:00 2001 From: newb1er Date: Fri, 14 Jun 2024 22:55:19 +0800 Subject: [PATCH 31/32] fix(nsselection): if `tai` and `homePlmnId` are both missing, return 400 --- .../nsselection_network_slice_information.go | 23 +++++++++++++++++++ internal/util/util.go | 1 + 2 files changed, 24 insertions(+) diff --git a/internal/sbi/processor/nsselection_network_slice_information.go b/internal/sbi/processor/nsselection_network_slice_information.go index 39adc72..5ebb584 100644 --- a/internal/sbi/processor/nsselection_network_slice_information.go +++ b/internal/sbi/processor/nsselection_network_slice_information.go @@ -129,6 +129,26 @@ func (p *Processor) NSSelectionSliceInformationGet(c *gin.Context, query url.Val return } + if param.Tai == nil && param.HomePlmnId == nil { + problemDetails = &models.ProblemDetails{ + Title: util.MANDATORY_IE_MISSING, + Status: http.StatusBadRequest, + Cause: "MANDATORY_IE_MISSING", + Detail: "Either `tai` or `home-plmn-id` should be provided", + InvalidParams: []models.InvalidParam{ + { + Param: "tai", + }, + { + Param: "home-plmn-id", + }, + }, + } + + util.GinProblemJson(c, problemDetails) + return + } + if param.SliceInfoRequestForRegistration != nil { // Network slice information is requested during the Registration procedure status, response, problemDetails = nsselectionForRegistration(param) @@ -622,6 +642,7 @@ func nsselectionForPduSession(param plugin.NsselectionQueryParameter) ( // Return ProblemDetails indicating S-NSSAI is not supported // TODO: Based on TS 23.501 V15.2.0, if the Requested NSSAI includes an S-NSSAI that is not valid in the // Serving PLMN, the NSSF may derive the Configured NSSAI for Serving PLMN + problemDetails := &models.ProblemDetails{ Title: util.UNSUPPORTED_RESOURCE, Status: http.StatusForbidden, @@ -692,5 +713,7 @@ func nsselectionForPduSession(param plugin.NsselectionQueryParameter) ( *authorizedNetworkSliceInfo.NsiInformation = nsiInformation } + logger.NsselLog.Infof("authorizedNetworkSliceInfo: %+v", authorizedNetworkSliceInfo) + return http.StatusOK, authorizedNetworkSliceInfo, nil } diff --git a/internal/util/util.go b/internal/util/util.go index 5591f75..0f76e8f 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -19,6 +19,7 @@ import ( const ( INTERNAL_ERROR = "Internal server error" INVALID_REQUEST = "Invalid request message framing" + MANDATORY_IE_MISSING = "Mandatory IEs are missing" MALFORMED_REQUEST = "Malformed request syntax" UNAUTHORIZED_CONSUMER = "Unauthorized NF service consumer" UNSUPPORTED_RESOURCE = "Unsupported request resources" From 201cb7360a7756e5db99dc03393b4b8e3c0bd602 Mon Sep 17 00:00:00 2001 From: newb1er Date: Tue, 25 Jun 2024 11:58:50 +0800 Subject: [PATCH 32/32] fix: prevent `app.Terminate()` being called twice --- cmd/main.go | 1 - pkg/service/init.go | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 67efd6a..2d9c89b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -75,7 +75,6 @@ func action(cliCtx *cli.Context) error { NSSF = nssf nssf.Start() - nssf.Wait() return nil } diff --git a/pkg/service/init.go b/pkg/service/init.go index 8518af8..67313df 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -28,6 +28,7 @@ type NssfApp struct { nssfCtx *nssf_context.NSSFContext ctx context.Context + cancel context.CancelFunc wg sync.WaitGroup sbiServer *sbi.Server processor *processor.Processor @@ -41,7 +42,6 @@ func NewApp(ctx context.Context, cfg *factory.Config, tlsKeyLogPath string) (*Ns nssf := &NssfApp{ cfg: cfg, - ctx: ctx, wg: sync.WaitGroup{}, nssfCtx: nssf_context.GetSelf(), } @@ -49,6 +49,8 @@ func NewApp(ctx context.Context, cfg *factory.Config, tlsKeyLogPath string) (*Ns nssf.SetLogLevel(cfg.GetLogLevel()) nssf.SetReportCaller(cfg.GetLogReportCaller()) + nssf.ctx, nssf.cancel = context.WithCancel(ctx) + processor := processor.NewProcessor(nssf) nssf.processor = processor @@ -159,19 +161,24 @@ func (a *NssfApp) Start() { }() a.sbiServer.Run(&a.wg) + go a.listenShutdown(a.ctx) + a.Wait() } func (a *NssfApp) listenShutdown(ctx context.Context) { <-ctx.Done() - a.Terminate() + a.terminateProcedure() } func (a *NssfApp) Terminate() { + a.cancel() +} + +func (a *NssfApp) terminateProcedure() { logger.MainLog.Infof("Terminating NSSF...") a.deregisterFromNrf() a.sbiServer.Shutdown() - a.Wait() } func (a *NssfApp) Wait() {