Merge branch 'extend-amqp'

# Conflicts:
#	amqp/amqpclient.go
#	routes/register.go
#	routes/register_test.go
#	start.go
This commit is contained in:
Sonja Happ 2020-11-16 13:20:13 +01:00
commit d5ce60c71f
22 changed files with 1619 additions and 748 deletions

View file

@ -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.",
})
}

View file

@ -1,350 +0,0 @@
/** AMQP package, client.
*
* @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 (
"encoding/json"
"fmt"
"log"
"time"
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"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/streadway/amqp"
)
const VILLAS_EXCHANGE = "villas"
type AMQPclient struct {
connection *amqp.Connection
channel *amqp.Channel
replies <-chan amqp.Delivery
}
type Action struct {
Act string `json:"action"`
When float32 `json:"when"`
Parameters struct{} `json:"parameters"`
UUID *string `json:"uuid"`
//Model struct{} `json:"model"`
//Results struct{} `json:"results"`
}
type ICUpdate struct {
State *string `json:"state"`
Properties struct {
UUID string `json:"uuid"`
Name *string `json:"name"`
Category *string `json:"category"`
Type *string `json:"type"`
Location *string `json:"location"`
WS_url *string `json:"ws_url"`
API_url *string `json:"api_url"`
} `json:"properties"`
}
var client AMQPclient
func ConnectAMQP(uri string) error {
var err error
// connect to broker
client.connection, err = amqp.Dial(uri)
if err != nil {
return fmt.Errorf("AMQP: failed to connect to RabbitMQ broker %v, error: %v", uri, err)
}
// create channel
client.channel, err = client.connection.Channel()
if err != nil {
return fmt.Errorf("AMQP: failed to open a channel, error: %v", err)
}
// declare exchange
err = client.channel.ExchangeDeclare(VILLAS_EXCHANGE,
"headers",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the exchange, error: %v", err)
}
// add a queue for the ICs
ICQueue, err := client.channel.QueueDeclare("infrastructure_components",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the queue, error: %v", err)
}
err = client.channel.QueueBind(ICQueue.Name, "", VILLAS_EXCHANGE, false, nil)
if err != nil {
return fmt.Errorf("AMQP: failed to bind the queue, error: %v", err)
}
// consume deliveries
client.replies, err = client.channel.Consume(ICQueue.Name,
"",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to consume deliveries, error: %v", err)
}
// consuming queue
go func() {
for {
for message := range client.replies {
processMessage(message)
}
time.Sleep(2) // sleep for 2 sek
}
}()
log.Printf("AMQP: Waiting for messages... ")
return nil
}
func SendActionAMQP(action Action) error {
payload, err := json.Marshal(action)
if err != nil {
return err
}
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = CheckConnection()
if err != nil {
return err
}
log.Println("AMQP: Sending message", string(msg.Body))
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
return err
}
func PingAMQP() error {
log.Println("AMQP: sending ping command to all ICs")
var a Action
a.Act = "ping"
*a.UUID = ""
err := SendActionAMQP(a)
return err
}
func CheckConnection() error {
if client.connection != nil {
if client.connection.IsClosed() {
return fmt.Errorf("connection to broker is closed")
}
} else {
return fmt.Errorf("connection is nil")
}
return nil
}
func StartAMQP(AMQPurl string, api *gin.RouterGroup) error {
if AMQPurl != "" {
log.Println("Starting AMQP client")
err := ConnectAMQP(AMQPurl)
if err != nil {
return err
}
// register IC action endpoint only if AMQP client is used
RegisterAMQPEndpoint(api.Group("/ic"))
// Periodically call the Ping function to check which ICs are still there
ticker := time.NewTicker(10 * time.Second)
go func() {
for {
select {
case <-ticker.C:
//TODO Add a useful regular event here
/*
err = PingAMQP()
if err != nil {
log.Println("AMQP Error: ", err.Error())
}
*/
}
}
}()
log.Printf("Connected AMQP client to %s", AMQPurl)
}
return nil
}
func processMessage(message amqp.Delivery) {
log.Println("Processing AMQP message: ", string(message.Body))
var payload ICUpdate
err := json.Unmarshal(message.Body, &payload)
if err != nil {
log.Println("AMQP: Could not unmarshal message to JSON:", string(message.Body), "err: ", err)
return
}
ICUUID := payload.Properties.UUID
_, err = uuid.Parse(ICUUID)
if err != nil {
log.Printf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body))
return
}
var sToBeUpdated infrastructure_component.InfrastructureComponent
err = sToBeUpdated.ByUUID(ICUUID)
if err == gorm.ErrRecordNotFound {
// create new record
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)
log.Println("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category")
return
}
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.Host = *payload.Properties.WS_url
}
if payload.Properties.API_url != nil {
newICReq.InfrastructureComponent.APIHost = *payload.Properties.API_url
}
if payload.Properties.Location != nil {
newICReq.InfrastructureComponent.Properties = postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)}
}
// Validate the new IC
err = newICReq.Validate()
if err != nil {
log.Println("AMQP: Validation of new IC failed:", err)
return
}
// Create the new IC
newIC := newICReq.CreateIC()
// save IC
err = newIC.Save()
if err != nil {
log.Println("AMQP: Saving new IC to DB failed:", err)
return
}
log.Println("AMQP: Created IC ", newIC.Name)
} else if err != nil {
log.Println("AMQP: Database error for IC", ICUUID, " DB error message: ", err)
return
} else {
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.Host = *payload.Properties.WS_url
}
if payload.Properties.API_url != nil {
updatedICReq.InfrastructureComponent.APIHost = *payload.Properties.API_url
}
if payload.Properties.Location != nil {
updatedICReq.InfrastructureComponent.Properties = postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)}
}
// Validate the updated IC
if err = updatedICReq.Validate(); err != nil {
log.Println("AMQP: Validation of updated IC failed:", err)
return
}
// 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 {
log.Println("AMQP: Unable to update IC", sToBeUpdated.Name, "in DB: ", err)
return
}
log.Println("AMQP: Updated IC ", sToBeUpdated.Name)
}
}

View file

