Merge branch 'refactor-amqp-session' of https://git.rwth-aachen.de/acs/public/villas/web-backend-go into refactor-amqp-session

This commit is contained in:
irismarie 2021-10-20 15:25:22 +02:00
commit 7a540db693
43 changed files with 496 additions and 355 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
villasweb-backend-go villasweb-backend-go
routes/file/testfile.txt routes/file/testfile.txt
routes/result/testfile.csv
routes/file/testfileupdated.txt routes/file/testfileupdated.txt
web-backend-go web-backend-go

View file

@ -10,6 +10,14 @@ stages:
# Stage: test # Stage: test
############################################################################## ##############################################################################
staticcheck:
stage: test
image: golang:1.16-buster
before_script:
- go install honnef.co/go/tools/cmd/staticcheck@latest
script:
- staticcheck ./...
test: test:
stage: test stage: test
image: golang:1.16-buster image: golang:1.16-buster

View file

@ -121,25 +121,25 @@ func InitConfig() error {
"k8sCluster": *k8sCluster, "k8sCluster": *k8sCluster,
} }
if *dbClear == true { if *dbClear {
static["db.clear"] = "true" static["db.clear"] = "true"
} else { } else {
static["db.clear"] = "false" static["db.clear"] = "false"
} }
if *s3NoSSL == true { if *s3NoSSL {
static["s3.nossl"] = "true" static["s3.nossl"] = "true"
} else { } else {
static["s3.nossl"] = "false" static["s3.nossl"] = "false"
} }
if *s3PathStyle == true { if *s3PathStyle {
static["s3.pathstyle"] = "true" static["s3.pathstyle"] = "true"
} else { } else {
static["s3.pathstyle"] = "false" static["s3.pathstyle"] = "false"
} }
if *authExternal == true { if *authExternal {
static["auth.external.enabled"] = "true" static["auth.external.enabled"] = "true"
} else { } else {
static["auth.external.enabled"] = "false" static["auth.external.enabled"] = "false"

View file

@ -23,12 +23,13 @@ package database
import ( import (
"fmt" "fmt"
"golang.org/x/crypto/bcrypt"
"log" "log"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"
"golang.org/x/crypto/bcrypt"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres" _ "github.com/jinzhu/gorm/dialects/postgres"
"github.com/zpatrick/go-config" "github.com/zpatrick/go-config"
@ -37,19 +38,22 @@ import (
var DBpool *gorm.DB // database used by backend var DBpool *gorm.DB // database used by backend
// InitDB Initialize connection to the database // InitDB Initialize connection to the database
func InitDB(cfg *config.Config, dbClear string) error { func InitDB(cfg *config.Config, clear bool) error {
name, err := cfg.String("db.name") name, err := cfg.String("db.name")
if err != nil { if err != nil {
return err return err
} }
host, err := cfg.String("db.host") host, err := cfg.String("db.host")
if err != nil { if err != nil {
return err return err
} }
user, err := cfg.String("db.user")
if err != nil && !strings.Contains(err.Error(), "Required setting 'db.user' not set") { user, err := cfg.StringOr("db.user", "")
if err != nil {
return err return err
} }
pass := "" pass := ""
if user != "" { if user != "" {
pass, err = cfg.String("db.pass") pass, err = cfg.String("db.pass")
@ -57,6 +61,7 @@ func InitDB(cfg *config.Config, dbClear string) error {
return err return err
} }
} }
sslmode, err := cfg.String("db.ssl") sslmode, err := cfg.String("db.ssl")
if err != nil { if err != nil {
return err return err
@ -75,7 +80,7 @@ func InitDB(cfg *config.Config, dbClear string) error {
DBpool = db DBpool = db
// drop tables if parameter set // drop tables if parameter set
if dbClear == "true" { if clear {
DropTables() DropTables()
log.Println("Database tables dropped") log.Println("Database tables dropped")
} }
@ -120,19 +125,19 @@ func MigrateModels() {
} }
// DBAddAdminUser adds a default admin user to the DB // DBAddAdminUser adds a default admin user to the DB
func DBAddAdminUser(cfg *config.Config) (error, string) { func DBAddAdminUser(cfg *config.Config) (string, error) {
DBpool.AutoMigrate(User{}) DBpool.AutoMigrate(User{})
// Check if admin user exists in DB // Check if admin user exists in DB
var users []User var users []User
err := DBpool.Where("Role = ?", "Admin").Find(&users).Error DBpool.Where("Role = ?", "Admin").Find(&users)
adminPW := "" adminPW := ""
adminName := ""
if len(users) == 0 { if len(users) == 0 {
fmt.Println("No admin user found in DB, adding default admin user.") fmt.Println("No admin user found in DB, adding default admin user.")
adminName, err = cfg.String("admin.user") adminName, err := cfg.String("admin.user")
if err != nil || adminName == "" { if err != nil || adminName == "" {
adminName = "admin" adminName = "admin"
} }
@ -157,10 +162,10 @@ func DBAddAdminUser(cfg *config.Config) (error, string) {
// add admin user to DB // add admin user to DB
err = DBpool.Create(&user).Error err = DBpool.Create(&user).Error
if err != nil { if err != nil {
return err, "" return "", err
} }
} }
return nil, adminPW return adminPW, nil
} }
func generatePassword(Len int) string { func generatePassword(Len int) string {
@ -217,8 +222,8 @@ var UserC = User{Username: "User_C", Password: string(pwC),
Role: "Guest", Mail: "User_C@example.com", Active: true} Role: "Guest", Mail: "User_C@example.com", Active: true}
type Credentials struct { type Credentials struct {
Username string `json:"username,required"` Username string `json:"username"`
Password string `json:"password,required"` Password string `json:"password"`
} }
var AdminCredentials = Credentials{ var AdminCredentials = Credentials{

View file

@ -22,7 +22,6 @@
package database package database
import ( import (
"fmt"
"os" "os"
"testing" "testing"
@ -49,42 +48,25 @@ func TestInitDB(t *testing.T) {
defaults := config.NewStatic(static) defaults := config.NewStatic(static)
env := config.NewEnvironment(mappings) env := config.NewEnvironment(mappings)
ownconfig := config.NewConfig([]config.Provider{defaults, env}) ownConfig := config.NewConfig([]config.Provider{defaults, env})
err = InitDB(ownconfig, "true") err = InitDB(ownConfig, true)
assert.Error(t, err)
dbname, err := configuration.GlobalConfig.String("db.name")
assert.NoError(t, err)
static["db.name"] = dbname
ownconfig = config.NewConfig([]config.Provider{defaults, env})
err = InitDB(ownconfig, "true")
assert.Error(t, err) assert.Error(t, err)
dbhost, err := configuration.GlobalConfig.String("db.host") dbOptions := []string{"db.name", "db.host", "db.user", "db.pass", "db.ssl"}
assert.NoError(t, err) for _, opt := range dbOptions {
static["db.host"] = dbhost val, err := configuration.GlobalConfig.String(opt)
ownconfig = config.NewConfig([]config.Provider{defaults, env}) assert.NoError(t, err)
err = InitDB(ownconfig, "true") static[opt] = val
assert.Error(t, err) ownConfig = config.NewConfig([]config.Provider{defaults, env})
err = InitDB(ownConfig, true)
dbuser, err := configuration.GlobalConfig.String("db.user") if opt == "db.ssl" {
static["db.user"] = dbuser assert.NoError(t, err)
ownconfig = config.NewConfig([]config.Provider{defaults, env}) } else {
err = InitDB(ownconfig, "true") assert.Error(t, err)
assert.Error(t, err) }
}
dbpass, err := configuration.GlobalConfig.String("db.pass")
static["db.pass"] = dbpass
ownconfig = config.NewConfig([]config.Provider{defaults, env})
err = InitDB(ownconfig, "true")
assert.Error(t, err)
dbssl, err := configuration.GlobalConfig.String("db.ssl")
assert.NoError(t, err)
static["db.ssl"] = dbssl
ownconfig = config.NewConfig([]config.Provider{defaults, env})
err = InitDB(ownconfig, "true")
assert.NoError(t, err)
// Verify that you can connect to the database // Verify that you can connect to the database
db := GetDB() db := GetDB()
@ -118,7 +100,7 @@ func TestUserAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&userB).Association("Scenarios").Append(&scenarioA).Error) assert.NoError(t, DBpool.Model(&userB).Association("Scenarios").Append(&scenarioA).Error)
var usr1 User var usr1 User
assert.NoError(t, DBpool.Find(&usr1, "ID = ?", 1).Error, fmt.Sprintf("Find User with ID=1")) assert.NoError(t, DBpool.Find(&usr1, "ID = ?", 1).Error, "Find User with ID=1")
// Get scenarios of usr1 // Get scenarios of usr1
var scenarios []Scenario var scenarios []Scenario
@ -196,7 +178,7 @@ func TestScenarioAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&scenarioA).Association("Results").Append(&resultB).Error) assert.NoError(t, DBpool.Model(&scenarioA).Association("Results").Append(&resultB).Error)
var scenario1 Scenario var scenario1 Scenario
assert.NoError(t, DBpool.Find(&scenario1, 1).Error, fmt.Sprintf("Find Scenario with ID=1")) assert.NoError(t, DBpool.Find(&scenario1, 1).Error, "Find Scenario with ID=1")
// Get users of scenario1 // Get users of scenario1
var users []User var users []User
@ -263,7 +245,7 @@ func TestICAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error) assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error)
var ic1 InfrastructureComponent var ic1 InfrastructureComponent
assert.NoError(t, DBpool.Find(&ic1, 1).Error, fmt.Sprintf("Find InfrastructureComponent with ID=1")) assert.NoError(t, DBpool.Find(&ic1, 1).Error, "Find InfrastructureComponent with ID=1")
// Get Component Configurations of ic1 // Get Component Configurations of ic1
var configs []ComponentConfiguration var configs []ComponentConfiguration
@ -314,7 +296,7 @@ func TestComponentConfigurationAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error) assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error)
var config1 ComponentConfiguration var config1 ComponentConfiguration
assert.NoError(t, DBpool.Find(&config1, 1).Error, fmt.Sprintf("Find ComponentConfiguration with ID=1")) assert.NoError(t, DBpool.Find(&config1, 1).Error, "Find ComponentConfiguration with ID=1")
// Check IC ID // Check IC ID
if config1.ICID != 1 { if config1.ICID != 1 {
@ -355,7 +337,7 @@ func TestDashboardAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&dashboardA).Association("Widgets").Append(&widgetB).Error) assert.NoError(t, DBpool.Model(&dashboardA).Association("Widgets").Append(&widgetB).Error)
var dashboard1 Dashboard var dashboard1 Dashboard
assert.NoError(t, DBpool.Find(&dashboard1, 1).Error, fmt.Sprintf("Find Dashboard with ID=1")) assert.NoError(t, DBpool.Find(&dashboard1, 1).Error, "Find Dashboard with ID=1")
//Get widgets of dashboard1 //Get widgets of dashboard1
var widgets []Widget var widgets []Widget
@ -380,7 +362,7 @@ func TestWidgetAssociations(t *testing.T) {
assert.NoError(t, DBpool.Create(&widgetB).Error) assert.NoError(t, DBpool.Create(&widgetB).Error)
var widget1 Widget var widget1 Widget
assert.NoError(t, DBpool.Find(&widget1, 1).Error, fmt.Sprintf("Find Widget with ID=1")) assert.NoError(t, DBpool.Find(&widget1, 1).Error, "Find Widget with ID=1")
} }
func TestFileAssociations(t *testing.T) { func TestFileAssociations(t *testing.T) {
@ -401,5 +383,5 @@ func TestFileAssociations(t *testing.T) {
assert.NoError(t, DBpool.Create(&fileD).Error) assert.NoError(t, DBpool.Create(&fileD).Error)
var file1 File var file1 File
assert.NoError(t, DBpool.Find(&file1, 1).Error, fmt.Sprintf("Find File with ID=1")) assert.NoError(t, DBpool.Find(&file1, 1).Error, "Find File with ID=1")
} }

View file

@ -65,10 +65,11 @@ type RoleActions map[string]ModelActions
// Predefined CRUD operations permissions to be used in Roles // Predefined CRUD operations permissions to be used in Roles
var crud = Permission{Create: true, Read: true, Update: true, Delete: true} var crud = Permission{Create: true, Read: true, Update: true, Delete: true}
var _ru_ = Permission{Create: false, Read: true, Update: true, Delete: false} var _ru_ = Permission{Create: false, Read: true, Update: true, Delete: false}
var __u_ = Permission{Create: false, Read: false, Update: true, Delete: false}
var _r__ = Permission{Create: false, Read: true, Update: false, Delete: false} var _r__ = Permission{Create: false, Read: true, Update: false, Delete: false}
var none = Permission{Create: false, Read: false, Update: false, Delete: false} var none = Permission{Create: false, Read: false, Update: false, Delete: false}
// var __u_ = Permission{Create: false, Read: false, Update: true, Delete: false}
// Roles is used as a look up variable to determine if a certain user is // Roles is used as a look up variable to determine if a certain user is
// allowed to do a certain action on a given model based on his role // allowed to do a certain action on a given model based on his role
var Roles = RoleActions{ var Roles = RoleActions{
@ -134,12 +135,12 @@ func ValidateRole(c *gin.Context, model ModelName, action CRUD) error {
// Get user's role from context // Get user's role from context
role, exists := c.Get(UserRoleCtx) role, exists := c.Get(UserRoleCtx)
if !exists { if !exists {
return fmt.Errorf("Request does not contain user's role") return fmt.Errorf("request does not contain user's role")
} }
// Check if the role can execute the action on the model // Check if the role can execute the action on the model
if !Roles[role.(string)][model][action] { if !Roles[role.(string)][model][action] {
return fmt.Errorf("Action not allowed for role %v", role) return fmt.Errorf("action not allowed for role %v", role)
} }
return nil return nil

View file

@ -21,6 +21,8 @@
*********************************************************************************/ *********************************************************************************/
package api package api
//lint:file-ignore U1000 Ignore all unused code, it's generated
import "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" import "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
// This file defines the responses to any endpoint in the backend // This file defines the responses to any endpoint in the backend

View file

@ -29,9 +29,6 @@ const (
// When setting up the channel after a channel exception // When setting up the channel after a channel exception
reInitDelay = 2 * time.Second reInitDelay = 2 * time.Second
// When resending messages the server didn't confirm
resendDelay = 5 * time.Second
) )
//var client AMQPsession //var client AMQPsession

View file

@ -25,11 +25,12 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/gin-gonic/gin"
"github.com/nsf/jsondiff"
"log" "log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"github.com/gin-gonic/gin"
"github.com/nsf/jsondiff"
) )
// data type used in testing // data type used in testing
@ -56,13 +57,13 @@ type UserRequest struct {
func GetResponseID(resp *bytes.Buffer) (int, error) { func GetResponseID(resp *bytes.Buffer) (int, error) {
// Transform bytes buffer into byte slice // Transform bytes buffer into byte slice
respBytes := []byte(resp.String()) respBytes := resp.Bytes()
// Map JSON response to a map[string]map[string]interface{} // Map JSON response to a map[string]map[string]interface{}
var respRemapped map[string]map[string]interface{} var respRemapped map[string]map[string]interface{}
err := json.Unmarshal(respBytes, &respRemapped) err := json.Unmarshal(respBytes, &respRemapped)
if err != nil { if err != nil {
return 0, fmt.Errorf("Unmarshal failed for respRemapped %v", err) return 0, fmt.Errorf("failed to unmarshal for respRemapped %v", err)
} }
// Get an arbitrary key from tha map. The only key (entry) of // Get an arbitrary key from tha map. The only key (entry) of
@ -75,11 +76,11 @@ func GetResponseID(resp *bytes.Buffer) (int, error) {
// the conversion to integer before returning // the conversion to integer before returning
id, ok := respRemapped[arbitrary_key]["id"].(float64) id, ok := respRemapped[arbitrary_key]["id"].(float64)
if !ok { if !ok {
return 0, fmt.Errorf("Cannot type assert respRemapped") return 0, fmt.Errorf("cannot type assert respRemapped")
} }
return int(id), nil return int(id), nil
} }
return 0, fmt.Errorf("GetResponse reached exit") return 0, fmt.Errorf("getResponse reached exit")
} }
// Return the length of an response in case it is an array // Return the length of an response in case it is an array
@ -90,7 +91,7 @@ func LengthOfResponse(router *gin.Engine, token string, url string,
req, err := http.NewRequest(method, url, nil) req, err := http.NewRequest(method, url, nil)
if err != nil { if err != nil {
return 0, fmt.Errorf("Failed to create new request: %v", err) return 0, fmt.Errorf("failed to create new request: %v", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
@ -103,7 +104,7 @@ func LengthOfResponse(router *gin.Engine, token string, url string,
} }
// Convert the response in array of bytes // Convert the response in array of bytes
responseBytes := []byte(w.Body.String()) responseBytes := w.Body.Bytes()
// First we are trying to unmarshal the response into an array of // First we are trying to unmarshal the response into an array of
// general type variables ([]interface{}). If this fails we will try // general type variables ([]interface{}). If this fails we will try
@ -131,7 +132,7 @@ func LengthOfResponse(router *gin.Engine, token string, url string,
} }
// Failed to identify response. // Failed to identify response.
return 0, fmt.Errorf("Length of response cannot be detected") return 0, fmt.Errorf("length of response cannot be detected")
} }
// Make a request to an endpoint // Make a request to an endpoint
@ -143,13 +144,13 @@ func TestEndpoint(router *gin.Engine, token string, url string,
// Marshal the HTTP request body // Marshal the HTTP request body
body, err := json.Marshal(requestBody) body, err := json.Marshal(requestBody)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("Failed to marshal request body: %v", err) return 0, nil, fmt.Errorf("failed to marshal request body: %v", err)
} }
// Create the request // Create the request
req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("Failed to create new request: %v", err) return 0, nil, fmt.Errorf("failed to create new request: %v", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
@ -202,14 +203,14 @@ func CompareResponse(resp *bytes.Buffer, expected interface{}) error {
// Serialize expected response // Serialize expected response
expectedBytes, err := json.Marshal(expected) expectedBytes, err := json.Marshal(expected)
if err != nil { if err != nil {
return fmt.Errorf("Failed to marshal expected response: %v", err) return fmt.Errorf("failed to marshal expected response: %v", err)
} }
// Compare // Compare
opts := jsondiff.DefaultConsoleOptions() opts := jsondiff.DefaultConsoleOptions()
diff, text := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts) diff, text := jsondiff.Compare(resp.Bytes(), expectedBytes, &opts)
if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" { if diff.String() != "FullMatch" && diff.String() != "SupersetMatch" {
log.Println(text) log.Println(text)
return fmt.Errorf("Response: Expected \"%v\". Got \"%v\".", return fmt.Errorf("response: Expected \"%v\". Got \"%v\"",
"(FullMatch OR SupersetMatch)", diff.String()) "(FullMatch OR SupersetMatch)", diff.String())
} }
@ -224,25 +225,25 @@ func AuthenticateForTest(router *gin.Engine, credentials interface{}) (string, e
// Marshal credentials // Marshal credentials
body, err := json.Marshal(credentials) body, err := json.Marshal(credentials)
if err != nil { if err != nil {
return "", fmt.Errorf("Failed to marshal credentials: %v", err) return "", fmt.Errorf("failed to marshal credentials: %v", err)
} }
req, err := http.NewRequest("POST", "/api/v2/authenticate/internal", bytes.NewBuffer(body)) req, err := http.NewRequest("POST", "/api/v2/authenticate/internal", bytes.NewBuffer(body))
if err != nil { if err != nil {
return "", fmt.Errorf("Failed to create new request: %v", err) return "", fmt.Errorf("failed to create new request: %v", err)
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
// Check that return HTTP Code is 200 (OK) // Check that return HTTP Code is 200 (OK)
if w.Code != http.StatusOK { if w.Code != http.StatusOK {
return "", fmt.Errorf("HTTP Code: Expected \"%v\". Got \"%v\".", return "", fmt.Errorf("http code: Expected \"%v\". Got \"%v\"",
http.StatusOK, w.Code) http.StatusOK, w.Code)
} }
// Get the response // Get the response
var body_data map[string]interface{} var body_data map[string]interface{}
err = json.Unmarshal([]byte(w.Body.String()), &body_data) err = json.Unmarshal(w.Body.Bytes(), &body_data)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -250,16 +251,16 @@ func AuthenticateForTest(router *gin.Engine, credentials interface{}) (string, e
// Check the response // Check the response
success, ok := body_data["success"].(bool) success, ok := body_data["success"].(bool)
if !ok { if !ok {
return "", fmt.Errorf("Type asssertion of response[\"success\"] failed") return "", fmt.Errorf("type asssertion of response[\"success\"] failed")
} }
if !success { if !success {
return "", fmt.Errorf("Authentication failed: %v", body_data["message"]) return "", fmt.Errorf("authentication failed: %v", body_data["message"])
} }
// Extract the token // Extract the token
token, ok := body_data["token"].(string) token, ok := body_data["token"].(string)
if !ok { if !ok {
return "", fmt.Errorf("Type assertion of response[\"token\"] failed") return "", fmt.Errorf("type assertion of response[\"token\"] failed")
} }
// Return the token and nil error // Return the token and nil error

View file

@ -33,14 +33,14 @@ func GetIDOfElement(c *gin.Context, elementName string, source string, providedI
if source == "path" { if source == "path" {
id, err := strconv.Atoi(c.Param(elementName)) id, err := strconv.Atoi(c.Param(elementName))
if err != nil { if err != nil {
BadRequestError(c, fmt.Sprintf("No or incorrect format of path parameter")) BadRequestError(c, "No or incorrect format of path parameter")
return -1, err return -1, err
} }
return id, nil return id, nil
} else if source == "query" { } else if source == "query" {
id, err := strconv.Atoi(c.Request.URL.Query().Get(elementName)) id, err := strconv.Atoi(c.Request.URL.Query().Get(elementName))
if err != nil { if err != nil {
BadRequestError(c, fmt.Sprintf("No or incorrect format of query parameter")) BadRequestError(c, "No or incorrect format of query parameter")
return -1, err return -1, err
} }
return id, nil return id, nil

View file

@ -22,8 +22,9 @@
package component_configuration package component_configuration
import ( import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"log" "log"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
) )
type ComponentConfiguration struct { type ComponentConfiguration struct {
@ -148,7 +149,7 @@ func (m *ComponentConfiguration) delete() error {
if err != nil { if err != nil {
return err return err
} }
for sig, _ := range InputMappingSignals { for sig := range InputMappingSignals {
err = db.Delete(&sig).Error err = db.Delete(&sig).Error
if err != nil { if err != nil {
return err return err
@ -161,7 +162,7 @@ func (m *ComponentConfiguration) delete() error {
if err != nil { if err != nil {
return err return err
} }
for sig, _ := range OutputMappingSignals { for sig := range OutputMappingSignals {
err = db.Delete(&sig).Error err = db.Delete(&sig).Error
if err != nil { if err != nil {
return err return err

View file

@ -85,16 +85,18 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) {
// POST $newICA // POST $newICA
newICA := ICRequest{ newICA := ICRequest{
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig", WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
Type: "villas-node", Type: "villas-node",
Name: "ACS Demo Signals", Name: "ACS Demo Signals",
Category: "gateway", Category: "gateway",
State: "idle", State: "idle",
Location: "k8s", Location: "k8s",
Description: "A signal generator for testing purposes", Description: "A signal generator for testing purposes",
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"prop1" : "a nice prop"}`)}, StartParameterSchema: postgres.Jsonb{
ManagedExternally: newFalse(), RawMessage: json.RawMessage(`{"prop1" : "a nice prop"}`),
},
ManagedExternally: newFalse(),
} }
code, resp, err := helper.TestEndpoint(router, token, code, resp, err := helper.TestEndpoint(router, token,
@ -118,8 +120,10 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) {
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
Name: "Scenario1", Name: "Scenario1",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, StartParameters: postgres.Jsonb{
RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`),
},
} }
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
"/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) "/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
@ -131,7 +135,7 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) {
newScenarioID, _ := helper.GetResponseID(resp) newScenarioID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID), uint(newICID) return uint(newScenarioID), uint(newICID)
@ -143,7 +147,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }

View file

@ -88,6 +88,9 @@ func (d *Dashboard) delete() error {
// remove association between Dashboard and Scenario // remove association between Dashboard and Scenario
err = db.Model(&sim).Association("Dashboards").Delete(d).Error err = db.Model(&sim).Association("Dashboards").Delete(d).Error
if err != nil {
return err
}
// get all widgets of the dashboard // get all widgets of the dashboard
var widgets []database.Widget var widgets []database.Widget
@ -97,8 +100,11 @@ func (d *Dashboard) delete() error {
} }
// Delete widgets // Delete widgets
for widget, _ := range widgets { for widget := range widgets {
err = db.Delete(&widget).Error err = db.Delete(&widget).Error
if err != nil {
return err
}
} }
// Delete dashboard // Delete dashboard

View file

@ -61,8 +61,10 @@ func addScenario(token string) (scenarioID uint) {
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
Name: "Scenario1", Name: "Scenario1",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, StartParameters: postgres.Jsonb{
RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`),
},
} }
_, resp, err := helper.TestEndpoint(router, token, _, resp, err := helper.TestEndpoint(router, token,
"/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) "/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
@ -74,7 +76,7 @@ func addScenario(token string) (scenarioID uint) {
newScenarioID, _ := helper.GetResponseID(resp) newScenarioID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID) return uint(newScenarioID)
@ -85,7 +87,7 @@ func TestMain(m *testing.M) {
if err != nil { if err != nil {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }

View file

@ -98,8 +98,13 @@ func (f *File) Register(fileHeader *multipart.FileHeader, scenarioID uint) error
defer fileContent.Close() defer fileContent.Close()
bucket, err := configuration.GlobalConfig.String("s3.bucket") bucket, err := configuration.GlobalConfig.String("s3.bucket")
if bucket == "" { if err != nil || bucket == "" {
// s3 object storage not used, s3.bucket param is empty
// save file to postgres DB
f.FileData, err = ioutil.ReadAll(fileContent) f.FileData, err = ioutil.ReadAll(fileContent)
if err != nil {
return err
}
f.Key = "" f.Key = ""
} else { } else {
err := f.putS3(fileContent) err := f.putS3(fileContent)
@ -160,8 +165,13 @@ func (f *File) update(fileHeader *multipart.FileHeader) error {
defer fileContent.Close() defer fileContent.Close()
bucket, err := configuration.GlobalConfig.String("s3.bucket") bucket, err := configuration.GlobalConfig.String("s3.bucket")
if bucket == "" { if err != nil || bucket == "" {
// s3 object storage not used, s3.bucket param is empty
// save file to postgres DB
f.FileData, err = ioutil.ReadAll(fileContent) f.FileData, err = ioutil.ReadAll(fileContent)
if err != nil {
return err
}
f.Key = "" f.Key = ""
} else { } else {
err := f.putS3(fileContent) err := f.putS3(fileContent)

View file

@ -58,9 +58,21 @@ func getS3Session() (*session.Session, string, error) {
func createS3Session() (*session.Session, error) { func createS3Session() (*session.Session, error) {
endpoint, err := configuration.GlobalConfig.String("s3.endpoint") endpoint, err := configuration.GlobalConfig.String("s3.endpoint")
if err != nil {
return nil, err
}
region, err := configuration.GlobalConfig.String("s3.region") region, err := configuration.GlobalConfig.String("s3.region")
if err != nil {
return nil, err
}
pathStyle, err := configuration.GlobalConfig.Bool("s3.pathstyle") pathStyle, err := configuration.GlobalConfig.Bool("s3.pathstyle")
if err != nil {
return nil, err
}
nossl, err := configuration.GlobalConfig.Bool("s3.nossl") nossl, err := configuration.GlobalConfig.Bool("s3.nossl")
if err != nil {
return nil, err
}
sess, err := session.NewSession( sess, err := session.NewSession(
&aws.Config{ &aws.Config{
@ -134,6 +146,7 @@ func (f *File) getS3Url() (string, error) {
return urlStr, nil return urlStr, nil
} }
//lint:ignore U1000 will be used later
func (f *File) deleteS3() error { func (f *File) deleteS3() error {
// The session the S3 Uploader will use // The session the S3 Uploader will use

View file

@ -53,10 +53,10 @@ type ScenarioRequest struct {
func addScenario() (scenarioID uint) { func addScenario() (scenarioID uint) {
// authenticate as admin // authenticate as admin
token, _ := helper.AuthenticateForTest(router, database.AdminCredentials) _, _ = helper.AuthenticateForTest(router, database.AdminCredentials)
// authenticate as normal user // authenticate as normal user
token, _ = helper.AuthenticateForTest(router, database.UserACredentials) token, _ := helper.AuthenticateForTest(router, database.UserACredentials)
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
@ -70,7 +70,7 @@ func addScenario() (scenarioID uint) {
newScenarioID, _ := helper.GetResponseID(resp) newScenarioID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID) return uint(newScenarioID)
@ -81,7 +81,7 @@ func TestMain(m *testing.M) {
if err != nil { if err != nil {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }
@ -129,7 +129,7 @@ func TestAddFile(t *testing.T) {
// try to POST without a scenario ID // try to POST without a scenario ID
// should return a bad request error // should return a bad request error
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/files"), "POST", emptyBuf) "/api/v2/files", "POST", emptyBuf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
@ -304,6 +304,7 @@ func TestUpdateFile(t *testing.T) {
assert.Equalf(t, 200, w_updated.Code, "Response body: \n%v\n", w_updated.Body) assert.Equalf(t, 200, w_updated.Code, "Response body: \n%v\n", w_updated.Body)
newFileIDUpdated, err := helper.GetResponseID(w_updated.Body) newFileIDUpdated, err := helper.GetResponseID(w_updated.Body)
assert.NoError(t, err)
assert.Equal(t, newFileID, newFileIDUpdated) assert.Equal(t, newFileID, newFileIDUpdated)
@ -407,7 +408,7 @@ func TestDeleteFile(t *testing.T) {
// try to DELETE non-existing fileID // try to DELETE non-existing fileID
// should return not found // should return not found
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/files/5"), "DELETE", nil) "/api/v2/files/5", "DELETE", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
@ -473,7 +474,7 @@ func TestGetAllFilesOfScenario(t *testing.T) {
//try to get all files with missing scenario ID; should return a bad request error //try to get all files with missing scenario ID; should return a bad request error
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/files"), "GET", nil) "/api/v2/files", "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)

View file

@ -25,7 +25,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strings"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "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/database"
@ -62,8 +61,8 @@ func getHealth(c *gin.Context) {
} }
// check if connection to AMQP broker is alive if backend was started with AMQP client // check if connection to AMQP broker is alive if backend was started with AMQP client
url, err := configuration.GlobalConfig.String("amqp.host") url, err := configuration.GlobalConfig.StringOr("amqp.host", "not-set")
if err != nil && strings.Contains(err.Error(), "Required setting 'amqp.host' not set") { if err != nil && url == "not-set" {
c.JSON(http.StatusOK, gin.H{}) c.JSON(http.StatusOK, gin.H{})
return return
} else if err != nil { } else if err != nil {

View file

@ -41,7 +41,7 @@ func TestHealthz(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
// connect DB // connect DB
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
assert.NoError(t, err) assert.NoError(t, err)
defer database.DBpool.Close() defer database.DBpool.Close()
@ -60,7 +60,7 @@ func TestHealthz(t *testing.T) {
assert.Equalf(t, 500, code, "Response body: \n%v\n", resp) assert.Equalf(t, 500, code, "Response body: \n%v\n", resp)
// reconnect DB // reconnect DB
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
assert.NoError(t, err) assert.NoError(t, err)
defer database.DBpool.Close() defer database.DBpool.Close()

View file

@ -24,12 +24,12 @@ package infrastructure_component
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres" "github.com/jinzhu/gorm/dialects/postgres"
"github.com/streadway/amqp" "github.com/streadway/amqp"
"log"
"strings"
) )
type Action struct { type Action struct {
@ -91,7 +91,7 @@ func ProcessMessage(message amqp.Delivery) {
ICUUID := payload.Properties.UUID ICUUID := payload.Properties.UUID
_, err = uuid.Parse(ICUUID) _, err = uuid.Parse(ICUUID)
if err != nil { if err != nil {
log.Printf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) log.Printf("amqp: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body))
} }
var sToBeUpdated InfrastructureComponent var sToBeUpdated InfrastructureComponent
@ -108,7 +108,7 @@ func ProcessMessage(message amqp.Delivery) {
err = sToBeUpdated.updateExternalIC(payload, message.Body) err = sToBeUpdated.updateExternalIC(payload, message.Body)
} }
if err != nil { if err != nil {
log.Printf(err.Error()) log.Println(err.Error())
} }
} }
@ -186,7 +186,7 @@ func (s *InfrastructureComponent) updateExternalIC(payload ICUpdate, body []byte
if err != nil { if err != nil {
// if component could not be deleted there are still configurations using it in the DB // 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 // continue with the update to save the new state of the component and get back to the deletion later
if strings.Contains(err.Error(), "postponed") { if _, ok := err.(*DeletionPostponed); ok {
log.Println(err) // print log message log.Println(err) // print log message
} else { } else {
return err // return upon DB error return err // return upon DB error

View file

@ -25,23 +25,21 @@ package infrastructure_component
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"github.com/go-resty/resty/v2"
"github.com/jinzhu/gorm/dialects/postgres"
"log" "log"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"github.com/go-resty/resty/v2"
"github.com/jinzhu/gorm/dialects/postgres"
) )
func QueryICAPIs(d time.Duration) { func QueryICAPIs(d time.Duration) {
client := resty.New()
//client.SetDebug(true)
go func() { go func() {
for _ = range time.Tick(d) { for range time.Tick(d) {
//log.Println("Querying IC APIs at time:", x) //log.Println("Querying IC APIs at time:", x)
var err error var err error
@ -55,134 +53,143 @@ func QueryICAPIs(d time.Duration) {
// iterate over ICs in DB // iterate over ICs in DB
for _, ic := range ics { for _, ic := range ics {
err := queryIC(&ic)
if ic.ManagedExternally { if err != nil {
continue fmt.Println(err)
}
if ic.APIURL == "" || (!strings.HasPrefix(ic.APIURL, "http://") && !strings.HasPrefix(ic.APIURL, "https://")) {
continue
}
if ic.Category == "gateway" && ic.Type == "villas-node" {
log.Println("External API: checking for villas-node gateway", ic.Name)
statusResponse, err := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/status")
if err != nil {
log.Println("Error querying status of", ic.Name, err)
continue
}
var status map[string]interface{}
err = json.Unmarshal(statusResponse.Body(), &status)
if err != nil {
log.Println("Error unmarshalling status of", ic.Name, err)
continue
}
parts := strings.Split(ic.WebsocketURL, "/")
if len(parts) > 0 && parts[len(parts)-1] != "" {
configResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1])
statsResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1] + "/stats")
var config map[string]interface{}
err = json.Unmarshal(configResponse.Body(), &config)
if err == nil {
status["config"] = config
}
var stats map[string]interface{}
err = json.Unmarshal(statsResponse.Body(), &stats)
if err == nil {
status["statistics"] = stats
}
}
var updatedIC UpdateICRequest
statusRaw, _ := json.Marshal(status)
updatedIC.InfrastructureComponent.StatusUpdateRaw = postgres.Jsonb{RawMessage: statusRaw}
updatedIC.InfrastructureComponent.State = fmt.Sprintf("%v", status["state"])
updatedIC.InfrastructureComponent.UUID = fmt.Sprintf("%v", status["uuid"])
timeNow, myerr := strconv.ParseFloat(fmt.Sprintf("%v", status["time_now"]), 64)
if myerr != nil {
log.Println("Error parsing time_now to float", myerr.Error())
continue
}
timeStarted, myerr := strconv.ParseFloat(fmt.Sprintf("%v", status["time_started"]), 64)
if myerr != nil {
log.Println("Error parsing time_started to float", myerr.Error())
continue
}
uptime := timeNow - timeStarted
updatedIC.InfrastructureComponent.Uptime = uptime
// validate the update
err = updatedIC.validate()
if err != nil {
log.Println("Error validating updated villas-node gateway", ic.Name, ic.UUID, err.Error())
continue
}
// create the update and update IC in DB
var x InfrastructureComponent
err = x.ByID(ic.ID)
if err != nil {
log.Println("Error getting villas-node gateway by ID", ic.Name, err)
continue
}
u := updatedIC.updatedIC(x)
err = x.update(u)
if err != nil {
log.Println("Error updating villas-node gateway", ic.Name, ic.UUID, err.Error())
continue
}
} else if ic.Category == "manager" && ic.Type == "villas-relay" {
log.Println("External API: checking for villas-relay manager", ic.Name)
statusResponse, err := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL)
if err != nil {
log.Println("Error querying API of", ic.Name, err)
continue
}
var status map[string]interface{}
err = json.Unmarshal(statusResponse.Body(), &status)
if err != nil {
log.Println("Error unmarshalling status villas-relay manager", ic.Name, err)
continue
}
var updatedIC UpdateICRequest
statusRaw, _ := json.Marshal(status)
updatedIC.InfrastructureComponent.StatusUpdateRaw = postgres.Jsonb{RawMessage: statusRaw}
updatedIC.InfrastructureComponent.UUID = fmt.Sprintf("%v", status["uuid"])
// validate the update
err = updatedIC.validate()
if err != nil {
log.Println("Error validating updated villas-relay manager", ic.Name, ic.UUID, err.Error())
continue
}
// create the update and update IC in DB
var x InfrastructureComponent
err = x.ByID(ic.ID)
if err != nil {
log.Println("Error getting villas-relay manager by ID", ic.Name, err)
continue
}
u := updatedIC.updatedIC(x)
err = x.update(u)
if err != nil {
log.Println("Error updating villas-relay manager", ic.Name, ic.UUID, err.Error())
continue
}
} else if ic.Category == "gateway" && ic.Type == "villas-relay" {
// TODO add code here once API for VILLASrelay sessions is available
} }
} }
} }
}() }()
} }
func queryIC(ic *database.InfrastructureComponent) error {
if ic.ManagedExternally || ic.APIURL == "" || (!strings.HasPrefix(ic.APIURL, "http://") && !strings.HasPrefix(ic.APIURL, "https://")) {
return nil
}
if ic.Category == "gateway" {
if ic.Type == "villas-node" {
err := queryVillasNodeGateway(ic)
if err != nil {
return err
}
} else if ic.Type == "villas-relay" {
err := queryVillasRelayGateway(ic)
if err != nil {
return err
}
}
}
return nil
}
func queryVillasNodeGateway(ic *database.InfrastructureComponent) error {
client := resty.New()
log.Println("External API: checking for villas-node gateway", ic.Name)
statusResponse, err := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/status")
if err != nil {
return fmt.Errorf("failed to query the status of %s: %w", ic.Name, err)
}
var status map[string]interface{}
err = json.Unmarshal(statusResponse.Body(), &status)
if err != nil {
return fmt.Errorf("failed to unmarshal status of %s: %w", ic.Name, err)
}
parts := strings.Split(ic.WebsocketURL, "/")
if len(parts) > 0 && parts[len(parts)-1] != "" {
configResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1])
statsResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1] + "/stats")
var config map[string]interface{}
err = json.Unmarshal(configResponse.Body(), &config)
if err == nil {
status["config"] = config
}
var stats map[string]interface{}
err = json.Unmarshal(statsResponse.Body(), &stats)
if err == nil {
status["statistics"] = stats
}
}
var updatedIC UpdateICRequest
statusRaw, _ := json.Marshal(status)
updatedIC.InfrastructureComponent.StatusUpdateRaw = postgres.Jsonb{RawMessage: statusRaw}
updatedIC.InfrastructureComponent.State = fmt.Sprintf("%v", status["state"])
updatedIC.InfrastructureComponent.UUID = fmt.Sprintf("%v", status["uuid"])
timeNow, err := strconv.ParseFloat(fmt.Sprintf("%v", status["time_now"]), 64)
if err != nil {
return fmt.Errorf("failed to parse time_now to float: %w", err)
}
timeStarted, err := strconv.ParseFloat(fmt.Sprintf("%v", status["time_started"]), 64)
if err != nil {
return fmt.Errorf("failed to parse time_started to float: %w", err)
}
uptime := timeNow - timeStarted
updatedIC.InfrastructureComponent.Uptime = uptime
// validate the update
err = updatedIC.validate()
if err != nil {
return fmt.Errorf("failed to validate updated villas-node gateway: %s (%s): %w", ic.Name, ic.UUID, err)
}
// create the update and update IC in DB
var x InfrastructureComponent
err = x.ByID(ic.ID)
if err != nil {
return fmt.Errorf("failed to get villas-node gateway by ID %s (%s): %w", ic.Name, ic.UUID, err)
}
u := updatedIC.updatedIC(x)
err = x.update(u)
if err != nil {
return fmt.Errorf("failed to update villas-node gateway %s (%s): %w", ic.Name, ic.UUID, err)
}
return nil
}
func queryVillasRelayGateway(ic *database.InfrastructureComponent) error {
client := resty.New()
log.Println("External API: checking for villas-relay manager", ic.Name)
statusResponse, err := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL)
if err != nil {
return fmt.Errorf("failed querying API of %s (%s): %w", ic.Name, ic.UUID, err)
}
var status map[string]interface{}
err = json.Unmarshal(statusResponse.Body(), &status)
if err != nil {
return fmt.Errorf("failed to unmarshal status villas-relay manager %s (%s): %w", ic.Name, ic.UUID, err)
}
var updatedIC UpdateICRequest
statusRaw, _ := json.Marshal(status)
updatedIC.InfrastructureComponent.StatusUpdateRaw = postgres.Jsonb{RawMessage: statusRaw}
updatedIC.InfrastructureComponent.UUID = fmt.Sprintf("%v", status["uuid"])
// validate the update
err = updatedIC.validate()
if err != nil {
return fmt.Errorf("failed to validate updated villas-relay manager %s (%s): %w", ic.Name, ic.UUID, err)
}
// create the update and update IC in DB
var x InfrastructureComponent
err = x.ByID(ic.ID)
if err != nil {
return fmt.Errorf("failed to get villas-relay manager by ID %s (%s): %w", ic.Name, ic.UUID, err)
}
u := updatedIC.updatedIC(x)
err = x.update(u)
if err != nil {
return fmt.Errorf("failed to update villas-relay manager %s (%s): %w", ic.Name, ic.UUID, err)
}
return nil
}

View file

@ -105,7 +105,7 @@ func addIC(c *gin.Context) {
} }
// Check if IC to be created is managed externally // Check if IC to be created is managed externally
if *req.InfrastructureComponent.ManagedExternally == true { if *req.InfrastructureComponent.ManagedExternally {
// if so: refuse creation // if so: refuse creation
helper.BadRequestError(c, "create for externally managed IC not possible with this endpoint - use /ic/{ICID}/action endpoint instead to request creation of the component") helper.BadRequestError(c, "create for externally managed IC not possible with this endpoint - use /ic/{ICID}/action endpoint instead to request creation of the component")
return return

View file

@ -0,0 +1,32 @@
/** User package, methods.
*
* @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 "fmt"
type DeletionPostponed struct {
References int
}
func (e *DeletionPostponed) Error() string {
return fmt.Sprintf("deletion of IC postponed, %d config(s) associated to it", e.References)
}

View file

@ -22,7 +22,6 @@
package infrastructure_component package infrastructure_component
import ( import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
) )
@ -68,7 +67,7 @@ func (s *InfrastructureComponent) delete() error {
noConfigs := db.Model(s).Association("ComponentConfigurations").Count() noConfigs := db.Model(s).Association("ComponentConfigurations").Count()
if noConfigs > 0 { if noConfigs > 0 {
return fmt.Errorf("deletion of IC postponed, %v config(s) associated to it", noConfigs) return &DeletionPostponed{References: noConfigs}
} }
// delete InfrastructureComponent from DB (does NOT remain as dangling) // delete InfrastructureComponent from DB (does NOT remain as dangling)

View file

@ -84,8 +84,8 @@ var newIC1 = ICRequest{
State: "idle", State: "idle",
Location: "k8s", Location: "k8s",
Description: "A signal generator for testing purposes", Description: "A signal generator for testing purposes",
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"startprop1" : "a nice prop"}`)}, StartParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`)},
CreateParameterSchema: postgres.Jsonb{json.RawMessage(`{"createprop1" : "a really nice prop"}`)}, CreateParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
ManagedExternally: newFalse(), ManagedExternally: newFalse(),
Manager: "7be0322d-354e-431e-84bd-ae4c9633beef", Manager: "7be0322d-354e-431e-84bd-ae4c9633beef",
} }
@ -100,8 +100,8 @@ var newIC2 = ICRequest{
State: "running", State: "running",
Location: "k8s", Location: "k8s",
Description: "This is a test description", Description: "This is a test description",
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"startprop1" : "a nice prop"}`)}, StartParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`)},
CreateParameterSchema: postgres.Jsonb{json.RawMessage(`{"createprop1" : "a really nice prop"}`)}, CreateParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
ManagedExternally: newTrue(), ManagedExternally: newTrue(),
Manager: "4854af30-325f-44a5-ad59-b67b2597de99", Manager: "4854af30-325f-44a5-ad59-b67b2597de99",
} }
@ -112,7 +112,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }
@ -132,9 +132,9 @@ func TestMain(m *testing.M) {
// connect AMQP client // connect AMQP client
// Make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set // Make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set
host, err := configuration.GlobalConfig.String("amqp.host") host, _ := configuration.GlobalConfig.String("amqp.host")
usr, err := configuration.GlobalConfig.String("amqp.user") usr, _ := configuration.GlobalConfig.String("amqp.user")
pass, err := configuration.GlobalConfig.String("amqp.pass") pass, _ := configuration.GlobalConfig.String("amqp.pass")
amqpURI := "amqp://" + usr + ":" + pass + "@" + host amqpURI := "amqp://" + usr + ":" + pass + "@" + host
// AMQP Connection startup is tested here // AMQP Connection startup is tested here
@ -584,7 +584,7 @@ func TestSendActionToIC(t *testing.T) {
var params startParams var params startParams
params.UUID = newIC1.UUID params.UUID = newIC1.UUID
paramsRaw, err := json.Marshal(&params) paramsRaw, _ := json.Marshal(&params)
action1.Parameters = paramsRaw action1.Parameters = paramsRaw
actions := [1]Action{action1} actions := [1]Action{action1}
@ -738,11 +738,13 @@ func TestDeleteICViaAMQPRecv(t *testing.T) {
// Add component config and associate with IC and scenario // Add component config and associate with IC and scenario
newConfig := ConfigRequest{ newConfig := ConfigRequest{
Name: "ConfigA", Name: "ConfigA",
ScenarioID: uint(newScenarioID), ScenarioID: uint(newScenarioID),
ICID: 1, ICID: 1,
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`)}, StartParameters: postgres.Jsonb{
FileIDs: []int64{}, RawMessage: json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`),
},
FileIDs: []int64{},
} }
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,

View file

@ -88,7 +88,7 @@ func (r *AddICRequest) validate() error {
return errs return errs
} }
if *r.InfrastructureComponent.ManagedExternally == true { if *r.InfrastructureComponent.ManagedExternally {
// check if valid manager UUID is provided // check if valid manager UUID is provided
_, errs = uuid.Parse(r.InfrastructureComponent.Manager) _, errs = uuid.Parse(r.InfrastructureComponent.Manager)
if errs != nil { if errs != nil {
@ -119,8 +119,7 @@ func (r *UpdateICRequest) validate() error {
func (r *AddICRequest) createIC() (InfrastructureComponent, error) { func (r *AddICRequest) createIC() (InfrastructureComponent, error) {
var s InfrastructureComponent var s InfrastructureComponent
var err error var err error = nil
err = nil
s.UUID = r.InfrastructureComponent.UUID s.UUID = r.InfrastructureComponent.UUID
s.WebsocketURL = r.InfrastructureComponent.WebsocketURL s.WebsocketURL = r.InfrastructureComponent.WebsocketURL

View file

@ -311,7 +311,7 @@ func AddTestData(cfg *config.Config, router *gin.Engine) (*bytes.Buffer, error)
defer fh.Close() defer fh.Close()
// io copy // io copy
_, err = io.Copy(fileWriter, fh) _, _ = io.Copy(fileWriter, fh)
contentType := bodyWriter.FormDataContentType() contentType := bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()
@ -338,7 +338,7 @@ func AddTestData(cfg *config.Config, router *gin.Engine) (*bytes.Buffer, error)
defer fh.Close() defer fh.Close()
// io copy // io copy
_, err = io.Copy(fileWriter, fh) _, _ = io.Copy(fileWriter, fh)
contentType := bodyWriter.FormDataContentType() contentType := bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()

View file

@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }

View file

@ -23,9 +23,10 @@
package result package result
import ( import (
"log"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/file"
"log"
) )
type Result struct { type Result struct {
@ -91,6 +92,9 @@ func (r *Result) delete() error {
// remove association between Result and Scenario // remove association between Result and Scenario
err = db.Model(&sco).Association("Results").Delete(r).Error err = db.Model(&sco).Association("Results").Delete(r).Error
if err != nil {
return err
}
// Delete result files // Delete result files
for _, fileid := range r.ResultFileIDs { for _, fileid := range r.ResultFileIDs {

View file

@ -70,10 +70,10 @@ var newResult = ResultRequest{
func addScenario() (scenarioID uint) { func addScenario() (scenarioID uint) {
// authenticate as admin // authenticate as admin
token, _ := helper.AuthenticateForTest(router, database.AdminCredentials) _, _ = helper.AuthenticateForTest(router, database.AdminCredentials)
// authenticate as normal user // authenticate as normal user
token, _ = helper.AuthenticateForTest(router, database.UserACredentials) token, _ := helper.AuthenticateForTest(router, database.UserACredentials)
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
@ -87,7 +87,7 @@ func addScenario() (scenarioID uint) {
newScenarioID, _ := helper.GetResponseID(resp) newScenarioID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID) return uint(newScenarioID)
@ -98,7 +98,7 @@ func TestMain(m *testing.M) {
if err != nil { if err != nil {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }
@ -135,7 +135,9 @@ func TestGetAllResultsOfScenario(t *testing.T) {
// test POST newResult // test POST newResult
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`) configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
confSnapshots := postgres.Jsonb{configSnapshot1} confSnapshots := postgres.Jsonb{
RawMessage: configSnapshot1,
}
newResult.ScenarioID = scenarioID newResult.ScenarioID = scenarioID
newResult.ConfigSnapshots = confSnapshots newResult.ConfigSnapshots = confSnapshots
@ -174,7 +176,9 @@ func TestAddGetUpdateDeleteResult(t *testing.T) {
// by adding a scenario // by adding a scenario
scenarioID := addScenario() scenarioID := addScenario()
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`) configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
confSnapshots := postgres.Jsonb{configSnapshot1} confSnapshots := postgres.Jsonb{
RawMessage: configSnapshot1,
}
newResult.ScenarioID = scenarioID newResult.ScenarioID = scenarioID
newResult.ConfigSnapshots = confSnapshots newResult.ConfigSnapshots = confSnapshots
// authenticate as normal userB who has no access to new scenario // authenticate as normal userB who has no access to new scenario
@ -347,7 +351,9 @@ func TestAddDeleteResultFile(t *testing.T) {
// by adding a scenario // by adding a scenario
scenarioID := addScenario() scenarioID := addScenario()
configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`) configSnapshot1 := json.RawMessage(`{"configs": [ {"Name" : "conf1", "scenarioID" : 1}, {"Name" : "conf2", "scenarioID" : 1}]}`)
confSnapshots := postgres.Jsonb{configSnapshot1} confSnapshots := postgres.Jsonb{
RawMessage: configSnapshot1,
}
newResult.ScenarioID = scenarioID newResult.ScenarioID = scenarioID
newResult.ConfigSnapshots = confSnapshots newResult.ConfigSnapshots = confSnapshots
@ -404,6 +410,7 @@ func TestAddDeleteResultFile(t *testing.T) {
assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body) assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body)
err = helper.CompareResponse(w.Body, helper.KeyModels{"result": newResult}) err = helper.CompareResponse(w.Body, helper.KeyModels{"result": newResult})
assert.NoError(t, err)
// extract file ID from response body // extract file ID from response body
var respResult ResponseResult var respResult ResponseResult
@ -455,6 +462,7 @@ func TestAddDeleteResultFile(t *testing.T) {
assert.Equalf(t, 200, w2.Code, "Response body: \n%v\n", w2.Body) assert.Equalf(t, 200, w2.Code, "Response body: \n%v\n", w2.Body)
err = helper.CompareResponse(w2.Body, helper.KeyModels{"result": newResult}) err = helper.CompareResponse(w2.Body, helper.KeyModels{"result": newResult})
assert.NoError(t, err)
// extract file ID from response body // extract file ID from response body
var respResult3 ResponseResult var respResult3 ResponseResult

View file

@ -23,6 +23,7 @@ package scenario
import ( import (
"fmt" "fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
) )
@ -104,6 +105,9 @@ func (s *Scenario) deleteUser(username string) error {
// There is only one associated user // There is only one associated user
var remainingUser database.User var remainingUser database.User
err = db.Model(s).Related(&remainingUser, "Users").Error err = db.Model(s).Related(&remainingUser, "Users").Error
if err != nil {
return err
}
if remainingUser.Username == username { if remainingUser.Username == username {
// if the remaining user is the one to be deleted // if the remaining user is the one to be deleted
return fmt.Errorf("cannot delete last user from scenario without deleting scenario itself, doing nothing") return fmt.Errorf("cannot delete last user from scenario without deleting scenario itself, doing nothing")

View file

@ -53,15 +53,19 @@ type UserRequest struct {
} }
var newScenario1 = ScenarioRequest{ var newScenario1 = ScenarioRequest{
Name: "Scenario1", Name: "Scenario1",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, StartParameters: postgres.Jsonb{
IsLocked: false, RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`),
},
IsLocked: false,
} }
var newScenario2 = ScenarioRequest{ var newScenario2 = ScenarioRequest{
Name: "Scenario2", Name: "Scenario2",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`)}, StartParameters: postgres.Jsonb{
IsLocked: false, RawMessage: json.RawMessage(`{"parameter1" : "testValue1B", "parameter2" : "testValue2B", "parameter3" : 55}`),
},
IsLocked: false,
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -70,7 +74,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }

View file

@ -35,14 +35,14 @@ func (s *Signal) save() error {
return err return err
} }
func (s *Signal) byID(id uint) error { /*func (s *Signal) byID(id uint) error {
db := database.GetDB() db := database.GetDB()
err := db.Find(s, id).Error err := db.Find(s, id).Error
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }*/
func (s *Signal) AddToConfig() error { func (s *Signal) AddToConfig() error {
db := database.GetDB() db := database.GetDB()

View file

@ -95,17 +95,21 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) {
// POST $newICA // POST $newICA
newICA := ICRequest{ newICA := ICRequest{
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig", WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
Type: "villas-node", Type: "villas-node",
Name: "ACS Demo Signals", Name: "ACS Demo Signals",
Category: "gateway", Category: "gateway",
State: "idle", State: "idle",
Location: "k8s", Location: "k8s",
Description: "A signal generator for testing purposes", Description: "A signal generator for testing purposes",
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"startprop1" : "a nice prop"}`)}, StartParameterSchema: postgres.Jsonb{
CreateParameterSchema: postgres.Jsonb{json.RawMessage(`{"createprop1" : "a really nice prop"}`)}, RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`),
ManagedExternally: newFalse(), },
CreateParameterSchema: postgres.Jsonb{
RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`),
},
ManagedExternally: newFalse(),
} }
_, resp, _ := helper.TestEndpoint(router, token, _, resp, _ := helper.TestEndpoint(router, token,
"/api/v2/ic", "POST", helper.KeyModels{"ic": newICA}) "/api/v2/ic", "POST", helper.KeyModels{"ic": newICA})
@ -118,8 +122,10 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) {
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
Name: "Scenario1", Name: "Scenario1",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, StartParameters: postgres.Jsonb{
RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`),
},
} }
_, resp, _ = helper.TestEndpoint(router, token, _, resp, _ = helper.TestEndpoint(router, token,
"/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) "/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
@ -141,7 +147,7 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) {
newConfigID, _ := helper.GetResponseID(resp) newConfigID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID), uint(newICID), uint(newConfigID) return uint(newScenarioID), uint(newICID), uint(newConfigID)
@ -153,7 +159,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }
@ -189,12 +195,12 @@ func TestAddSignal(t *testing.T) {
_, _, configID := addScenarioAndICAndConfig() _, _, configID := addScenarioAndICAndConfig()
// authenticate as normal user // authenticate as normal user
token, err := helper.AuthenticateForTest(router, database.UserACredentials) _, err := helper.AuthenticateForTest(router, database.UserACredentials)
assert.NoError(t, err) assert.NoError(t, err)
newSignal1.ConfigID = configID newSignal1.ConfigID = configID
// authenticate as normal userB who has no access to new scenario // authenticate as normal userB who has no access to new scenario
token, err = helper.AuthenticateForTest(router, database.UserBCredentials) token, err := helper.AuthenticateForTest(router, database.UserBCredentials)
assert.NoError(t, err) assert.NoError(t, err)
// try to POST to component config without access // try to POST to component config without access

View file

@ -65,7 +65,7 @@ func duplicateScenarioForUser(s database.Scenario, user *database.User) <-chan e
if ic.Category == "simulator" && ic.Type == "kubernetes" { if ic.Category == "simulator" && ic.Type == "kubernetes" {
duplicateUUID, err := duplicateIC(ic, user.Username) duplicateUUID, err := duplicateIC(ic, user.Username)
if err != nil { if err != nil {
errs <- fmt.Errorf("Duplication of IC (id=%d) unsuccessful, err: %s", icID, err) errs <- fmt.Errorf("duplication of IC (id=%d) unsuccessful, err: %s", icID, err)
continue continue
} }
@ -278,6 +278,9 @@ func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint
// duplication of signals // duplication of signals
var sigs []database.Signal var sigs []database.Signal
err = db.Order("ID asc").Model(&m).Related(&sigs, "OutputMapping").Error err = db.Order("ID asc").Model(&m).Related(&sigs, "OutputMapping").Error
if err != nil {
return err
}
smap := *signalMap smap := *signalMap
for _, s := range sigs { for _, s := range sigs {
var sigDup database.Signal var sigDup database.Signal

View file

@ -24,7 +24,6 @@ package user
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strings"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
@ -188,9 +187,9 @@ func updateUser(c *gin.Context) {
// case that the request updates the role of the old user) // case that the request updates the role of the old user)
updatedUser, err := req.updatedUser(callerID, callerRole, oldUser) updatedUser, err := req.updatedUser(callerID, callerRole, oldUser)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "Admin") || strings.Contains(err.Error(), "pw not changed") { if _, ok := err.(*ForbiddenError); ok {
helper.ForbiddenError(c, err.Error()) helper.ForbiddenError(c, err.Error())
} else if strings.Contains(err.Error(), "Username") || strings.Contains(err.Error(), "old or admin password") { } else if _, ok := err.(*UsernameAlreadyTaken); ok {
helper.BadRequestError(c, err.Error()) helper.BadRequestError(c, err.Error())
} else { // password encryption failed } else { // password encryption failed
helper.InternalServerError(c, err.Error()) helper.InternalServerError(c, err.Error())

View file

@ -0,0 +1,40 @@
/** User package, methods.
*
* @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 user
import "fmt"
type UsernameAlreadyTaken struct {
Username string
}
func (e *UsernameAlreadyTaken) Error() string {
return fmt.Sprintf("username is already taken: %s", e.Username)
}
type ForbiddenError struct {
Reason string
}
func (e *ForbiddenError) Error() string {
return fmt.Sprintf("permission denied: %s", e.Reason)
}

View file

@ -43,7 +43,7 @@ func NewUser(username, password, mail, role string, active bool) (User, error) {
// Check that the username is NOT taken // Check that the username is NOT taken
err := newUser.byUsername(username) err := newUser.byUsername(username)
if err == nil { if err == nil {
return newUser, fmt.Errorf("Username is already taken") return newUser, &UsernameAlreadyTaken{Username: username}
} }
newUser.Username = username newUser.Username = username
@ -97,12 +97,12 @@ func (u *User) byID(id uint) error {
func (u *User) setPassword(password string) error { func (u *User) setPassword(password string) error {
if len(password) == 0 { if len(password) == 0 {
return fmt.Errorf("Password cannot be empty") return fmt.Errorf("password cannot be empty")
} }
newPassword, err := newPassword, err :=
bcrypt.GenerateFromPassword([]byte(password), bcryptCost) bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
if err != nil { if err != nil {
return fmt.Errorf("Failed to generate hash from password") return fmt.Errorf("failed to generate hash from password")
} }
u.Password = string(newPassword) u.Password = string(newPassword)
return nil return nil

View file

@ -62,7 +62,7 @@ func isAuthenticated(c *gin.Context) (bool, error) {
func(token *jwt.Token) (interface{}, error) { func(token *jwt.Token) (interface{}, error) {
// Validate alg for signing the jwt // Validate alg for signing the jwt
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing alg: %v", return nil, fmt.Errorf("unexpected signing alg: %v",
token.Header["alg"]) token.Header["alg"])
} }

View file

@ -66,11 +66,11 @@ type UserRequest struct {
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
err := configuration.InitConfig() err := configuration.InitConfig()
if err != nil { if err != nil {
panic(m) panic(err)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(err)
} }
defer database.DBpool.Close() defer database.DBpool.Close()
@ -93,7 +93,7 @@ func TestMain(m *testing.M) {
func TestAuthenticate(t *testing.T) { func TestAuthenticate(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// try to authenticate with non JSON body // try to authenticate with non JSON body
@ -190,7 +190,7 @@ func TestAuthenticateQueryToken(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -211,7 +211,7 @@ func TestAddGetUser(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -326,7 +326,7 @@ func TestAddGetUser(t *testing.T) {
// try to GET user with invalid user ID // try to GET user with invalid user ID
// should result in bad request // should result in bad request
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/users/bla"), "GET", nil) "/api/v2/users/bla", "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
} }
@ -335,7 +335,7 @@ func TestUsersNotAllowedActions(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -394,7 +394,7 @@ func TestGetAllUsers(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -447,7 +447,7 @@ func TestModifyAddedUserAsUser(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -478,7 +478,7 @@ func TestModifyAddedUserAsUser(t *testing.T) {
// Try PUT with invalid user ID in path // Try PUT with invalid user ID in path
// Should return a bad request // Should return a bad request
code, resp, err = helper.TestEndpoint(router, token, fmt.Sprintf("/api/v2/users/blabla"), "PUT", code, resp, err = helper.TestEndpoint(router, token, "/api/v2/users/blabla", "PUT",
helper.KeyModels{"user": newUser}) helper.KeyModels{"user": newUser})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
@ -559,7 +559,7 @@ func TestModifyAddedUserAsUser(t *testing.T) {
fmt.Sprintf("/api/v2/users/%v", newUserID), "PUT", fmt.Sprintf("/api/v2/users/%v", newUserID), "PUT",
helper.KeyModels{"user": modRequest}) helper.KeyModels{"user": modRequest})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 403, code, "Response body: \n%v\n", resp)
// modify newUser's password with wring old password // modify newUser's password with wring old password
modRequest = UserRequest{ modRequest = UserRequest{
@ -602,7 +602,7 @@ func TestInvalidUserUpdate(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -668,14 +668,13 @@ func TestInvalidUserUpdate(t *testing.T) {
helper.KeyModels{"user": modRequest}) helper.KeyModels{"user": modRequest})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
} }
func TestModifyAddedUserAsAdmin(t *testing.T) { func TestModifyAddedUserAsAdmin(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -744,7 +743,7 @@ func TestModifyAddedUserAsAdmin(t *testing.T) {
fmt.Sprintf("/api/v2/users/%v", newUserID), "PUT", fmt.Sprintf("/api/v2/users/%v", newUserID), "PUT",
helper.KeyModels{"user": modRequest}) helper.KeyModels{"user": modRequest})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 403, code, "Response body: \n%v\n", resp)
// modify newUser's password, requires admin password // modify newUser's password, requires admin password
modRequest = UserRequest{ modRequest = UserRequest{
@ -792,7 +791,7 @@ func TestDeleteUser(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
err, adminpw := database.DBAddAdminUser(configuration.GlobalConfig) adminpw, err := database.DBAddAdminUser(configuration.GlobalConfig)
assert.NoError(t, err) assert.NoError(t, err)
// authenticate as admin // authenticate as admin
@ -817,7 +816,7 @@ func TestDeleteUser(t *testing.T) {
// try to DELETE with invalid ID // try to DELETE with invalid ID
// should result in bad request // should result in bad request
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/users/bla"), "DELETE", nil) "/api/v2/users/bla", "DELETE", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)

View file

@ -82,16 +82,16 @@ func (r *updateUserRequest) updatedUser(callerID interface{}, role interface{},
// Only the Admin must be able to update user's role // Only the Admin must be able to update user's role
if role != "Admin" && r.User.Role != "" { if role != "Admin" && r.User.Role != "" {
if r.User.Role != u.Role { if r.User.Role != u.Role {
return u, fmt.Errorf("Only Admin can update user's Role") return u, &ForbiddenError{Reason: "only Admin can update user's Role"}
} }
} else if role == "Admin" && r.User.Role != "" { } else if role == "Admin" && r.User.Role != "" {
u.Role = r.User.Role u.Role = r.User.Role
} }
// Only the Admin must be able to update users Active state // Only the Admin must be able to update users Active state
if (r.User.Active == "yes" && u.Active == false) || (r.User.Active == "no" && u.Active == true) { if (r.User.Active == "yes" && !u.Active) || (r.User.Active == "no" && u.Active) {
if role != "Admin" { if role != "Admin" {
return u, fmt.Errorf("Only Admin can update user's Active state") return u, &ForbiddenError{Reason: "only Admin can update user's Active state"}
} else { } else {
u.Active = !u.Active u.Active = !u.Active
} }
@ -100,7 +100,7 @@ func (r *updateUserRequest) updatedUser(callerID interface{}, role interface{},
// Update the username making sure it is NOT taken // Update the username making sure it is NOT taken
var testUser User var testUser User
if err := testUser.byUsername(r.User.Username); err == nil { if err := testUser.byUsername(r.User.Username); err == nil {
return u, fmt.Errorf("Username is alreaday taken") return u, &UsernameAlreadyTaken{Username: r.User.Username}
} }
if r.User.Username != "" { if r.User.Username != "" {
@ -111,7 +111,7 @@ func (r *updateUserRequest) updatedUser(callerID interface{}, role interface{},
if r.User.Password != "" { if r.User.Password != "" {
if r.User.OldPassword == "" { // admin or old password has to be present for pw change if r.User.OldPassword == "" { // admin or old password has to be present for pw change
return u, fmt.Errorf("old or admin password is missing in request") return u, &ForbiddenError{Reason: "missing old or admin password"}
} }
if role == "Admin" { // admin has to enter admin password if role == "Admin" { // admin has to enter admin password
@ -123,14 +123,14 @@ func (r *updateUserRequest) updatedUser(callerID interface{}, role interface{},
err = adminUser.validatePassword(r.User.OldPassword) err = adminUser.validatePassword(r.User.OldPassword)
if err != nil { if err != nil {
return u, fmt.Errorf("admin password not correct, pw not changed") return u, &ForbiddenError{Reason: "admin password not correct, pw not changed"}
} }
} else { //normal or guest user has to enter old password } else { //normal or guest user has to enter old password
err := oldUser.validatePassword(r.User.OldPassword) err := oldUser.validatePassword(r.User.OldPassword)
if err != nil { if err != nil {
return u, fmt.Errorf("previous password not correct, pw not changed") return u, &ForbiddenError{Reason: "previous password not correct, pw not changed"}
} }
} }

View file

@ -86,8 +86,10 @@ func addScenarioAndDashboard(token string) (scenarioID uint, dashboardID uint) {
// POST $newScenario // POST $newScenario
newScenario := ScenarioRequest{ newScenario := ScenarioRequest{
Name: "Scenario1", Name: "Scenario1",
StartParameters: postgres.Jsonb{json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)}, StartParameters: postgres.Jsonb{
RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`),
},
} }
_, resp, _ := helper.TestEndpoint(router, token, _, resp, _ := helper.TestEndpoint(router, token,
"/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) "/api/v2/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
@ -108,7 +110,7 @@ func addScenarioAndDashboard(token string) (scenarioID uint, dashboardID uint) {
newDashboardID, _ := helper.GetResponseID(resp) newDashboardID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, _, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/v2/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID), uint(newDashboardID) return uint(newScenarioID), uint(newDashboardID)
@ -120,7 +122,7 @@ func TestMain(m *testing.M) {
panic(m) panic(m)
} }
err = database.InitDB(configuration.GlobalConfig, "true") err = database.InitDB(configuration.GlobalConfig, true)
if err != nil { if err != nil {
panic(m) panic(m)
} }

View file

@ -103,7 +103,7 @@ func main() {
} }
// Init database // Init database
err = database.InitDB(configuration.GlobalConfig, dbClear) err = database.InitDB(configuration.GlobalConfig, dbClear == "true")
if err != nil { if err != nil {
log.Fatalf("Error during initialization of database: %s, aborting.", err) log.Fatalf("Error during initialization of database: %s, aborting.", err)
} }
@ -142,7 +142,7 @@ func main() {
} }
// Make sure that at least one admin user exists in DB // Make sure that at least one admin user exists in DB
err, _ = database.DBAddAdminUser(configuration.GlobalConfig) _, err = database.DBAddAdminUser(configuration.GlobalConfig)
if err != nil { if err != nil {
fmt.Println("error: adding admin user failed:", err.Error()) fmt.Println("error: adding admin user failed:", err.Error())
log.Fatal(err) log.Fatal(err)