mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
merge amqp module into infrastructure-component module to avoid circular deps, add TODOs in IC endpoints
This commit is contained in:
parent
4922d8c4ce
commit
f9cef090d4
7 changed files with 186 additions and 207 deletions
|
@ -1,85 +0,0 @@
|
|||
/** AMQP package, endpoints.
|
||||
*
|
||||
* @author Sonja Happ <sonja.happ@eonerc.rwth-aachen.de>
|
||||
* @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 <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
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.",
|
||||
})
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*********************************************************************************/
|
||||
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
|
||||
}
|
|
@ -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.",
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
4
start.go
4
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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue