diff --git a/backend/webui_service/webui_init.go b/backend/webui_service/webui_init.go index 25d6f82..866148b 100644 --- a/backend/webui_service/webui_init.go +++ b/backend/webui_service/webui_init.go @@ -139,7 +139,7 @@ func setupAuthenticationFeature(subconfig_router *gin.Engine) { dbadapter.ConnectMongo(mongodb.WebuiDBUrl, mongodb.WebuiDBName, &dbadapter.WebuiDBClient) resp, err := dbadapter.WebuiDBClient.CreateIndex(configmodels.UserAccountDataColl, "username") if !resp || err != nil { - logger.InitLog.Errorf("error initializing webuiDB %v", err) + logger.InitLog.Errorf("error creating userAccount index in webuiDB %v", err) } configapi.AddUserAccountService(subconfig_router, jwtSecret) auth.AddAuthenticationService(subconfig_router, jwtSecret) @@ -157,6 +157,15 @@ func (webui *WEBUI) Start() { dbadapter.ConnectMongo(mongodb.AuthUrl, mongodb.AuthKeysDbName, &dbadapter.AuthDBClient) } + resp, err := dbadapter.CommonDBClient.CreateIndex(configmodels.GnbDataColl, "name") + if !resp || err != nil { + logger.InitLog.Errorf("error creating gNB index in commonDB %v", err) + } + resp, err = dbadapter.CommonDBClient.CreateIndex(configmodels.UpfDataColl, "hostname") + if !resp || err != nil { + logger.InitLog.Errorf("error creating UPF index in commonDB %v", err) + } + logger.InitLog.Infoln("WebUI server started") /* First HTTP Server running at port to receive Config from ROC */ diff --git a/configapi/api_default.go b/configapi/api_default.go index f3f8e2c..5594837 100644 --- a/configapi/api_default.go +++ b/configapi/api_default.go @@ -240,7 +240,7 @@ func NetworkSliceSliceNameDelete(c *gin.Context) { // @Failure 401 {object} nil "Authorization failed" // @Failure 403 {object} nil "Forbidden" // @Failure 500 {object} nil "Error creating network slice" -// @Router /config/v1/network-slice/{sliceName [post] +// @Router /config/v1/network-slice/{sliceName} [post] func NetworkSliceSliceNamePost(c *gin.Context) { logger.ConfigLog.Debugf("Received NetworkSliceSliceNamePost ") if ret := NetworkSlicePostHandler(c, configmodels.Post_op); ret { diff --git a/configapi/api_inventory.go b/configapi/api_inventory.go index e92f437..35e23cb 100644 --- a/configapi/api_inventory.go +++ b/configapi/api_inventory.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "strings" "github.com/gin-gonic/gin" @@ -16,11 +17,6 @@ import ( "go.mongodb.org/mongo-driver/bson" ) -const ( - gnbDataColl = "webconsoleData.snapshots.gnbData" - upfDataColl = "webconsoleData.snapshots.upfData" -) - func setInventoryCorsHeader(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") @@ -45,7 +41,7 @@ func GetGnbs(c *gin.Context) { var gnbs []*configmodels.Gnb gnbs = make([]*configmodels.Gnb, 0) - rawGnbs, errGetMany := dbadapter.CommonDBClient.RestfulAPIGetMany(gnbDataColl, bson.M{}) + rawGnbs, errGetMany := dbadapter.CommonDBClient.RestfulAPIGetMany(configmodels.GnbDataColl, bson.M{}) if errGetMany != nil { logger.DbLog.Errorln(errGetMany) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve gNBs"}) @@ -177,7 +173,7 @@ func GetUpfs(c *gin.Context) { var upfs []*configmodels.Upf upfs = make([]*configmodels.Upf, 0) - rawUpfs, errGetMany := dbadapter.CommonDBClient.RestfulAPIGetMany(upfDataColl, bson.M{}) + rawUpfs, errGetMany := dbadapter.CommonDBClient.RestfulAPIGetMany(configmodels.UpfDataColl, bson.M{}) if errGetMany != nil { logger.DbLog.Errorln(errGetMany) c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve UPFs"}) @@ -200,21 +196,47 @@ func GetUpfs(c *gin.Context) { // @Description Create a new UPF // @Tags UPFs // @Produce json -// @Param upf-hostname path string true "Name of the UPF" -// @Param port body configmodels.PostUpfRequest true "Port of the UPF" +// @Param upf body configmodels.PostUpfRequest true "Hostname and port of the UPF to create" // @Security BearerAuth -// @Success 200 {object} nil "UPF created" -// @Failure 400 {object} nil "Failed to create the UPF" +// @Success 201 {object} nil "UPF successfully created" +// @Failure 400 {object} nil "Bad request" // @Failure 401 {object} nil "Authorization failed" // @Failure 403 {object} nil "Forbidden" -// @Router /config/v1/inventory/upf/{upf-hostname} [post] +// @Failure 500 {object} nil "Error creating UPF" +// @Router /config/v1/inventory/upf/ [post] func PostUpf(c *gin.Context) { - setInventoryCorsHeader(c) - if err := handlePostUpf(c); err == nil { - c.JSON(http.StatusOK, gin.H{}) - } else { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + logger.WebUILog.Infoln("received POST UPF") + var postUpfParams configmodels.PostUpfRequest + err := c.ShouldBindJSON(&postUpfParams) + if err != nil { + logger.ConfigLog.Errorln("invalid UPF POST input parameters. Error:", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON format"}) + return + } + if postUpfParams.Hostname == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "UPF hostname must be provided"}) + return + } + if _, err := strconv.Atoi(postUpfParams.Port); err != nil { + errorMessage := "UPF port cannot be converted to integer or it was not provided" + logger.WebUILog.Errorln(errorMessage) + c.JSON(http.StatusBadRequest, gin.H{"error": errorMessage}) + return } + filter := bson.M{"hostname": postUpfParams.Hostname} + upfDataBson := configmodels.ToBsonM(postUpfParams) + err = dbadapter.CommonDBClient.RestfulAPIPostMany(configmodels.UpfDataColl, filter, []interface{}{upfDataBson}) + if err != nil { + if strings.Contains(err.Error(), "E11000") { + logger.DbLog.Errorln("Duplicate hostname found:", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "UPF already exists"}) + return + } + logger.DbLog.Errorln(err.Error()) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create UPF"}) + return + } + c.JSON(http.StatusCreated, gin.H{}) } // DeleteUpf godoc @@ -238,42 +260,6 @@ func DeleteUpf(c *gin.Context) { } } -func handlePostUpf(c *gin.Context) error { - upfHostname, exists := c.Params.Get("upf-hostname") - if !exists { - errorMessage := "post UPF request is missing upf-hostname" - logger.ConfigLog.Errorln(errorMessage) - return fmt.Errorf("%s", errorMessage) - } - logger.ConfigLog.Infof("received UPF %v", upfHostname) - if !strings.HasPrefix(c.GetHeader("Content-Type"), "application/json") { - return fmt.Errorf("invalid header") - } - var postUpfRequest configmodels.PostUpfRequest - err := c.ShouldBindJSON(&postUpfRequest) - if err != nil { - logger.ConfigLog.Errorf("err %v", err) - return fmt.Errorf("invalid JSON format") - } - if postUpfRequest.Port == "" { - errorMessage := "post UPF request body is missing port" - logger.ConfigLog.Errorln(errorMessage) - return fmt.Errorf("%s", errorMessage) - } - postUpf := configmodels.Upf{ - Hostname: upfHostname, - Port: postUpfRequest.Port, - } - msg := configmodels.ConfigMessage{ - MsgType: configmodels.Inventory, - MsgMethod: configmodels.Post_op, - Upf: &postUpf, - } - configChannel <- &msg - logger.ConfigLog.Infof("successfully added UPF [%v] to config channel", upfHostname) - return nil -} - func handleDeleteUpf(c *gin.Context) error { upfHostname, exists := c.Params.Get("upf-hostname") if !exists { @@ -291,3 +277,90 @@ func handleDeleteUpf(c *gin.Context) error { logger.ConfigLog.Infof("successfully added UPF [%v] with delete_op to config channel", upfHostname) return nil } + +// PutUpf godoc +// +// @Description Create or update a UPF +// @Tags UPFs +// @Produce json +// @Param upf-hostname path string true "Name of the UPF to update" +// @Param port body configmodels.PutUpfRequest true "Port of the UPF to update" +// @Security BearerAuth +// @Success 200 {object} nil "UPF successfully updated" +// @Success 201 {object} nil "UPF successfully created" +// @Failure 400 {object} nil "Bad request" +// @Failure 401 {object} nil "Authorization failed" +// @Failure 403 {object} nil "Forbidden" +// @Failure 500 {object} nil "Error updating UPF" +// @Router /config/v1/inventory/upf/{upf-hostname} [put] +func PutUpf(c *gin.Context) { + logger.WebUILog.Infoln("received PUT UPF") + hostname := c.Param("upf-hostname") + var putUpfParams configmodels.PutUpfRequest + err := c.ShouldBindJSON(&putUpfParams) + if err != nil { + logger.WebUILog.Errorln("invalid UPF PUT input parameters", hostname, "Error:", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON format"}) + return + } + if _, err := strconv.Atoi(putUpfParams.Port); err != nil { + errorMessage := "UPF port cannot be converted to integer or it was not provided" + logger.WebUILog.Errorln(errorMessage) + c.JSON(http.StatusBadRequest, gin.H{"error": errorMessage}) + return + } + filter := bson.M{"hostname": hostname} + putUpf := configmodels.Upf{ + Hostname: hostname, + Port: putUpfParams.Port, + } + upfDataBson := configmodels.ToBsonM(putUpf) + existed, errPut := dbadapter.CommonDBClient.RestfulAPIPutOne(configmodels.UpfDataColl, filter, upfDataBson) + if errPut != nil { + logger.DbLog.Errorln("failed to PUT UPF:", hostname, "Error:", errPut) + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create or update UPF"}) + return + } + patchJSON := []byte(fmt.Sprintf(`[ + { + "op": "replace", + "path": "/site-info/upf", + "value": { + "upf-name": "%s", + "upf-port": "%s" + } + } + ]`, putUpf.Hostname, putUpf.Port)) + updateNSErr := updateUpfInNetworkSlices(putUpf.Hostname, patchJSON) + if updateNSErr != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create or update Network Slices"}) + return + } + if existed { + c.JSON(http.StatusOK, gin.H{}) + return + } + c.JSON(http.StatusCreated, gin.H{}) +} + +func updateUpfInNetworkSlices(hostname string, patchJSON []byte) error { + filterByUpf := bson.M{"site-info.upf.upf-name": hostname} + rawNetworkSlices, errGetMany := dbadapter.CommonDBClient.RestfulAPIGetMany(sliceDataColl, filterByUpf) + if errGetMany != nil { + logger.DbLog.Errorln("failed to fetch network slices:", errGetMany) + return errGetMany + } + for _, rawNetworkSlice := range rawNetworkSlices { + sliceName, ok := rawNetworkSlice["slice-name"].(string) + if !ok { + logger.ConfigLog.Warnln("invalid slice-name in network slice:", rawNetworkSlice) + continue + } + filterBySliceName := bson.M{"slice-name": sliceName} + err := dbadapter.CommonDBClient.RestfulAPIJSONPatch(sliceDataColl, filterBySliceName, patchJSON) + if err != nil { + logger.DbLog.Warnln("failed to update network slice:", sliceName, "Error:", err) + } + } + return nil +} diff --git a/configapi/api_inventory_test.go b/configapi/api_inventory_test.go index f3e5ce6..490bfa0 100644 --- a/configapi/api_inventory_test.go +++ b/configapi/api_inventory_test.go @@ -5,6 +5,7 @@ package configapi import ( "encoding/json" + "errors" "net/http" "net/http/httptest" "strings" @@ -96,6 +97,22 @@ func (m *MockMongoClientManyUpfs) RestfulAPIGetMany(coll string, filter bson.M) return results, nil } +func (db *MockMongoClientDBError) RestfulAPIPostMany(collName string, filter bson.M, postDataArray []interface{}) error { + return errors.New("DB error") +} + +func (db *MockMongoClientEmptyDB) RestfulAPIPutOne(collName string, filter bson.M, postData map[string]interface{}) (bool, error) { + return false, nil +} + +func (db *MockMongoClientOneUpf) RestfulAPIPutOne(collName string, filter bson.M, postData map[string]interface{}) (bool, error) { + return true, nil +} + +func (db *MockMongoClientDBError) RestfulAPIPutOne(collName string, filter bson.M, postData map[string]interface{}) (bool, error) { + return false, errors.New("DB error") +} + func TestInventoryGetHandlers(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.Default() @@ -186,7 +203,7 @@ func TestInventoryGetHandlers(t *testing.T) { } } -func TestInventoryPostHandlers_Failure(t *testing.T) { +func TestGnbPostHandler_Failure(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.Default() AddConfigV1Service(router) @@ -219,27 +236,6 @@ func TestInventoryPostHandlers_Failure(t *testing.T) { header: "application", expectedBody: `{"error":"invalid header"}`, }, - { - name: "Port is not a string", - route: "/config/v1/inventory/upf/upf1", - inputData: `{"port": 1234}`, - header: "application/json", - expectedBody: `{"error":"invalid JSON format"}`, - }, - { - name: "Missing port", - route: "/config/v1/inventory/upf/upf1", - inputData: `{"some_param": "123"}`, - header: "application/json", - expectedBody: `{"error":"post UPF request body is missing port"}`, - }, - { - name: "UpfInvalidHeader", - route: "/config/v1/inventory/upf/upf1", - inputData: `{"port": "123"}`, - header: "application", - expectedBody: `{"error":"invalid header"}`, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -271,7 +267,7 @@ func TestInventoryPostHandlers_Failure(t *testing.T) { } } -func TestInventoryPostHandlers_Success(t *testing.T) { +func TestGnbPostHandler_Success(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.Default() AddConfigV1Service(router) @@ -295,19 +291,6 @@ func TestInventoryPostHandlers_Success(t *testing.T) { }, }, }, - { - name: "PostUpf", - route: "/config/v1/inventory/upf/upf1", - inputData: `{"port": "123"}`, - expectedMessage: configmodels.ConfigMessage{ - MsgType: configmodels.Inventory, - MsgMethod: configmodels.Post_op, - Upf: &configmodels.Upf{ - Hostname: "upf1", - Port: "123", - }, - }, - }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { @@ -355,14 +338,6 @@ func TestInventoryPostHandlers_Success(t *testing.T) { t.Errorf("expected gNB %+v, but got %+v", tc.expectedMessage.Gnb, msg.Gnb) } } - if tc.expectedMessage.Upf != nil { - if msg.Upf == nil { - t.Errorf("expected UPF %+v, but got nil", tc.expectedMessage.Upf) - } - if tc.expectedMessage.Upf.Hostname != msg.Upf.Hostname || tc.expectedMessage.Upf.Port != msg.Upf.Port { - t.Errorf("expected UPF %+v, but got %+v", tc.expectedMessage.Upf, msg.Upf) - } - } default: t.Error("expected message in configChannel, but none received") } @@ -370,6 +345,180 @@ func TestInventoryPostHandlers_Success(t *testing.T) { } } +func TestUpfPostHandler(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + AddConfigV1Service(router) + + testCases := []struct { + name string + route string + dbAdapter dbadapter.DBInterface + inputData string + expectedCode int + expectedBody string + }{ + { + name: "Create a new UPF success", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"hostname": "host", "port": "123"}`, + expectedCode: http.StatusCreated, + expectedBody: "{}", + }, + { + name: "Create an existing UPF expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientDuplicateCreation{}, + inputData: `{"hostname": "upf1", "port": "123"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF already exists"}`, + }, + { + name: "Port is not a string expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"hostname": "host", "port": 1234}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"invalid JSON format"}`, + }, + { + name: "Missing port expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"hostname": "host", "some_param": "123"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF port cannot be converted to integer or it was not provided"}`, + }, + { + name: "DB POST operation fails expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientDBError{}, + inputData: `{"hostname": "host", "port": "123"}`, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"error":"failed to create UPF"}`, + }, + { + name: "Port cannot be converted to int expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"hostname": "host", "port": "a"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF port cannot be converted to integer or it was not provided"}`, + }, + { + name: "Hostname not provided expects failure", + route: "/config/v1/inventory/upf", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"port": "a"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF hostname must be provided"}`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dbadapter.CommonDBClient = tc.dbAdapter + req, err := http.NewRequest(http.MethodPost, tc.route, strings.NewReader(tc.inputData)) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + + if tc.expectedCode != w.Code { + t.Errorf("Expected `%v`, got `%v`", tc.expectedCode, w.Code) + } + if tc.expectedBody != w.Body.String() { + t.Errorf("Expected `%v`, got `%v`", tc.expectedBody, w.Body.String()) + } + }) + } +} + +func TestUpfPutHandler(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + AddConfigV1Service(router) + + testCases := []struct { + name string + route string + dbAdapter dbadapter.DBInterface + inputData string + expectedCode int + expectedBody string + }{ + { + name: "Put a new UPF", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"port": "123"}`, + expectedCode: http.StatusCreated, + expectedBody: "{}", + }, + { + name: "Put an existing UPF", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientOneUpf{}, + inputData: `{"port": "123"}`, + expectedCode: http.StatusOK, + expectedBody: "{}", + }, + { + name: "Port is not a string", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"port": 1234}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"invalid JSON format"}`, + }, + { + name: "Missing port", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"some_param": "123"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF port cannot be converted to integer or it was not provided"}`, + }, + { + name: "DB PUT operation fails", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientDBError{}, + inputData: `{"port": "123"}`, + expectedCode: http.StatusInternalServerError, + expectedBody: `{"error":"failed to create or update UPF"}`, + }, + { + name: "Port cannot be converted to int", + route: "/config/v1/inventory/upf/upf1", + dbAdapter: &MockMongoClientEmptyDB{}, + inputData: `{"port": "a"}`, + expectedCode: http.StatusBadRequest, + expectedBody: `{"error":"UPF port cannot be converted to integer or it was not provided"}`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + dbadapter.CommonDBClient = tc.dbAdapter + req, err := http.NewRequest(http.MethodPut, tc.route, strings.NewReader(tc.inputData)) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + w := httptest.NewRecorder() + + router.ServeHTTP(w, req) + + if tc.expectedCode != w.Code { + t.Errorf("Expected `%v`, got `%v`", tc.expectedCode, w.Code) + } + if tc.expectedBody != w.Body.String() { + t.Errorf("Expected `%v`, got `%v`", tc.expectedBody, w.Body.String()) + } + }) + } +} + func TestInventoryDeleteHandlers_Success(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.Default() @@ -439,9 +588,6 @@ func TestInventoryDeleteHandlers_Success(t *testing.T) { if msg.Gnb != nil { t.Errorf("expected gNB nil, but got %+v", msg.Gnb) } - if msg.Upf != nil { - t.Errorf("expected UPF nil, but got %+v", msg.Upf) - } default: t.Error("expected message in configChannel, but none received") } diff --git a/configapi/routers.go b/configapi/routers.go index bb4082a..52fd588 100644 --- a/configapi/routers.go +++ b/configapi/routers.go @@ -180,7 +180,7 @@ var routes = Routes{ { "PostUpf", http.MethodPost, - "/inventory/upf/:upf-hostname", + "/inventory/upf", PostUpf, }, { @@ -189,4 +189,10 @@ var routes = Routes{ "/inventory/upf/:upf-hostname", DeleteUpf, }, + { + "PutUpf", + http.MethodPut, + "/inventory/upf/:upf-hostname", + PutUpf, + }, } diff --git a/configmodels/config_msg.go b/configmodels/config_msg.go index 3802348..fc1f7b5 100644 --- a/configmodels/config_msg.go +++ b/configmodels/config_msg.go @@ -27,7 +27,6 @@ type ConfigMessage struct { Slice *Slice AuthSubData *models.AuthenticationSubscription Gnb *Gnb - Upf *Upf DevGroupName string SliceName string Imsi string diff --git a/configmodels/model_inventory.go b/configmodels/model_inventory.go index 4e8e9a8..44ada97 100644 --- a/configmodels/model_inventory.go +++ b/configmodels/model_inventory.go @@ -3,6 +3,11 @@ package configmodels +const ( + GnbDataColl = "webconsoleData.snapshots.gnbData" + UpfDataColl = "webconsoleData.snapshots.upfData" +) + type Gnb struct { Name string `json:"name"` Tac string `json:"tac"` @@ -18,5 +23,10 @@ type Upf struct { } type PostUpfRequest struct { + Hostname string `json:"hostname"` + Port string `json:"port"` +} + +type PutUpfRequest struct { Port string `json:"port"` } diff --git a/docs/docs.go b/docs/docs.go index 156c3e1..de63535 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,6 +1,3 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2024 Canonical Ltd - // Package docs Code generated by swaggo/swag. DO NOT EDIT package docs @@ -728,7 +725,7 @@ const docTemplate = `{ } } }, - "/config/v1/inventory/upf/{upf-hostname}": { + "/config/v1/inventory/upf/": { "post": { "security": [ { @@ -742,36 +739,86 @@ const docTemplate = `{ "tags": [ "UPFs" ], + "parameters": [ + { + "description": "Hostname and port of the UPF to create", + "name": "upf", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/configmodels.PostUpfRequest" + } + } + ], + "responses": { + "201": { + "description": "UPF successfully created" + }, + "400": { + "description": "Bad request" + }, + "401": { + "description": "Authorization failed" + }, + "403": { + "description": "Forbidden" + }, + "500": { + "description": "Error creating UPF" + } + } + } + }, + "/config/v1/inventory/upf/{upf-hostname}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create or update a UPF", + "produces": [ + "application/json" + ], + "tags": [ + "UPFs" + ], "parameters": [ { "type": "string", - "description": "Name of the UPF", + "description": "Name of the UPF to update", "name": "upf-hostname", "in": "path", "required": true }, { - "description": "Port of the UPF", + "description": "Port of the UPF to update", "name": "port", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/configmodels.PostUpfRequest" + "$ref": "#/definitions/configmodels.PutUpfRequest" } } ], "responses": { "200": { - "description": "UPF created" + "description": "UPF successfully updated" + }, + "201": { + "description": "UPF successfully created" }, "400": { - "description": "Failed to create the UPF" + "description": "Bad request" }, "401": { "description": "Authorization failed" }, "403": { "description": "Forbidden" + }, + "500": { + "description": "Error updating UPF" } } }, @@ -849,14 +896,17 @@ const docTemplate = `{ } } }, - "/config/v1/network-slice/{sliceName": { - "post": { + "/config/v1/network-slice/{sliceName}": { + "get": { "security": [ { "BearerAuth": [] } ], - "description": "Create a new network slice", + "description": "Return the network slice", + "produces": [ + "application/json" + ], "tags": [ "Network Slices" ], @@ -867,23 +917,14 @@ const docTemplate = `{ "name": "sliceName", "in": "path", "required": true - }, - { - "description": " ", - "name": "content", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/configmodels.Slice" - } } ], "responses": { "200": { - "description": "Network slice created" - }, - "400": { - "description": "Invalid network slice content" + "description": "Network slice", + "schema": { + "$ref": "#/definitions/configmodels.Slice" + } }, "401": { "description": "Authorization failed" @@ -891,23 +932,21 @@ const docTemplate = `{ "403": { "description": "Forbidden" }, + "404": { + "description": "Network slices not found" + }, "500": { - "description": "Error creating network slice" + "description": "Error retrieving network slice" } } - } - }, - "/config/v1/network-slice/{sliceName}": { - "get": { + }, + "post": { "security": [ { "BearerAuth": [] } ], - "description": "Return the network slice", - "produces": [ - "application/json" - ], + "description": "Create a new network slice", "tags": [ "Network Slices" ], @@ -918,14 +957,23 @@ const docTemplate = `{ "name": "sliceName", "in": "path", "required": true + }, + { + "description": " ", + "name": "content", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/configmodels.Slice" + } } ], "responses": { "200": { - "description": "Network slice", - "schema": { - "$ref": "#/definitions/configmodels.Slice" - } + "description": "Network slice created" + }, + "400": { + "description": "Invalid network slice content" }, "401": { "description": "Authorization failed" @@ -933,11 +981,8 @@ const docTemplate = `{ "403": { "description": "Forbidden" }, - "404": { - "description": "Network slices not found" - }, "500": { - "description": "Error retrieving network slice" + "description": "Error creating network slice" } } }, @@ -1193,6 +1238,17 @@ const docTemplate = `{ } }, "configmodels.PostUpfRequest": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + }, + "port": { + "type": "string" + } + } + }, + "configmodels.PutUpfRequest": { "type": "object", "properties": { "port": { diff --git a/docs/docs.go.license b/docs/docs.go.license new file mode 100644 index 0000000..001eccf --- /dev/null +++ b/docs/docs.go.license @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2025 Canonical Ltd diff --git a/proto/server/configEvtHandler.go b/proto/server/configEvtHandler.go index 82cbe2d..f6903b9 100644 --- a/proto/server/configEvtHandler.go +++ b/proto/server/configEvtHandler.go @@ -102,11 +102,6 @@ func configHandler(configMsgChan chan *configmodels.ConfigMessage, configReceive handleGnbPost(configMsg.Gnb) } - if configMsg.Upf != nil { - logger.ConfigLog.Infof("received UPF [%v] configuration from config channel", configMsg.UpfHostname) - handleUpfPost(configMsg.Upf) - } - // loop through all clients and send this message to all clients if len(clientNFPool) == 0 { logger.ConfigLog.Infoln("no client available. No need to send config") @@ -264,17 +259,6 @@ func handleGnbDelete(gnbName string) { rwLock.Unlock() } -func handleUpfPost(upf *configmodels.Upf) { - rwLock.Lock() - filter := bson.M{"hostname": upf.Hostname} - upfDataBson := configmodels.ToBsonM(upf) - _, errPost := dbadapter.CommonDBClient.RestfulAPIPost(upfDataColl, filter, upfDataBson) - if errPost != nil { - logger.DbLog.Warnln(errPost) - } - rwLock.Unlock() -} - func handleUpfDelete(upfHostname string) { rwLock.Lock() filter := bson.M{"hostname": upfHostname} diff --git a/proto/server/configEvtHandler_test.go b/proto/server/configEvtHandler_test.go index 4cb8e26..836fce4 100644 --- a/proto/server/configEvtHandler_test.go +++ b/proto/server/configEvtHandler_test.go @@ -507,29 +507,3 @@ func TestPostGnb(t *testing.T) { t.Errorf("Expected port %v, got %v", newGnb.Tac, result["tac"]) } } - -func TestPostUpf(t *testing.T) { - upfHostname := "some-upf" - newUpf := configmodels.Upf{ - Hostname: upfHostname, - Port: "1233", - } - postData = make([]map[string]interface{}, 0) - dbadapter.CommonDBClient = &MockMongoPost{} - handleUpfPost(&newUpf) - - expected_collection := "webconsoleData.snapshots.upfData" - if postData[0]["coll"] != expected_collection { - t.Errorf("Expected collection %v, got %v", expected_collection, postData[0]["coll"]) - } - - expected_filter := bson.M{"hostname": upfHostname} - if !reflect.DeepEqual(postData[0]["filter"], expected_filter) { - t.Errorf("Expected filter %v, got %v", expected_filter, postData[0]["filter"]) - } - - var result map[string]interface{} = postData[0]["data"].(map[string]interface{}) - if result["port"] != newUpf.Port { - t.Errorf("Expected port %v, got %v", newUpf.Port, result["port"]) - } -}