@ -23,6 +23,7 @@ package configuration
import (
"flag"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
"log"
"os"
@ -51,9 +52,9 @@ func InitConfig() error {
port = flag.String("port", "4000", "Port of the backend (default is 4000)")
baseHost = flag.String("base-host", "localhost:4000", "The host at which the backend is hosted (default: localhost)")
basePath = flag.String("base-path", "/api/v2", "The path at which the API routes are located (default /api/v2)")
adminUser = flag.String("admin-user", "", "Initial admin username")
adminPass = flag.String("admin-pass", "", "Initial admin password")
adminMail = flag.String("admin-mail", "", "Initial admin mail address")
adminUser = flag.String("admin-user", helper.User0.Username, "Initial admin username")
adminPass = flag.String("admin-pass", helper.StrPassword0, "Initial admin password")
adminMail = flag.String("admin-mail", helper.User0.Mail, "Initial admin mail address")
)
flag.Parse()

View file

@ -120,24 +120,28 @@ type InfrastructureComponent struct {
UUID string `json:"uuid" gorm:"not null"`
// Name of the IC
Name string `json:"name" gorm:"default:''"`
// Host if the IC
Host string `json:"host" gorm:"default:''"`
// Host of API for IC
APIHost string `json:"apihost" gorm:"default:''"`
// WebsocketURL if the IC
WebsocketURL string `json:"websocketurl" gorm:"default:''"`
// API URL of API for IC
APIURL string `json:"apiurl" gorm:"default:''"`
// Category of IC (simulator, gateway, database, etc.)
Category string `json:"category" gorm:"default:''"`
// Type of IC (RTDS, VILLASnode, RTDS, etc.)
Type string `json:"type" gorm:"default:''"`
// Uptime of the IC
Uptime int `json:"uptime" gorm:"default:0"`
Uptime float64 `json:"uptime" gorm:"default:0"`
// State of the IC
State string `json:"state" gorm:"default:''"`
// Time of last state update
StateUpdateAt string `json:"stateUpdateAt" gorm:"default:''"`
// Properties of IC as JSON string
Properties postgres.Jsonb `json:"properties"`
// Raw properties of IC as JSON string
RawProperties postgres.Jsonb `json:"rawProperties"`
// Location of the IC
Location string `json:"location" gorm:"default:''"`
// Description of the IC
Description string `json:"description" gorm:"default:''"`
// JSON scheme of start parameters for IC
StartParameterScheme postgres.Jsonb `json:"startparameterscheme"`
// Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)
ManagedExternally bool `json:"managedexternally" gorm:"default:false"`
// ComponentConfigurations in which the IC is used
ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ICID"`
}

View file

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at
// 2020-09-25 16:13:15.130920598 +0200 CEST m=+0.092357808
// 2020-11-11 16:32:47.799676915 +0100 CET m=+0.126448240
package docs
@ -1078,7 +1078,7 @@ var doc = `{
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/infrastructure_component.addICRequest"
"$ref": "#/definitions/infrastructure_component.AddICRequest"
}
}
],
@ -1198,7 +1198,7 @@ var doc = `{
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/infrastructure_component.updateICRequest"
"$ref": "#/definitions/infrastructure_component.UpdateICRequest"
}
},
{
@ -1310,7 +1310,7 @@ var doc = `{
"application/json"
],
"tags": [
"AMQP"
"infrastructure-components"
],
"summary": "Send an action to IC (only available if backend server is started with -amqp parameter)",
"operationId": "sendActionToIC",
@ -2977,31 +2977,35 @@ var doc = `{
"database.InfrastructureComponent": {
"type": "object",
"properties": {
"apihost": {
"description": "Host of API for IC",
"apiurl": {
"description": "API URL of API for IC",
"type": "string"
},
"category": {
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"host": {
"description": "Host if the IC",
"description": {
"description": "Description of the IC",
"type": "string"
},
"id": {
"type": "integer"
},
"location": {
"description": "Location of the IC",
"type": "string"
},
"managedexternally": {
"description": "Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)",
"type": "boolean"
},
"name": {
"description": "Name of the IC",
"type": "string"
},
"properties": {
"description": "Properties of IC as JSON string",
"type": "string"
},
"rawProperties": {
"description": "Raw properties of IC as JSON string",
"startparameterscheme": {
"description": "JSON scheme of start parameters for IC",
"type": "string"
},
"state": {
@ -3018,11 +3022,15 @@ var doc = `{
},
"uptime": {
"description": "Uptime of the IC",
"type": "integer"
"type": "number"
},
"uuid": {
"description": "UUID of the IC",
"type": "string"
},
"websocketurl": {
"description": "WebsocketURL if the IC",
"type": "string"
}
}
},
@ -3351,7 +3359,7 @@ var doc = `{
}
}
},
"infrastructure_component.addICRequest": {
"infrastructure_component.AddICRequest": {
"type": "object",
"properties": {
"ic": {
@ -3360,7 +3368,7 @@ var doc = `{
}
}
},
"infrastructure_component.updateICRequest": {
"infrastructure_component.UpdateICRequest": {
"type": "object",
"properties": {
"ic": {
@ -3373,24 +3381,30 @@ var doc = `{
"type": "object",
"required": [
"Category",
"ManagedExternally",
"Name",
"Type",
"UUID"
"Type"
],
"properties": {
"APIHost": {
"APIURL": {
"type": "string"
},
"Category": {
"type": "string"
},
"Host": {
"Description": {
"type": "string"
},
"Location": {
"type": "string"
},
"ManagedExternally": {
"type": "boolean"
},
"Name": {
"type": "string"
},
"Properties": {
"StartParameterScheme": {
"type": "string"
},
"State": {
@ -3401,25 +3415,34 @@ var doc = `{
},
"UUID": {
"type": "string"
},
"Uptime": {
"type": "number"
},
"WebsocketURL": {
"type": "string"
}
}
},
"infrastructure_component.validUpdatedIC": {
"type": "object",
"properties": {
"APIHost": {
"APIURL": {
"type": "string"
},
"Category": {
"type": "string"
},
"Host": {
"Description": {
"type": "string"
},
"Location": {
"type": "string"
},
"Name": {
"type": "string"
},
"Properties": {
"StartParameterScheme": {
"type": "string"
},
"State": {
@ -3430,6 +3453,12 @@ var doc = `{
},
"UUID": {
"type": "string"
},
"Uptime": {
"type": "number"
},
"WebsocketURL": {
"type": "string"
}
}
},
@ -3765,7 +3794,7 @@ type swaggerInfo struct {
var SwaggerInfo = swaggerInfo{
Version: "2.0",
Host: "",
BasePath: "http://localhost:4000/api/v2/",
BasePath: "/api/v2",
Schemes: []string{},
Title: "VILLASweb Backend API",
Description: "This is the [VILLASweb Backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) API v2.0.\nThis documentation is auto-generated based on the API documentation in the code. The tool [swag](https://github.com/swaggo/swag) is used to auto-generate API docs for the [gin-gonic](https://github.com/gin-gonic/gin) framework.\nAuthentication: Use the authenticate endpoint below to obtain a token for your user account, copy the token into to the value field of the dialog showing up for the green Authorize button below and confirm with Done.",

View file

@ -13,7 +13,7 @@
},
"version": "2.0"
},
"basePath": "http://localhost:4000/api/v2/",
"basePath": "/api/v2",
"paths": {
"/authenticate": {
"post": {
@ -1061,7 +1061,7 @@
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/infrastructure_component.addICRequest"
"$ref": "#/definitions/infrastructure_component.AddICRequest"
}
}
],
@ -1181,7 +1181,7 @@
"required": true,
"schema": {
"type": "object",
"$ref": "#/definitions/infrastructure_component.updateICRequest"
"$ref": "#/definitions/infrastructure_component.UpdateICRequest"
}
},
{
@ -1293,7 +1293,7 @@
"application/json"
],
"tags": [
"AMQP"
"infrastructure-components"
],
"summary": "Send an action to IC (only available if backend server is started with -amqp parameter)",
"operationId": "sendActionToIC",
@ -2960,31 +2960,35 @@
"database.InfrastructureComponent": {
"type": "object",
"properties": {
"apihost": {
"description": "Host of API for IC",
"apiurl": {
"description": "API URL of API for IC",
"type": "string"
},
"category": {
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"host": {
"description": "Host if the IC",
"description": {
"description": "Description of the IC",
"type": "string"
},
"id": {
"type": "integer"
},
"location": {
"description": "Location of the IC",
"type": "string"
},
"managedexternally": {
"description": "Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)",
"type": "boolean"
},
"name": {
"description": "Name of the IC",
"type": "string"
},
"properties": {
"description": "Properties of IC as JSON string",
"type": "string"
},
"rawProperties": {
"description": "Raw properties of IC as JSON string",
"startparameterscheme": {
"description": "JSON scheme of start parameters for IC",
"type": "string"
},
"state": {
@ -3001,11 +3005,15 @@
},
"uptime": {
"description": "Uptime of the IC",
"type": "integer"
"type": "number"
},
"uuid": {
"description": "UUID of the IC",
"type": "string"
},
"websocketurl": {
"description": "WebsocketURL if the IC",
"type": "string"
}
}
},
@ -3334,7 +3342,7 @@
}
}
},
"infrastructure_component.addICRequest": {
"infrastructure_component.AddICRequest": {
"type": "object",
"properties": {
"ic": {
@ -3343,7 +3351,7 @@
}
}
},
"infrastructure_component.updateICRequest": {
"infrastructure_component.UpdateICRequest": {
"type": "object",
"properties": {
"ic": {
@ -3356,24 +3364,30 @@
"type": "object",
"required": [
"Category",
"ManagedExternally",
"Name",
"Type",
"UUID"
"Type"
],
"properties": {
"APIHost": {
"APIURL": {
"type": "string"
},
"Category": {
"type": "string"
},
"Host": {
"Description": {
"type": "string"
},
"Location": {
"type": "string"
},
"ManagedExternally": {
"type": "boolean"
},
"Name": {
"type": "string"
},
"Properties": {
"StartParameterScheme": {
"type": "string"
},
"State": {
@ -3384,25 +3398,34 @@
},
"UUID": {
"type": "string"
},
"Uptime": {
"type": "number"
},
"WebsocketURL": {
"type": "string"
}
}
},
"infrastructure_component.validUpdatedIC": {
"type": "object",
"properties": {
"APIHost": {
"APIURL": {
"type": "string"
},
"Category": {
"type": "string"
},
"Host": {
"Description": {
"type": "string"
},
"Location": {
"type": "string"
},
"Name": {
"type": "string"
},
"Properties": {
"StartParameterScheme": {
"type": "string"
},
"State": {
@ -3413,6 +3436,12 @@
},
"UUID": {
"type": "string"
},
"Uptime": {
"type": "number"
},
"WebsocketURL": {
"type": "string"
}
}
},

View file

@ -1,4 +1,4 @@
basePath: http://localhost:4000/api/v2/
basePath: /api/v2
definitions:
component_configuration.addConfigRequest:
properties:
@ -146,25 +146,28 @@ definitions:
type: object
database.InfrastructureComponent:
properties:
apihost:
description: Host of API for IC
apiurl:
description: API URL of API for IC
type: string
category:
description: Category of IC (simulator, gateway, database, etc.)
type: string
host:
description: Host if the IC
description:
description: Description of the IC
type: string
id:
type: integer
location:
description: Location of the IC
type: string
managedexternally:
description: Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)
type: boolean
name:
description: Name of the IC
type: string
properties:
description: Properties of IC as JSON string
type: string
rawProperties:
description: Raw properties of IC as JSON string
startparameterscheme:
description: JSON scheme of start parameters for IC
type: string
state:
description: State of the IC
@ -177,10 +180,13 @@ definitions:
type: string
uptime:
description: Uptime of the IC
type: integer
type: number
uuid:
description: UUID of the IC
type: string
websocketurl:
description: WebsocketURL if the IC
type: string
type: object
database.Scenario:
properties:
@ -404,13 +410,13 @@ definitions:
$ref: '#/definitions/database.Widget'
type: array
type: object
infrastructure_component.addICRequest:
infrastructure_component.AddICRequest:
properties:
ic:
$ref: '#/definitions/infrastructure_component.validNewIC'
type: object
type: object
infrastructure_component.updateICRequest:
infrastructure_component.UpdateICRequest:
properties:
ic:
$ref: '#/definitions/infrastructure_component.validUpdatedIC'
@ -418,15 +424,19 @@ definitions:
type: object
infrastructure_component.validNewIC:
properties:
APIHost:
APIURL:
type: string
Category:
type: string
Host:
Description:
type: string
Location:
type: string
ManagedExternally:
type: boolean
Name:
type: string
Properties:
StartParameterScheme:
type: string
State:
type: string
@ -434,23 +444,29 @@ definitions:
type: string
UUID:
type: string
Uptime:
type: number
WebsocketURL:
type: string
required:
- Category
- ManagedExternally
- Name
- Type
- UUID
type: object
infrastructure_component.validUpdatedIC:
properties:
APIHost:
APIURL:
type: string
Category:
type: string
Host:
Description:
type: string
Location:
type: string
Name:
type: string
Properties:
StartParameterScheme:
type: string
State:
type: string
@ -458,6 +474,10 @@ definitions:
type: string
UUID:
type: string
Uptime:
type: number
WebsocketURL:
type: string
type: object
scenario.addScenarioRequest:
properties:
@ -1357,7 +1377,7 @@ paths:
name: inputIC
required: true
schema:
$ref: '#/definitions/infrastructure_component.addICRequest'
$ref: '#/definitions/infrastructure_component.AddICRequest'
type: object
produces:
- application/json
@ -1470,7 +1490,7 @@ paths:
name: inputIC
required: true
schema:
$ref: '#/definitions/infrastructure_component.updateICRequest'
$ref: '#/definitions/infrastructure_component.UpdateICRequest'
type: object
- description: InfrastructureComponent ID
in: path
@ -1547,7 +1567,7 @@ paths:
summary: Send an action to IC (only available if backend server is started with
-amqp parameter)
tags:
- AMQP
- infrastructure-components
/ic/{ICID}/configs:
get:
operationId: getConfigsOfIC

View file

@ -92,34 +92,38 @@ var NewUserC = UserRequest{
// Infrastructure components
var propertiesA = json.RawMessage(`{"location" : "ACSlab"}`)
var propertiesB = json.RawMessage(`{"location" : "ACSlab"}`)
var propertiesA = json.RawMessage(`{"prop1" : "a nice prop"}`)
var propertiesB = json.RawMessage(`{"prop1" : "not so nice"}`)
var ICA = database.InfrastructureComponent{
UUID: "4854af30-325f-44a5-ad59-b67b2597de68",
Host: "xxx.yyy.zzz.aaa",
Type: "DPsim",
Category: "Simulator",
Name: "Test DPsim Simulator",
Uptime: 0,
State: "running",
StateUpdateAt: time.Now().Format(time.RFC1123),
Properties: postgres.Jsonb{propertiesA},
RawProperties: postgres.Jsonb{propertiesA},
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
WebsocketURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api/v2",
Type: "villas-node",
Category: "gateway",
Name: "ACS Demo Signals",
Uptime: -1.0,
State: "idle",
Location: "k8s",
Description: "A signal generator for testing purposes",
//StateUpdateAt: time.Now().Format(time.RFC1123),
StartParameterScheme: postgres.Jsonb{propertiesA},
ManagedExternally: false,
}
var ICB = database.InfrastructureComponent{
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
Host: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
APIHost: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api",
Type: "VILLASnode Signal Generator",
Category: "Signal Generator",
Name: "ACS Demo Signals",
Uptime: 0,
State: "idle",
StateUpdateAt: time.Now().Format(time.RFC1123),
Properties: postgres.Jsonb{propertiesB},
RawProperties: postgres.Jsonb{propertiesB},
UUID: "4854af30-325f-44a5-ad59-b67b2597de68",
WebsocketURL: "xxx.yyy.zzz.aaa",
Type: "dpsim",
Category: "simulator",
Name: "Test DPsim Simulator",
Uptime: -1.0,
State: "running",
Location: "ACS Laboratory",
Description: "This is a test description",
//StateUpdateAt: time.Now().Format(time.RFC1123),
StartParameterScheme: postgres.Jsonb{propertiesB},
ManagedExternally: true,
}
// Scenarios
@ -350,20 +354,28 @@ func DBAddAdminUser(cfg *config.Config) error {
if len(users) == 0 {
fmt.Println("No admin user found in DB, adding default admin user.")
mode, err := cfg.String("mode")
name, err := cfg.String("admin.user")
if err != nil || name == "" {
if (err != nil || name == "") && mode != "test" {
name = "admin"
} else if mode == "test" {
name = User0.Username
}
pw, err := cfg.String("admin.pass")
if err != nil || pw == "" {
if (err != nil || pw == "") && mode != "test" {
pw = generatePassword(16)
fmt.Printf(" Generated admin password: %s\n", pw)
} else if mode == "test" {
pw = StrPassword0
}
mail, err := cfg.String("admin.mail")
if err == nil || mail == "" {
if (err == nil || mail == "") && mode != "test" {
mail = "admin@example.com"
} else if mode == "test" {
mail = User0.Mail
}
pwEnc, _ := bcrypt.GenerateFromPassword([]byte(pw), bcryptCost)

View file

@ -23,7 +23,6 @@ package component_configuration
import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
)
@ -61,8 +60,11 @@ func (m *ComponentConfiguration) addToScenario() error {
}
// associate IC with component configuration
var ic infrastructure_component.InfrastructureComponent
err = ic.ByID(m.ICID)
var ic database.InfrastructureComponent
err = db.Find(&ic, m.ICID).Error
if err != nil {
return err
}
err = db.Model(&ic).Association("ComponentConfigurations").Append(m).Error
if err != nil {
return err
@ -80,23 +82,24 @@ func (m *ComponentConfiguration) Update(modifiedConfig ComponentConfiguration) e
// check if IC has been updated
if m.ICID != modifiedConfig.ICID {
// update IC
var s infrastructure_component.InfrastructureComponent
var s_old infrastructure_component.InfrastructureComponent
err := s.ByID(modifiedConfig.ICID)
var ic database.InfrastructureComponent
var ic_old database.InfrastructureComponent
err := db.Find(&ic, modifiedConfig.ICID).Error
if err != nil {
return err
}
err = s_old.ByID(m.ICID)
err = db.Find(&ic_old, m.ICID).Error
if err != nil {
return err
}
// remove component configuration from old IC
err = db.Model(&s_old).Association("ComponentConfigurations").Delete(m).Error
err = db.Model(&ic_old).Association("ComponentConfigurations").Delete(m).Error
if err != nil {
return err
}
// add component configuration to new IC
err = db.Model(&s).Association("ComponentConfigurations").Append(m).Error
err = db.Model(&ic).Association("ComponentConfigurations").Append(m).Error
if err != nil {
return err
}
@ -121,9 +124,37 @@ func (m *ComponentConfiguration) delete() error {
return err
}
var ic database.InfrastructureComponent
err = db.Find(&ic, m.ICID).Error
if err != nil {
return err
}
// remove association between ComponentConfiguration and Scenario
// ComponentConfiguration itself is not deleted from DB, it remains as "dangling"
err = db.Model(&so).Association("ComponentConfigurations").Delete(m).Error
if err != nil {
return err
}
return err
// remove association between Infrastructure component and config
err = db.Model(&ic).Association("ComponentConfigurations").Delete(m).Error
if err != nil {
return err
}
// delete component configuration
err = db.Delete(m).Error
if err != nil {
return err
}
// if IC has state gone and there is no component configuration associated with it: delete IC
no_configs := db.Model(ic).Association("ComponentConfigurations").Count()
if no_configs == 0 && ic.State == "gone" {
err = db.Delete(ic).Error
return err
}
return nil
}

View file

@ -49,13 +49,16 @@ type ConfigRequest struct {
}
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
Host string `json:"host,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"`
ManagedExternally *bool `json:"managedexternally,omitempty"`
}
type ScenarioRequest struct {
@ -72,32 +75,44 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) {
// POST $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: &helper.ICA.ManagedExternally,
}
_, resp, _ := helper.TestEndpoint(router, token,
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})
if code != 200 || err != nil {
fmt.Println("Adding IC returned code", code, err, resp)
}
// Read newIC's ID from the response
newICID, _ := helper.GetResponseID(resp)
// POST a second IC to change to that IC during testing
newICB := ICRequest{
UUID: helper.ICB.UUID,
Host: helper.ICB.Host,
Type: helper.ICB.Type,
Name: helper.ICB.Name,
Category: helper.ICB.Category,
State: helper.ICB.State,
Properties: helper.ICB.Properties,
UUID: helper.ICB.UUID,
WebsocketURL: helper.ICB.WebsocketURL,
Type: helper.ICB.Type,
Name: helper.ICB.Name,
Category: helper.ICB.Category,
State: helper.ICB.State,
Location: helper.ICB.Location,
Description: helper.ICB.Description,
StartParameterScheme: helper.ICB.StartParameterScheme,
ManagedExternally: &helper.ICA.ManagedExternally,
}
_, resp, _ = helper.TestEndpoint(router, token,
code, resp, err = helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICB})
if code != 200 || err != nil {
fmt.Println("Adding IC returned code", code, err, resp)
}
// authenticate as normal user
token, _ = helper.AuthenticateForTest(router,
@ -109,8 +124,11 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) {
Running: helper.ScenarioA.Running,
StartParameters: helper.ScenarioA.StartParameters,
}
_, resp, _ = helper.TestEndpoint(router, token,
code, resp, err = helper.TestEndpoint(router, token,
"/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
if code != 200 || err != nil {
fmt.Println("Adding Scenario returned code", code, err, resp)
}
// Read newScenario's ID from the response
newScenarioID, _ := helper.GetResponseID(resp)

View file

@ -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{

View file

@ -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

View file

@ -0,0 +1,431 @@
/** AMQP package, client.
*
* @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 infrastructure_component
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/jinzhu/gorm"
"github.com/streadway/amqp"
"log"
"time"
)
const VILLAS_EXCHANGE = "villas"
type AMQPclient struct {
connection *amqp.Connection
channel *amqp.Channel
replies <-chan amqp.Delivery
}
type Action struct {
Act string `json:"action"`
When int64 `json:"when"`
Properties struct {
UUID *string `json:"uuid"`
Name *string `json:"name"`
Category *string `json:"category"`
Type *string `json:"type"`
Location *string `json:"location"`
WS_url *string `json:"ws_url"`
API_url *string `json:"api_url"`
Description *string `json:"description"`
} `json:"properties"`
}
type ICStatus struct {
UUID string `json:"uuid"`
State *string `json:"state"`
Name *string `json:"name"`
Category *string `json:"category"`
Type *string `json:"type"`
Location *string `json:"location"`
WS_url *string `json:"ws_url"`
API_url *string `json:"api_url"`
Description *string `json:"description"`
Uptime *float64 `json:"uptime"` // TODO check if data type of uptime is float64 or int
}
type ICUpdate struct {
Status *ICStatus `json:"status"`
// TODO add JSON start parameter scheme
}
var client AMQPclient
func ConnectAMQP(uri string) error {
var err error
// connect to broker
client.connection, err = amqp.Dial(uri)
if err != nil {
return fmt.Errorf("AMQP: failed to connect to RabbitMQ broker %v, error: %v", uri, err)
}
// create channel
client.channel, err = client.connection.Channel()
if err != nil {
return fmt.Errorf("AMQP: failed to open a channel, error: %v", err)
}
// declare exchange
err = client.channel.ExchangeDeclare(VILLAS_EXCHANGE,
"headers",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the exchange, error: %v", err)
}
// add a queue for the ICs
ICQueue, err := client.channel.QueueDeclare("infrastructure_components",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to declare the queue, error: %v", err)
}
err = client.channel.QueueBind(ICQueue.Name, "", VILLAS_EXCHANGE, false, nil)
if err != nil {
return fmt.Errorf("AMQP: failed to bind the queue, error: %v", err)
}
// consume deliveries
client.replies, err = client.channel.Consume(ICQueue.Name,
"",
true,
false,
false,
false,
nil)
if err != nil {
return fmt.Errorf("AMQP: failed to consume deliveries, error: %v", err)
}
// consuming queue
go func() {
for {
for message := range client.replies {
err = processMessage(message)
if err != nil {
log.Println(err.Error())
}
}
time.Sleep(2) // sleep for 2 sek
}
}()
log.Printf(" AMQP: Waiting for messages... ")
return nil
}
func sendActionAMQP(action Action) error {
payload, err := json.Marshal(action)
if err != nil {
return err
}
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
// set message headers
var headers map[string]interface{}
headers = make(map[string]interface{}) // empty map
if action.Properties.UUID != nil {
headers["uuid"] = *action.Properties.UUID
}
if action.Properties.Type != nil {
headers["type"] = *action.Properties.Type
}
if action.Properties.Category != nil {
headers["category"] = *action.Properties.Category
}
msg.Headers = headers
err = CheckConnection()
if err != nil {
return err
}
//log.Println("AMQP: Sending message", string(msg.Body))
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
return err
}
//func PingAMQP() error {
// log.Println("AMQP: sending ping command to all ICs")
//
// var a Action
// a.Act = "ping"
// *a.Properties.UUID = ""
//
// err := sendActionAMQP(a)
// return err
//}
func CheckConnection() error {
if client.connection != nil {
if client.connection.IsClosed() {
return fmt.Errorf("connection to broker is closed")
}
} else {
return fmt.Errorf("connection is nil")
}
return nil
}
func StartAMQP(AMQPurl string, api *gin.RouterGroup) error {
if AMQPurl != "" {
log.Println("Starting AMQP client")
err := ConnectAMQP(AMQPurl)
if err != nil {
return err
}
// register IC action endpoint only if AMQP client is used
RegisterAMQPEndpoint(api.Group("/ic"))
// Periodically call the Ping function to check which ICs are still there
ticker := time.NewTicker(10 * time.Second)
go func() {
for {
select {
case <-ticker.C:
//TODO Add a useful regular event here
/*
err = PingAMQP()
if err != nil {
log.Println("AMQP Error: ", err.Error())
}
*/
}
}
}()
log.Printf("Connected AMQP client to %s", AMQPurl)
}
return nil
}
func processMessage(message amqp.Delivery) error {
var payload ICUpdate
err := json.Unmarshal(message.Body, &payload)
if err != nil {
return fmt.Errorf("AMQP: Could not unmarshal message to JSON: %v err: %v", string(message.Body), err)
}
if payload.Status != nil {
//log.Println("Processing AMQP message: ", string(message.Body))
// if a message contains a "state" field, it is an update for an IC
ICUUID := payload.Status.UUID
_, err = uuid.Parse(ICUUID)
if err != nil {
return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body))
}
var sToBeUpdated InfrastructureComponent
err = sToBeUpdated.byUUID(ICUUID)
if err == gorm.ErrRecordNotFound {
// create new record
err = createExternalIC(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 based on payload
err = sToBeUpdated.updateExternalIC(payload)
}
}
return err
}
func createExternalIC(payload ICUpdate) error {
var newICReq AddICRequest
newICReq.InfrastructureComponent.UUID = payload.Status.UUID
if payload.Status.Name == nil ||
payload.Status.Category == nil ||
payload.Status.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.Status.Name
newICReq.InfrastructureComponent.Category = *payload.Status.Category
newICReq.InfrastructureComponent.Type = *payload.Status.Type
// add optional params
if payload.Status.State != nil {
newICReq.InfrastructureComponent.State = *payload.Status.State
} else {
newICReq.InfrastructureComponent.State = "unknown"
}
if newICReq.InfrastructureComponent.State == "gone" {
// Check if state is "gone" and abort creation of IC in this case
log.Println("AMQP: Aborting creation of IC with state gone")
return nil
}
if payload.Status.WS_url != nil {
newICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url
}
if payload.Status.API_url != nil {
newICReq.InfrastructureComponent.APIURL = *payload.Status.API_url
}
if payload.Status.Location != nil {
newICReq.InfrastructureComponent.Location = *payload.Status.Location
}
if payload.Status.Description != nil {
newICReq.InfrastructureComponent.Description = *payload.Status.Description
}
if payload.Status.Uptime != nil {
newICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime
}
// 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, err := newICReq.createIC(true)
if err != nil {
return fmt.Errorf("AMQP: Creating new IC failed: %v", err)
}
// save IC
err = newIC.save()
if err != nil {
return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err)
}
log.Println("AMQP: Created IC with UUID ", newIC.UUID)
return nil
}
func (s *InfrastructureComponent) updateExternalIC(payload ICUpdate) error {
var updatedICReq UpdateICRequest
if payload.Status.State != nil {
updatedICReq.InfrastructureComponent.State = *payload.Status.State
if *payload.Status.State == "gone" {
// remove IC from DB
log.Println("AMQP: Deleting IC with state gone")
err := s.delete(true)
if err != nil {
// if component could not be deleted there are still configurations using it in the DB
// continue with the update to save the new state of the component and get back to the deletion later
log.Println("AMQP: Deletion of IC postponed (config(s) associated to it)")
}
}
}
if payload.Status.Type != nil {
updatedICReq.InfrastructureComponent.Type = *payload.Status.Type
}
if payload.Status.Category != nil {
updatedICReq.InfrastructureComponent.Category = *payload.Status.Category
}
if payload.Status.Name != nil {
updatedICReq.InfrastructureComponent.Name = *payload.Status.Name
}
if payload.Status.WS_url != nil {
updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url
}
if payload.Status.API_url != nil {
updatedICReq.InfrastructureComponent.APIURL = *payload.Status.API_url
}
if payload.Status.Location != nil {
//postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Status.Location + `"}`)}
updatedICReq.InfrastructureComponent.Location = *payload.Status.Location
}
if payload.Status.Description != nil {
updatedICReq.InfrastructureComponent.Description = *payload.Status.Description
}
if payload.Status.Uptime != nil {
updatedICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime
}
// TODO add JSON start parameter scheme
// 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)
}
log.Println("AMQP: Updated IC with UUID ", s.UUID)
return err
}
func newTrue() *bool {
b := true
return &b
}
func newFalse() *bool {
b := false
return &b
}

View file

@ -22,11 +22,11 @@
package infrastructure_component
import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"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"
)
func RegisterICEndpoints(r *gin.RouterGroup) {
@ -38,6 +38,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
@ -91,20 +95,28 @@ func addIC(c *gin.Context) {
}
// Validate the request
if err = req.Validate(); err != nil {
if err = req.validate(); err != nil {
helper.UnprocessableEntityError(c, err.Error())
return
}
// Create the new IC from the request
newIC := req.CreateIC()
// Save new IC to DB
err = newIC.Save()
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"ic": newIC.InfrastructureComponent})
newIC, err := req.createIC(false)
if err != nil {
helper.InternalServerError(c, "Unable to send create action: "+err.Error())
return
}
if !(newIC.ManagedExternally) {
// Save new IC to DB if not managed externally
err = newIC.save()
if helper.DBError(c, err) {
return
}
}
c.JSON(http.StatusOK, gin.H{"ic": newIC.InfrastructureComponent})
}
// updateIC godoc
@ -129,6 +141,11 @@ func updateIC(c *gin.Context) {
return
}
if oldIC.ManagedExternally {
helper.ForbiddenError(c, "Cannot update externally managed component via API")
return
}
var req UpdateICRequest
err := c.BindJSON(&req)
if err != nil {
@ -137,16 +154,16 @@ func updateIC(c *gin.Context) {
}
// Validate the request
if err = req.Validate(); err != nil {
if err = req.validate(); err != nil {
helper.UnprocessableEntityError(c, err.Error())
return
}
// Create the updatedIC from oldIC
updatedIC := req.UpdatedIC(oldIC)
updatedIC := req.updatedIC(oldIC)
// Finally update the IC in the DB
err = oldIC.Update(updatedIC)
err = oldIC.update(updatedIC)
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"ic": updatedIC.InfrastructureComponent})
}
@ -197,11 +214,15 @@ func deleteIC(c *gin.Context) {
}
// Delete the IC
err := s.delete()
if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"ic": s.InfrastructureComponent})
err := s.delete(false)
if helper.DBError(c, err) {
return
} else if err != nil {
helper.InternalServerError(c, "Unable to send delete action: "+err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"ic": s.InfrastructureComponent})
}
// getConfigsOfIC godoc
@ -231,3 +252,69 @@ 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: Sending actions:", actions)
for _, action := range actions {
/*if action.When == 0 {
action.When = float32(now.Unix())
}*/
// make sure that the important properties are set correctly so that the message can be identified by the receiver
if action.Properties.UUID == nil {
action.Properties.UUID = new(string)
*action.Properties.UUID = s.UUID
}
if action.Properties.Type == nil {
action.Properties.Type = new(string)
*action.Properties.Type = s.Type
}
if action.Properties.Category == nil {
action.Properties.Category = new(string)
*action.Properties.Category = s.Category
}
if action.Properties.Name == nil {
action.Properties.Name = new(string)
*action.Properties.Name = s.Name
}
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.",
})
}

View file

@ -23,33 +23,34 @@ package infrastructure_component
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"log"
"time"
)
type InfrastructureComponent struct {
database.InfrastructureComponent
}
func (s *InfrastructureComponent) Save() error {
func (s *InfrastructureComponent) save() error {
db := database.GetDB()
err := db.Create(s).Error
return err
}
func (s *InfrastructureComponent) ByID(id uint) error {
func (s *InfrastructureComponent) byID(id uint) error {
db := database.GetDB()
err := db.Find(s, id).Error
return err
}
func (s *InfrastructureComponent) ByUUID(uuid string) error {
func (s *InfrastructureComponent) byUUID(uuid string) error {
db := database.GetDB()
err := db.Find(s, "UUID = ?", uuid).Error
return err
}
func (s *InfrastructureComponent) Update(updatedIC InfrastructureComponent) error {
func (s *InfrastructureComponent) update(updatedIC InfrastructureComponent) error {
db := database.GetDB()
err := db.Model(s).Updates(updatedIC).Error
@ -57,7 +58,19 @@ func (s *InfrastructureComponent) Update(updatedIC InfrastructureComponent) erro
return err
}
func (s *InfrastructureComponent) delete() error {
func (s *InfrastructureComponent) delete(receivedViaAMQP bool) error {
if s.ManagedExternally && !receivedViaAMQP {
var action Action
action.Act = "delete"
action.When = time.Now().Unix()
action.Properties.UUID = new(string)
*action.Properties.UUID = s.UUID
log.Println("AMQP: Sending request to delete IC with UUID", s.UUID)
err := sendActionAMQP(action)
return err
}
db := database.GetDB()
no_configs := db.Model(s).Association("ComponentConfigurations").Count()

View file

@ -45,7 +45,7 @@ func CheckPermissions(c *gin.Context, modeltype database.ModelName, operation da
return false, s
}
err = s.ByID(uint(ICID))
err = s.byID(uint(ICID))
if helper.DBError(c, err) {
return false, s
}

View file

@ -22,12 +22,17 @@
package infrastructure_component
import (
"encoding/json"
"fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
component_configuration "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/streadway/amqp"
"github.com/stretchr/testify/assert"
"os"
"testing"
"time"
"github.com/gin-gonic/gin"
@ -37,16 +42,50 @@ import (
)
var router *gin.Engine
var api *gin.RouterGroup
var waitingTime time.Duration = 2
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
Host string `json:"host,omitempty"`
APIHost string `json:"apihost,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
APIURL string `json:"apiurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"`
ManagedExternally *bool `json:"managedexternally"`
}
type ScenarioRequest struct {
Name string `json:"name,omitempty"`
Running bool `json:"running,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
type ConfigRequest struct {
Name string `json:"name,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
ICID uint `json:"icID,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
FileIDs []int64 `json:"fileIDs,omitempty"`
}
type ICAction struct {
Act string `json:"action,omitempty"`
When int64 `json:"when,omitempty"`
Properties struct {
UUID *string `json:"uuid,omitempty"`
Name *string `json:"name,omitempty"`
Category *string `json:"category,omitempty"`
Type *string `json:"type,omitempty"`
Location *string `json:"location,omitempty"`
WS_url *string `json:"ws_url,omitempty"`
API_url *string `json:"api_url,omitempty"`
Description *string `json:"description,omitempty"`
} `json:"properties,omitempty"`
}
func TestMain(m *testing.M) {
@ -62,11 +101,16 @@ func TestMain(m *testing.M) {
defer database.DBpool.Close()
router = gin.Default()
api := router.Group("/api")
api = router.Group("/api")
user.RegisterAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterICEndpoints(api.Group("/ic"))
// component configuration endpoints required to associate an IC with a component config
component_configuration.RegisterComponentConfigurationEndpoints(api.Group("/configs"))
// scenario endpoints required here to first add a scenario to the DB
// that can be associated with a new component configuration
scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
os.Exit(m.Run())
}
@ -76,6 +120,22 @@ func TestAddICAsAdmin(t *testing.T) {
database.MigrateModels()
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
// check AMQP connection
err := CheckConnection()
assert.Errorf(t, err, "connection is nil")
// connect AMQP client
// Make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set
host, err := configuration.GolbalConfig.String("amqp.host")
user, err := configuration.GolbalConfig.String("amqp.user")
pass, err := configuration.GolbalConfig.String("amqp.pass")
amqpURI := "amqp://" + user + ":" + pass + "@" + host
// AMQP Connection startup is tested here
// Not repeated in other tests because it is only needed once
err = StartAMQP(amqpURI, api)
assert.NoError(t, err)
// authenticate as admin
token, err := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.AdminCredentials)
@ -100,13 +160,17 @@ func TestAddICAsAdmin(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
APIURL: helper.ICB.APIURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err = helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newIC})
@ -138,6 +202,30 @@ func TestAddICAsAdmin(t *testing.T) {
fmt.Sprintf("/api/ic/%v", newICID+1), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
newExternalIC := ICRequest{
UUID: helper.ICB.UUID,
WebsocketURL: helper.ICB.WebsocketURL,
APIURL: helper.ICB.APIURL,
Type: helper.ICB.Type,
Name: helper.ICB.Name,
Category: helper.ICB.Category,
State: helper.ICB.State,
Location: helper.ICB.Location,
Description: helper.ICB.Description,
StartParameterScheme: helper.ICB.StartParameterScheme,
ManagedExternally: newTrue(),
}
// test creation of external IC (should lead to emission of AMQP message to VILLAS)
code, resp, err = helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newExternalIC})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare POST's response with the newExternalIC
err = helper.CompareResponse(resp, helper.KeyModels{"ic": newExternalIC})
assert.NoError(t, err)
}
func TestAddICAsUser(t *testing.T) {
@ -152,13 +240,16 @@ func TestAddICAsUser(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
// This should fail with unprocessable entity 422 error code
@ -181,13 +272,16 @@ func TestUpdateICAsAdmin(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newIC})
@ -210,7 +304,7 @@ func TestUpdateICAsAdmin(t *testing.T) {
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// Test PUT IC
newIC.Host = "ThisIsMyNewHost"
newIC.WebsocketURL = "ThisIsMyNewURL"
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v", newICID), "PUT", helper.KeyModels{"ic": newIC})
assert.NoError(t, err)
@ -231,6 +325,53 @@ func TestUpdateICAsAdmin(t *testing.T) {
err = helper.CompareResponse(resp, helper.KeyModels{"ic": newIC})
assert.NoError(t, err)
// fake an IC update (create) message
var update ICUpdate
update.Status = new(ICStatus)
update.Status.UUID = helper.ICB.UUID
update.Status.State = new(string)
*update.Status.State = "idle"
update.Status.Name = new(string)
*update.Status.Name = helper.ICB.Name
update.Status.Category = new(string)
*update.Status.Category = helper.ICB.Category
update.Status.Type = new(string)
*update.Status.Type = helper.ICB.Type
payload, err := json.Marshal(update)
assert.NoError(t, err)
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = CheckConnection()
assert.NoError(t, err)
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
// Wait until externally managed IC is created (happens async)
time.Sleep(waitingTime * time.Second)
// try to update this IC
var updatedIC ICRequest
updatedIC.Name = "a new name"
// Should result in forbidden return code 403
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v", 2), "PUT", helper.KeyModels{"ic": updatedIC})
assert.NoError(t, err)
assert.Equalf(t, 403, code, "Response body: \n%v\n", resp)
}
func TestUpdateICAsUser(t *testing.T) {
@ -245,13 +386,16 @@ func TestUpdateICAsUser(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newIC})
@ -269,7 +413,7 @@ func TestUpdateICAsUser(t *testing.T) {
// Test PUT IC
// This should fail with unprocessable entity status code 422
newIC.Host = "ThisIsMyNewHost"
newIC.WebsocketURL = "ThisIsMyNewURL"
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v", newICID), "PUT", helper.KeyModels{"ic": newIC})
assert.NoError(t, err)
@ -289,13 +433,16 @@ func TestDeleteICAsAdmin(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newIC})
@ -327,6 +474,58 @@ func TestDeleteICAsAdmin(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, finalNumber, initialNumber-1)
// fake an IC update (create) message
var update ICUpdate
update.Status = new(ICStatus)
update.Status.UUID = helper.ICB.UUID
update.Status.State = new(string)
*update.Status.State = "idle"
update.Status.Name = new(string)
*update.Status.Name = helper.ICB.Name
update.Status.Category = new(string)
*update.Status.Category = helper.ICB.Category
update.Status.Type = new(string)
*update.Status.Type = helper.ICB.Type
payload, err := json.Marshal(update)
assert.NoError(t, err)
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = CheckConnection()
assert.NoError(t, err)
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
// Wait until externally managed IC is created (happens async)
time.Sleep(waitingTime * time.Second)
// Delete the added external IC (triggers an AMQP message, but should not remove the IC from the DB)
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v", 2), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Again count the number of all the ICs returned
finalNumberAfterExtneralDelete, err := helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, finalNumber+1, finalNumberAfterExtneralDelete)
}
func TestDeleteICAsUser(t *testing.T) {
@ -341,13 +540,16 @@ func TestDeleteICAsUser(t *testing.T) {
// test POST ic/ $newIC
newIC := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newIC})
@ -365,7 +567,7 @@ func TestDeleteICAsUser(t *testing.T) {
// Test DELETE ICs
// This should fail with unprocessable entity status code 422
newIC.Host = "ThisIsMyNewHost"
newIC.WebsocketURL = "ThisIsMyNewURL"
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v", newICID), "DELETE", nil)
assert.NoError(t, err)
@ -389,13 +591,16 @@ func TestGetAllICs(t *testing.T) {
// test POST ic/ $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})
@ -404,14 +609,18 @@ func TestGetAllICs(t *testing.T) {
// test POST ic/ $newICB
newICB := ICRequest{
UUID: helper.ICB.UUID,
Host: helper.ICB.Host,
Type: helper.ICB.Type,
Name: helper.ICB.Name,
Category: helper.ICB.Category,
State: helper.ICB.State,
Properties: helper.ICB.Properties,
UUID: helper.ICB.UUID,
WebsocketURL: helper.ICB.WebsocketURL,
Type: helper.ICB.Type,
Name: helper.ICB.Name,
Category: helper.ICB.Category,
State: helper.ICB.State,
Location: helper.ICB.Location,
Description: helper.ICB.Description,
StartParameterScheme: helper.ICB.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err = helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICB})
assert.NoError(t, err)
@ -449,13 +658,16 @@ func TestGetConfigsOfIC(t *testing.T) {
// test POST ic/ $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})
@ -467,7 +679,6 @@ func TestGetConfigsOfIC(t *testing.T) {
assert.NoError(t, err)
// test GET ic/ID/confis
// TODO how to properly test this without using component configuration endpoints?
numberOfConfigs, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/ic/%v/configs", newICID), "GET", nil)
assert.NoError(t, err)
@ -481,7 +692,6 @@ func TestGetConfigsOfIC(t *testing.T) {
assert.NoError(t, err)
// test GET ic/ID/configs
// TODO how to properly test this without using component configuration endpoints?
numberOfConfigs, err = helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/ic/%v/configs", newICID), "GET", nil)
assert.NoError(t, err)
@ -496,3 +706,332 @@ func TestGetConfigsOfIC(t *testing.T) {
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func TestSendActionToIC(t *testing.T) {
database.DropTables()
database.MigrateModels()
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
// authenticate as admin
token, err := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.AdminCredentials)
assert.NoError(t, err)
// test POST ic/ $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: newFalse(),
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newIC's ID from the response
newICID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
// create action to be sent to IC
action1 := ICAction{
Act: "start",
When: time.Now().Unix(),
}
action1.Properties.UUID = new(string)
*action1.Properties.UUID = newICA.UUID
actions := [1]ICAction{action1}
// Send action to IC
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v/action", newICID), "POST", actions)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Send malformed actions array to IC (should yield bad request)
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/ic/%v/action", newICID), "POST", action1)
assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
}
func TestCreateUpdateViaAMQPRecv(t *testing.T) {
database.DropTables()
database.MigrateModels()
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
// authenticate as admin
token, err := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.AdminCredentials)
assert.NoError(t, err)
// fake an IC update message
var update ICUpdate
update.Status = new(ICStatus)
update.Status.UUID = helper.ICA.UUID
update.Status.State = new(string)
*update.Status.State = "idle"
payload, err := json.Marshal(update)
assert.NoError(t, err)
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = CheckConnection()
assert.NoError(t, err)
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
time.Sleep(waitingTime * time.Second)
// get the length of the GET all ICs response for user
number, err := helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 0, number)
// complete the (required) data of an IC
update.Status.Name = new(string)
*update.Status.Name = helper.ICA.Name
update.Status.Category = new(string)
*update.Status.Category = helper.ICA.Category
update.Status.Type = new(string)
*update.Status.Type = helper.ICA.Type
update.Status.Uptime = new(float64)
*update.Status.Uptime = -1.0
update.Status.WS_url = new(string)
*update.Status.WS_url = helper.ICA.WebsocketURL
update.Status.API_url = new(string)
*update.Status.API_url = helper.ICA.APIURL
update.Status.Description = new(string)
*update.Status.Description = helper.ICA.Description
update.Status.Location = new(string)
*update.Status.Location = helper.ICA.Location
payload, err = json.Marshal(update)
assert.NoError(t, err)
msg = amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
time.Sleep(waitingTime * time.Second)
// get the length of the GET all ICs response for user
number, err = helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 1, number)
// modify status update
*update.Status.Name = "This is the new name"
payload, err = json.Marshal(update)
assert.NoError(t, err)
msg = amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
time.Sleep(waitingTime * time.Second)
// get the length of the GET all ICs response for user
number, err = helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 1, number)
}
func TestDeleteICViaAMQPRecv(t *testing.T) {
database.DropTables()
database.MigrateModels()
assert.NoError(t, helper.DBAddAdminAndUserAndGuest())
// authenticate as admin
token, err := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.AdminCredentials)
assert.NoError(t, err)
// fake an IC update message
var update ICUpdate
update.Status = new(ICStatus)
update.Status.UUID = helper.ICA.UUID
update.Status.State = new(string)
*update.Status.State = "idle"
// complete the (required) data of an IC
update.Status.Name = new(string)
*update.Status.Name = helper.ICA.Name
update.Status.Category = new(string)
*update.Status.Category = helper.ICA.Category
update.Status.Type = new(string)
*update.Status.Type = helper.ICA.Type
update.Status.Uptime = new(float64)
*update.Status.Uptime = -1.0
update.Status.WS_url = new(string)
*update.Status.WS_url = helper.ICA.WebsocketURL
update.Status.API_url = new(string)
*update.Status.API_url = helper.ICA.APIURL
update.Status.Description = new(string)
*update.Status.Description = helper.ICA.Description
update.Status.Location = new(string)
*update.Status.Location = helper.ICA.Location
payload, err := json.Marshal(update)
assert.NoError(t, err)
msg := amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
err = CheckConnection()
assert.NoError(t, err)
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
time.Sleep(waitingTime * time.Second)
// get the length of the GET all ICs response for user
number, err := helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 1, number)
// add scenario
newScenario := ScenarioRequest{
Name: helper.ScenarioA.Name,
Running: helper.ScenarioA.Running,
StartParameters: helper.ScenarioA.StartParameters,
}
code, resp, err := helper.TestEndpoint(router, token,
"/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare POST's response with the newScenario
err = helper.CompareResponse(resp, helper.KeyModels{"scenario": newScenario})
assert.NoError(t, err)
// Read newScenario's ID from the response
newScenarioID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
// Add component config and associate with IC and scenario
newConfig := ConfigRequest{
Name: helper.ConfigA.Name,
ScenarioID: uint(newScenarioID),
ICID: 1,
StartParameters: helper.ConfigA.StartParameters,
FileIDs: helper.ConfigA.FileIDs,
}
code, resp, err = helper.TestEndpoint(router, token,
"/api/configs", "POST", helper.KeyModels{"config": newConfig})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare POST's response with the newConfig
err = helper.CompareResponse(resp, helper.KeyModels{"config": newConfig})
assert.NoError(t, err)
// Read newConfig's ID from the response
newConfigID, err := helper.GetResponseID(resp)
assert.NoError(t, err)
// modify status update to state "gone"
*update.Status.State = "gone"
payload, err = json.Marshal(update)
assert.NoError(t, err)
msg = amqp.Publishing{
DeliveryMode: 2,
Timestamp: time.Now(),
ContentType: "application/json",
ContentEncoding: "utf-8",
Priority: 0,
Body: payload,
}
// attempt to delete IC (should not work immediately because IC is still associated with component config)
err = client.channel.Publish(VILLAS_EXCHANGE,
"",
false,
false,
msg)
assert.NoError(t, err)
time.Sleep(waitingTime * time.Second)
// get the length of the GET all ICs response for user
number, err = helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 1, number)
// Delete component config from earlier
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/configs/%v", newConfigID), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare DELETE's response with the newConfig
err = helper.CompareResponse(resp, helper.KeyModels{"config": newConfig})
assert.NoError(t, err)
// get the length of the GET all ICs response for user
number, err = helper.LengthOfResponse(router, token,
"/api/ic", "GET", nil)
assert.NoError(t, err)
assert.Equal(t, 0, number)
}

View file

@ -23,34 +23,43 @@ package infrastructure_component
import (
"encoding/json"
"github.com/google/uuid"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/nsf/jsondiff"
"gopkg.in/go-playground/validator.v9"
"log"
"time"
)
var validate *validator.Validate
type validNewIC struct {
UUID string `form:"UUID" validate:"required"`
Host string `form:"Host" validate:"omitempty"`
APIHost string `form:"APIHost" validate:"omitempty"`
Type string `form:"Type" validate:"required"`
Name string `form:"Name" validate:"required"`
Category string `form:"Category" validate:"required"`
Properties postgres.Jsonb `form:"Properties" validate:"omitempty"`
State string `form:"State" validate:"omitempty"`
UUID string `form:"UUID" validate:"omitempty"`
WebsocketURL string `form:"WebsocketURL" validate:"omitempty"`
APIURL string `form:"APIURL" validate:"omitempty"`
Type string `form:"Type" validate:"required"`
Name string `form:"Name" validate:"required"`
Category string `form:"Category" validate:"required"`
State string `form:"State" validate:"omitempty"`
Location string `form:"Location" validate:"omitempty"`
Description string `form:"Description" validate:"omitempty"`
StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"`
ManagedExternally *bool `form:"ManagedExternally" validate:"required"`
Uptime float64 `form:"Uptime" validate:"omitempty"`
}
type validUpdatedIC struct {
UUID string `form:"UUID" validate:"omitempty"`
Host string `form:"Host" validate:"omitempty"`
APIHost string `form:"APIHost" validate:"omitempty"`
Type string `form:"Type" validate:"omitempty"`
Name string `form:"Name" validate:"omitempty"`
Category string `form:"Category" validate:"omitempty"`
Properties postgres.Jsonb `form:"Properties" validate:"omitempty"`
State string `form:"State" validate:"omitempty"`
UUID string `form:"UUID" validate:"omitempty"`
WebsocketURL string `form:"WebsocketURL" validate:"omitempty"`
APIURL string `form:"APIURL" validate:"omitempty"`
Type string `form:"Type" validate:"omitempty"`
Name string `form:"Name" validate:"omitempty"`
Category string `form:"Category" validate:"omitempty"`
State string `form:"State" validate:"omitempty"`
Location string `form:"Location" validate:"omitempty"`
Description string `form:"Description" validate:"omitempty"`
StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"`
Uptime float64 `form:"Uptime" validate:"omitempty"`
}
type AddICRequest struct {
@ -61,28 +70,83 @@ type UpdateICRequest struct {
InfrastructureComponent validUpdatedIC `json:"ic"`
}
func (r *AddICRequest) Validate() error {
func (r *AddICRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
if errs != nil {
return errs
}
// check if uuid is valid
_, errs = uuid.Parse(r.InfrastructureComponent.UUID)
return errs
}
func (r *UpdateICRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *UpdateICRequest) Validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *AddICRequest) CreateIC() InfrastructureComponent {
func (r *AddICRequest) createIC(receivedViaAMQP bool) (InfrastructureComponent, error) {
var s InfrastructureComponent
var err error
err = nil
// case distinction for externally managed IC
if *r.InfrastructureComponent.ManagedExternally && !receivedViaAMQP {
var action Action
action.Act = "create"
action.When = time.Now().Unix()
action.Properties.Type = new(string)
action.Properties.Name = new(string)
action.Properties.Category = new(string)
*action.Properties.Type = r.InfrastructureComponent.Type
*action.Properties.Name = r.InfrastructureComponent.Name
*action.Properties.Category = r.InfrastructureComponent.Category
// set optional properties
if r.InfrastructureComponent.Description != "" {
action.Properties.Description = new(string)
*action.Properties.Description = r.InfrastructureComponent.Description
}
if r.InfrastructureComponent.Location != "" {
action.Properties.Location = new(string)
*action.Properties.Location = r.InfrastructureComponent.Location
}
if r.InfrastructureComponent.APIURL != "" {
action.Properties.API_url = new(string)
*action.Properties.API_url = r.InfrastructureComponent.APIURL
}
if r.InfrastructureComponent.WebsocketURL != "" {
action.Properties.WS_url = new(string)
*action.Properties.WS_url = r.InfrastructureComponent.WebsocketURL
}
if r.InfrastructureComponent.UUID != "" {
action.Properties.UUID = new(string)
*action.Properties.UUID = r.InfrastructureComponent.UUID
}
log.Println("AMQP: Sending request to create new IC")
err = sendActionAMQP(action)
}
s.UUID = r.InfrastructureComponent.UUID
s.Host = r.InfrastructureComponent.Host
s.APIHost = r.InfrastructureComponent.APIHost
s.WebsocketURL = r.InfrastructureComponent.WebsocketURL
s.APIURL = r.InfrastructureComponent.APIURL
s.Type = r.InfrastructureComponent.Type
s.Name = r.InfrastructureComponent.Name
s.Category = r.InfrastructureComponent.Category
s.Properties = r.InfrastructureComponent.Properties
s.Location = r.InfrastructureComponent.Location
s.Description = r.InfrastructureComponent.Description
s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme
s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally
s.Uptime = -1.0 // no uptime available
if r.InfrastructureComponent.State != "" {
s.State = r.InfrastructureComponent.State
} else {
@ -91,10 +155,10 @@ func (r *AddICRequest) CreateIC() InfrastructureComponent {
// set last update to creation time of IC
s.StateUpdateAt = time.Now().Format(time.RFC1123)
return s
return s, err
}
func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) InfrastructureComponent {
func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent) InfrastructureComponent {
// Use the old InfrastructureComponent as a basis for the updated InfrastructureComponent `s`
s := oldIC
@ -102,12 +166,12 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur
s.UUID = r.InfrastructureComponent.UUID
}
if r.InfrastructureComponent.Host != "" {
s.Host = r.InfrastructureComponent.Host
if r.InfrastructureComponent.WebsocketURL != "" {
s.WebsocketURL = r.InfrastructureComponent.WebsocketURL
}
if r.InfrastructureComponent.APIHost != "" {
s.APIHost = r.InfrastructureComponent.APIHost
if r.InfrastructureComponent.APIURL != "" {
s.APIURL = r.InfrastructureComponent.APIURL
}
if r.InfrastructureComponent.Type != "" {
@ -126,6 +190,14 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur
s.State = r.InfrastructureComponent.State
}
if r.InfrastructureComponent.Location != "" {
s.Location = r.InfrastructureComponent.Location
}
if r.InfrastructureComponent.Description != "" {
s.Description = r.InfrastructureComponent.Description
}
// set last update time
s.StateUpdateAt = time.Now().Format(time.RFC1123)
@ -133,11 +205,11 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur
var emptyJson postgres.Jsonb
// Serialize empty json and params
emptyJson_ser, _ := json.Marshal(emptyJson)
startParams_ser, _ := json.Marshal(r.InfrastructureComponent.Properties)
startParams_ser, _ := json.Marshal(r.InfrastructureComponent.StartParameterScheme)
opts := jsondiff.DefaultConsoleOptions()
diff, _ := jsondiff.Compare(emptyJson_ser, startParams_ser, &opts)
if diff.String() != "FullMatch" {
s.Properties = r.InfrastructureComponent.Properties
s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme
}
return s

View file

@ -117,9 +117,12 @@ func AddTestData(cfg *config.Config, router *gin.Engine) (*bytes.Buffer, error)
if code != http.StatusOK {
return resp, fmt.Errorf("error adding IC A")
}
code, resp, err = helper.TestEndpoint(router, token, basePath+"/ic", "POST", helper.KeyModels{"ic": helper.ICB})
if code != http.StatusOK {
return resp, fmt.Errorf("error adding IC B")
amqphost, err := cfg.String("amqp.host")
if err != nil && amqphost != "" {
code, resp, err = helper.TestEndpoint(router, token, basePath+"/ic", "POST", helper.KeyModels{"ic": helper.ICB})
if code != http.StatusOK {
return resp, fmt.Errorf("error adding IC B")
}
}
// add scenarios
@ -155,7 +158,7 @@ func AddTestData(cfg *config.Config, router *gin.Engine) (*bytes.Buffer, error)
configB := helper.ConfigB
configA.ScenarioID = 1
configB.ScenarioID = 1
configA.ICID = 2
configA.ICID = 1
configB.ICID = 1
code, resp, err = helper.TestEndpoint(router, token, basePath+"/configs", "POST", helper.KeyModels{"config": configA})
if code != http.StatusOK {

View file

@ -28,11 +28,13 @@ import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
var router *gin.Engine
var amqpURI string
func TestMain(m *testing.M) {
err := configuration.InitConfig()
@ -48,6 +50,15 @@ func TestMain(m *testing.M) {
router = gin.Default()
// connect AMQP client (make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set via command line parameters)
host, err := configuration.GolbalConfig.String("amqp.host")
user, err := configuration.GolbalConfig.String("amqp.user")
pass, err := configuration.GolbalConfig.String("amqp.pass")
amqpURI := "amqp://" + user + ":" + pass + "@" + host
err = infrastructure_component.ConnectAMQP(amqpURI)
os.Exit(m.Run())
}

View file

@ -56,13 +56,16 @@ type ConfigRequest struct {
}
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
Host string `json:"host,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"`
ManagedExternally *bool `json:"managedexternally,omitempty"`
}
type ScenarioRequest struct {
@ -79,13 +82,16 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) {
// POST $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
UUID: helper.ICA.UUID,
WebsocketURL: helper.ICA.WebsocketURL,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Location: helper.ICA.Location,
Description: helper.ICA.Description,
StartParameterScheme: helper.ICA.StartParameterScheme,
ManagedExternally: &helper.ICA.ManagedExternally,
}
_, resp, _ := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})

View file

@ -25,12 +25,12 @@ import (
"fmt"
"log"
"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"
"github.com/zpatrick/go-config"
)
@ -96,22 +96,22 @@ func main() {
apidocs.SwaggerInfo.Host = baseHost
apidocs.SwaggerInfo.BasePath = basePath
//Start AMQP client
if amqphost != "" {
// create amqp URL based on username, password and host
amqpurl := "amqp://" + amqpuser + ":" + amqppass + "@" + amqphost
err = infrastructure_component.StartAMQP(amqpurl, api)
if err != nil {
panic(err)
}
}
// add data to DB (if any)
err = addData(r, configuration.GolbalConfig)
if err != nil {
panic(err)
}
//Start AMQP client
if amqphost != "" {
// create amqp URL based on username, password and host
amqpurl := "amqp://" + amqpuser + ":" + amqppass + "@" + amqphost
err = amqp.StartAMQP(amqpurl, api)
if err != nil {
panic(err)
}
}
// server at port 4000 to match frontend's redirect path
r.Run(":" + port)
}