diff --git a/amqp/amqp_endpoints.go b/amqp/amqp_endpoints.go deleted file mode 100644 index 8082859..0000000 --- a/amqp/amqp_endpoints.go +++ /dev/null @@ -1,85 +0,0 @@ -/** AMQP package, endpoints. -* -* @author Sonja Happ -* @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC -* @license GNU General Public License (version 3) -* -* VILLASweb-backend-go -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*********************************************************************************/ -package amqp - -import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" - "github.com/gin-gonic/gin" - "log" - "net/http" -) - -func RegisterAMQPEndpoint(r *gin.RouterGroup) { - r.POST("/:ICID/action", sendActionToIC) -} - -// sendActionToIC godoc -// @Summary Send an action to IC (only available if backend server is started with -amqp parameter) -// @ID sendActionToIC -// @Tags AMQP -// @Produce json -// @Param inputAction query string true "Action for IC" -// @Success 200 {object} docs.ResponseError "Action sent successfully" -// @Failure 400 {object} docs.ResponseError "Bad request" -// @Failure 404 {object} docs.ResponseError "Not found" -// @Failure 422 {object} docs.ResponseError "Unprocessable entity" -// @Failure 500 {object} docs.ResponseError "Internal server error" -// @Param ICID path int true "InfrastructureComponent ID" -// @Router /ic/{ICID}/action [post] -// @Security Bearer -func sendActionToIC(c *gin.Context) { - - ok, s := infrastructure_component.CheckPermissions(c, database.ModelInfrastructureComponentAction, database.Update, true) - if !ok { - return - } - - var actions []Action - err := c.BindJSON(&actions) - if err != nil { - helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error()) - return - } - - //now := time.Now() - log.Println("AMQP: Will attempt to send the following actions:", actions) - - for _, action := range actions { - /*if action.When == 0 { - action.When = float32(now.Unix()) - }*/ - action.UUID = new(string) - *action.UUID = s.UUID - err = SendActionAMQP(action) - if err != nil { - helper.InternalServerError(c, "Unable to send actions to IC: "+err.Error()) - return - } - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "OK.", - }) -} diff --git a/routes/healthz/healthz_endpoint.go b/routes/healthz/healthz_endpoint.go index 5d2276c..e9928ab 100644 --- a/routes/healthz/healthz_endpoint.go +++ b/routes/healthz/healthz_endpoint.go @@ -22,10 +22,10 @@ package healthz import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "log" "net/http" @@ -68,7 +68,7 @@ func getHealth(c *gin.Context) { } if len(url) != 0 { - err = amqp.CheckConnection() + err = infrastructure_component.CheckConnection() if err != nil { log.Println(err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ diff --git a/routes/healthz/healthz_test.go b/routes/healthz/healthz_test.go index 24e4a61..6bdada4 100644 --- a/routes/healthz/healthz_test.go +++ b/routes/healthz/healthz_test.go @@ -22,10 +22,10 @@ package healthz import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "log" @@ -78,7 +78,7 @@ func TestHealthz(t *testing.T) { amqpURI := "amqp://" + user + ":" + pass + "@" + host log.Println("AMQP URI is", amqpURI) - err = amqp.ConnectAMQP(amqpURI) + err = infrastructure_component.ConnectAMQP(amqpURI) assert.NoError(t, err) // test healthz endpoint for connected DB and AMQP client diff --git a/amqp/amqpclient.go b/routes/infrastructure-component/amqpclient.go similarity index 59% rename from amqp/amqpclient.go rename to routes/infrastructure-component/amqpclient.go index a5358e0..7bf89dd 100644 --- a/amqp/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -19,12 +19,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *********************************************************************************/ -package amqp +package infrastructure_component import ( "encoding/json" "fmt" - infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jinzhu/gorm" @@ -249,129 +248,19 @@ func processMessage(message amqp.Delivery) error { return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) } - var sToBeUpdated infrastructure_component.InfrastructureComponent + var sToBeUpdated InfrastructureComponent err = sToBeUpdated.ByUUID(ICUUID) if err == gorm.ErrRecordNotFound { // create new record - err = createNewIC(payload) + err = createNewICviaAMQP(payload) } else if err != nil { // database error err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) } else { - // update record - err = updateIC(payload, sToBeUpdated) + // update record based on payload + err = sToBeUpdated.updateICviaAMQP(payload) } return err } - -func createNewIC(payload ICUpdate) error { - - var newICReq infrastructure_component.AddICRequest - newICReq.InfrastructureComponent.UUID = payload.Properties.UUID - if payload.Properties.Name == nil || - payload.Properties.Category == nil || - payload.Properties.Type == nil { - // cannot create new IC because required information (name, type, and/or category missing) - return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") - } - newICReq.InfrastructureComponent.Name = *payload.Properties.Name - newICReq.InfrastructureComponent.Category = *payload.Properties.Category - newICReq.InfrastructureComponent.Type = *payload.Properties.Type - - // add optional params - if payload.State != nil { - newICReq.InfrastructureComponent.State = *payload.State - } else { - newICReq.InfrastructureComponent.State = "unknown" - } - if payload.Properties.WS_url != nil { - newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - newICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - newICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is created via AMQP - newICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the new IC - err := newICReq.Validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) - } - - // Create the new IC - newIC := newICReq.CreateIC() - - // save IC - err = newIC.Save() - if err != nil { - return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) - } - - return nil -} - -func updateIC(payload ICUpdate, sToBeUpdated infrastructure_component.InfrastructureComponent) error { - var updatedICReq infrastructure_component.UpdateICRequest - if payload.State != nil { - updatedICReq.InfrastructureComponent.State = *payload.State - } - if payload.Properties.Type != nil { - updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type - } - if payload.Properties.Category != nil { - updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category - } - if payload.Properties.Name != nil { - updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name - } - if payload.Properties.WS_url != nil { - updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} - updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is updated via AMQP - updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the updated IC - err := updatedICReq.Validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) - } - - // Create the updated IC from old IC - updatedIC := updatedICReq.UpdatedIC(sToBeUpdated) - - // Finally update the IC in the DB - err = sToBeUpdated.Update(updatedIC) - if err != nil { - return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", sToBeUpdated.Name, err) - } - - return err -} - -func newTrue() *bool { - b := true - return &b -} diff --git a/routes/infrastructure-component/ic_endpoints.go b/routes/infrastructure-component/ic_endpoints.go index afc3b32..0328ff7 100644 --- a/routes/infrastructure-component/ic_endpoints.go +++ b/routes/infrastructure-component/ic_endpoints.go @@ -24,6 +24,7 @@ package infrastructure_component import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/gin-gonic/gin" + "log" "net/http" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" @@ -38,6 +39,10 @@ func RegisterICEndpoints(r *gin.RouterGroup) { r.GET("/:ICID/configs", getConfigsOfIC) } +func RegisterAMQPEndpoint(r *gin.RouterGroup) { + r.POST("/:ICID/action", sendActionToIC) +} + // getICs godoc // @Summary Get all infrastructure components // @ID getICs @@ -96,6 +101,8 @@ func addIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Create the new IC from the request newIC := req.CreateIC() @@ -142,6 +149,8 @@ func updateIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Create the updatedIC from oldIC updatedIC := req.UpdatedIC(oldIC) @@ -196,6 +205,8 @@ func deleteIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Delete the IC err := s.delete() if !helper.DBError(c, err) { @@ -231,3 +242,53 @@ func getConfigsOfIC(c *gin.Context) { } } + +// sendActionToIC godoc +// @Summary Send an action to IC (only available if backend server is started with -amqp parameter) +// @ID sendActionToIC +// @Tags infrastructure-components +// @Produce json +// @Param inputAction query string true "Action for IC" +// @Success 200 {object} docs.ResponseError "Action sent successfully" +// @Failure 400 {object} docs.ResponseError "Bad request" +// @Failure 404 {object} docs.ResponseError "Not found" +// @Failure 422 {object} docs.ResponseError "Unprocessable entity" +// @Failure 500 {object} docs.ResponseError "Internal server error" +// @Param ICID path int true "InfrastructureComponent ID" +// @Router /ic/{ICID}/action [post] +// @Security Bearer +func sendActionToIC(c *gin.Context) { + + ok, s := CheckPermissions(c, database.ModelInfrastructureComponentAction, database.Update, true) + if !ok { + return + } + + var actions []Action + err := c.BindJSON(&actions) + if err != nil { + helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error()) + return + } + + //now := time.Now() + log.Println("AMQP: Will attempt to send the following actions:", actions) + + for _, action := range actions { + /*if action.When == 0 { + action.When = float32(now.Unix()) + }*/ + action.UUID = new(string) + *action.UUID = s.UUID + err = SendActionAMQP(action) + if err != nil { + helper.InternalServerError(c, "Unable to send actions to IC: "+err.Error()) + return + } + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "OK.", + }) +} diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index 62a2449..674fd8f 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -77,3 +77,117 @@ func (s *InfrastructureComponent) getConfigs() ([]database.ComponentConfiguratio err := db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error return configs, len(configs), err } + +func createNewICviaAMQP(payload ICUpdate) error { + + var newICReq AddICRequest + newICReq.InfrastructureComponent.UUID = payload.Properties.UUID + if payload.Properties.Name == nil || + payload.Properties.Category == nil || + payload.Properties.Type == nil { + // cannot create new IC because required information (name, type, and/or category missing) + return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") + } + newICReq.InfrastructureComponent.Name = *payload.Properties.Name + newICReq.InfrastructureComponent.Category = *payload.Properties.Category + newICReq.InfrastructureComponent.Type = *payload.Properties.Type + + // add optional params + if payload.State != nil { + newICReq.InfrastructureComponent.State = *payload.State + } else { + newICReq.InfrastructureComponent.State = "unknown" + } + // TODO check if state is "gone" and abort creation of IC in this case + + if payload.Properties.WS_url != nil { + newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + newICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is created via AMQP + newICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the new IC + err := newICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) + } + + // Create the new IC + newIC := newICReq.CreateIC() + + // save IC + err = newIC.Save() + if err != nil { + return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) + } + + return nil +} + +func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { + var updatedICReq UpdateICRequest + if payload.State != nil { + updatedICReq.InfrastructureComponent.State = *payload.State + // TODO check if state is "gone" and attempt to remove IC from DB if it still exists + // TODO if state is different from "gone", continue to update the IC + } + if payload.Properties.Type != nil { + updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type + } + if payload.Properties.Category != nil { + updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category + } + if payload.Properties.Name != nil { + updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name + } + if payload.Properties.WS_url != nil { + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is updated via AMQP + updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the updated IC + err := updatedICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) + } + + // Create the updated IC from old IC + updatedIC := updatedICReq.UpdatedIC(*s) + + // Finally update the IC in the DB + err = s.Update(updatedIC) + if err != nil { + return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", s.Name, err) + } + + return err +} + +func newTrue() *bool { + b := true + return &b +} diff --git a/start.go b/start.go index c229163..e222f90 100644 --- a/start.go +++ b/start.go @@ -23,12 +23,12 @@ package main import ( "fmt" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" apidocs "git.rwth-aachen.de/acs/public/villas/web-backend-go/doc/api" // doc/api folder is used by Swag CLI, you have to import it "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "log" ) @@ -104,7 +104,7 @@ func main() { if amqphost != "" { // create amqp URL based on username, password and host amqpurl := "amqp://" + amqpuser + ":" + amqppass + "@" + amqphost - err = amqp.StartAMQP(amqpurl, api) + err = infrastructure_component.StartAMQP(amqpurl, api) if err != nil { panic(err) }