mirror of
https://git.rwth-aachen.de/acs/public/villas/web-backend-go/
synced 2025-03-30 00:00:12 +01:00
Fix duplication: synchronous duplication, proper duplication of widgets which use IDs, reuse duplicated IC (if alive)
This commit is contained in:
parent
61ead46392
commit
4db2f91ea5
4 changed files with 569 additions and 156 deletions
|
@ -100,5 +100,7 @@ deploy:
|
|||
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
|
||||
--destination ${DOCKER_IMAGE}:${DOCKER_TAG}
|
||||
--snapshotMode=redo
|
||||
--single-snapshot
|
||||
dependencies:
|
||||
- test
|
||||
|
||||
|
|
|
@ -287,10 +287,7 @@ func authenticateExternal(c *gin.Context) (User, error) {
|
|||
}
|
||||
|
||||
if groupedScenario.Duplicate {
|
||||
if err := <-duplicateScenarioForUser(so, &myUser.User, ""); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
duplicateScenarioForUser(so, &myUser.User, "")
|
||||
} else { // add user to scenario
|
||||
err = db.Model(&so).Association("Users").Append(&(myUser.User)).Error
|
||||
if err != nil {
|
||||
|
|
|
@ -27,16 +27,15 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jinzhu/gorm/dialects/postgres"
|
||||
)
|
||||
|
||||
func duplicateScenarioForUser(s database.Scenario, user *database.User, uuidstr string) <-chan error {
|
||||
errs := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
func duplicateScenarioForUser(s database.Scenario, user *database.User, uuidstr string) {
|
||||
|
||||
// get all component configs of the scenario
|
||||
db := database.GetDB()
|
||||
|
@ -46,12 +45,15 @@ func duplicateScenarioForUser(s database.Scenario, user *database.User, uuidstr
|
|||
log.Printf("Warning: scenario to duplicate (id=%d) has no component configurations", s.ID)
|
||||
}
|
||||
|
||||
// iterate over component configs to check for ICs to duplicate
|
||||
icIdmap := make(map[uint]uint) // key: original IC id, value: duplicated IC id
|
||||
duplicatedICuuids := make(map[uint]string) // key: original icID; value: UUID of duplicate
|
||||
var externalUUIDs []string // external ICs to wait for
|
||||
|
||||
// iterate over component configs to check for ICs to duplicate
|
||||
for _, config := range configs {
|
||||
icID := config.ICID
|
||||
if duplicatedICuuids[icID] != "" { // this IC was already added
|
||||
|
||||
if _, ok := duplicatedICuuids[icID]; ok { // this IC was already added
|
||||
log.Println("IC already added while ranging configs")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -63,66 +65,71 @@ func duplicateScenarioForUser(s database.Scenario, user *database.User, uuidstr
|
|||
continue
|
||||
}
|
||||
|
||||
alreadyDuplicated, dupID := isICalreadyDuplicated(ic, user.Username)
|
||||
// create new kubernetes simulator OR use existing IC
|
||||
if ic.Category == "simulator" && ic.Type == "kubernetes" {
|
||||
if ic.Category == "simulator" && ic.Type == "kubernetes" && !alreadyDuplicated {
|
||||
duplicateUUID, err := duplicateIC(ic, user.Username, uuidstr)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("duplication of IC (id=%d) unsuccessful, err: %s", icID, err)
|
||||
log.Printf("duplication of IC (id=%d) unsuccessful, err: %s", icID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
duplicatedICuuids[ic.ID] = duplicateUUID
|
||||
externalUUIDs = append(externalUUIDs, duplicateUUID)
|
||||
} else if alreadyDuplicated {
|
||||
duplicatedICuuids[ic.ID] = "alreadyduplicated"
|
||||
icIdmap[ic.ID] = dupID
|
||||
err = nil
|
||||
} else { // use existing IC
|
||||
duplicatedICuuids[ic.ID] = ""
|
||||
duplicatedICuuids[ic.ID] = "useoriginal"
|
||||
icIdmap[ic.ID] = ic.ID
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// copy scenario after all new external ICs are in DB
|
||||
icsToWaitFor := len(externalUUIDs)
|
||||
icsToWaitFor := len(duplicatedICuuids)
|
||||
var timeout = 20 // seconds
|
||||
|
||||
for i := 0; i < timeout; i++ {
|
||||
// duplicate scenario after all duplicated ICs have been found in the DB
|
||||
if icsToWaitFor == 0 {
|
||||
err := duplicateScenario(s, duplicatedICuuids, user)
|
||||
log.Printf("Number of ICs to wait for: %d", icsToWaitFor)
|
||||
if icsToWaitFor <= 0 {
|
||||
err := duplicateScenario(s, icIdmap, user)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("duplicate scenario %v fails with error %v", s.Name, err.Error())
|
||||
log.Printf("duplicate scenario %v fails with error %v", s.Name, err.Error())
|
||||
}
|
||||
|
||||
close(errs)
|
||||
return
|
||||
} else {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// check for new ICs with previously created UUIDs
|
||||
for _, uuid_r := range externalUUIDs {
|
||||
if uuid_r == "" {
|
||||
for icId, uuid_r := range duplicatedICuuids {
|
||||
if uuid_r == "alreadyduplicated" || uuid_r == "useoriginal" {
|
||||
icsToWaitFor--
|
||||
continue
|
||||
}
|
||||
log.Printf("Looking for duplicated IC with UUID %s", uuid_r)
|
||||
|
||||
// a new IC is searched by UUID, otherwise by string comparison of IC name
|
||||
// this makes sense in testing and also in reality: if an external IC was requested
|
||||
// it should be checked for, checking shouldn't be omitted due to old duplicated IC
|
||||
var duplicatedIC database.InfrastructureComponent
|
||||
err = db.Find(&duplicatedIC, "UUID = ?", uuid_r).Error
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("Error looking up duplicated IC: %s", err)
|
||||
log.Printf("Didn't find duplicated IC: %s, keep waiting for it..", err)
|
||||
} else {
|
||||
log.Printf("Found duplicated IC! Original IC id: %d, duplicated IC id: %d", icId, duplicatedIC.ID)
|
||||
icsToWaitFor--
|
||||
icIdmap[icId] = duplicatedIC.ID
|
||||
uuid_r = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
errs <- fmt.Errorf("ALERT! Timed out while waiting for IC duplication, scenario not properly duplicated")
|
||||
close(errs)
|
||||
|
||||
}()
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func duplicateScenario(s database.Scenario, icIds map[uint]string, user *database.User) error {
|
||||
func duplicateScenario(s database.Scenario, icIds map[uint]uint, user *database.User) error {
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
|
@ -145,28 +152,34 @@ func duplicateScenario(s database.Scenario, icIds map[uint]string, user *databas
|
|||
|
||||
// duplicate files
|
||||
var files []database.File
|
||||
fileidmap := make(map[uint]uint) // key: original file id, value: duplicated file id
|
||||
err = db.Order("ID asc").Model(s).Related(&files, "Files").Error
|
||||
if err != nil {
|
||||
log.Printf("error getting files for scenario %d", s.ID)
|
||||
}
|
||||
for _, f := range files {
|
||||
err = duplicateFile(f, duplicateSo.ID)
|
||||
duplicateFileID, err := duplicateFile(f, duplicateSo.ID)
|
||||
if err != nil {
|
||||
log.Printf("error creating duplicate file %d: %s", f.ID, err)
|
||||
continue
|
||||
} else {
|
||||
fileidmap[f.ID] = duplicateFileID
|
||||
}
|
||||
}
|
||||
|
||||
var configs []database.ComponentConfiguration
|
||||
configidmap := make(map[uint]uint) // key: original config id, value: duplicated config id
|
||||
// map existing signal IDs to duplicated signal IDs for widget duplication
|
||||
signalMap := make(map[uint]uint)
|
||||
err = db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error
|
||||
if err == nil {
|
||||
for _, c := range configs {
|
||||
err = duplicateComponentConfig(c, duplicateSo.ID, icIds, &signalMap)
|
||||
duplicatConfigID, err := duplicateComponentConfig(c, duplicateSo.ID, icIds, &signalMap)
|
||||
if err != nil {
|
||||
log.Printf("Error duplicating component config %d: %s", c.ID, err)
|
||||
continue
|
||||
} else {
|
||||
configidmap[c.ID] = duplicatConfigID
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -180,7 +193,7 @@ func duplicateScenario(s database.Scenario, icIds map[uint]string, user *databas
|
|||
}
|
||||
|
||||
for _, dab := range dabs {
|
||||
err = duplicateDashboard(dab, duplicateSo.ID, signalMap)
|
||||
err = duplicateDashboard(dab, duplicateSo.ID, signalMap, configidmap, fileidmap, icIds)
|
||||
if err != nil {
|
||||
log.Printf("Error duplicating dashboard %d: %s", dab.ID, err)
|
||||
continue
|
||||
|
@ -190,7 +203,7 @@ func duplicateScenario(s database.Scenario, icIds map[uint]string, user *databas
|
|||
return err
|
||||
}
|
||||
|
||||
func duplicateFile(f database.File, scenarioID uint) error {
|
||||
func duplicateFile(f database.File, scenarioID uint) (uint, error) {
|
||||
|
||||
var dup database.File
|
||||
dup.Name = f.Name
|
||||
|
@ -209,7 +222,7 @@ func duplicateFile(f database.File, scenarioID uint) error {
|
|||
db := database.GetDB()
|
||||
err := db.Create(&dup).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Create association of duplicate file to scenario ID of duplicate file
|
||||
|
@ -217,15 +230,15 @@ func duplicateFile(f database.File, scenarioID uint) error {
|
|||
var so database.Scenario
|
||||
err = db.Find(&so, scenarioID).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = db.Model(&so).Association("Files").Append(&dup).Error
|
||||
|
||||
return err
|
||||
return dup.ID, err
|
||||
}
|
||||
|
||||
func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint, icIds map[uint]string, signalMap *map[uint]uint) error {
|
||||
func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint, icIds map[uint]uint, signalMap *map[uint]uint) (uint, error) {
|
||||
|
||||
db := database.GetDB()
|
||||
|
||||
|
@ -234,53 +247,53 @@ func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint
|
|||
dup.StartParameters = m.StartParameters
|
||||
dup.ScenarioID = scenarioID
|
||||
|
||||
if icIds[m.ICID] == "" {
|
||||
dup.ICID = m.ICID
|
||||
} else {
|
||||
if val, ok := icIds[m.ICID]; ok {
|
||||
var duplicatedIC database.InfrastructureComponent
|
||||
err := db.Find(&duplicatedIC, "UUID = ?", icIds[m.ICID]).Error
|
||||
err := db.Find(&duplicatedIC, "ID = ?", val).Error
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
dup.ICID = duplicatedIC.ID
|
||||
} else {
|
||||
dup.ICID = m.ICID
|
||||
}
|
||||
|
||||
// save duplicate to DB and create associations with IC and scenario
|
||||
var so database.Scenario
|
||||
err := db.Find(&so, scenarioID).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// save component configuration to DB
|
||||
err = db.Create(&dup).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// associate IC with component configuration
|
||||
var ic database.InfrastructureComponent
|
||||
err = db.Find(&ic, dup.ICID).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = db.Model(&ic).Association("ComponentConfigurations").Append(&dup).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// associate component configuration with scenario
|
||||
err = db.Model(&so).Association("ComponentConfigurations").Append(&dup).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// duplication of signals
|
||||
var sigs []database.Signal
|
||||
err = db.Order("ID asc").Model(&m).Related(&sigs, "OutputMapping").Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
smap := *signalMap
|
||||
for _, s := range sigs {
|
||||
|
@ -295,7 +308,7 @@ func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint
|
|||
// save signal to DB
|
||||
err = db.Create(&sigDup).Error
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// associate signal with component configuration in correct direction
|
||||
|
@ -306,16 +319,17 @@ func duplicateComponentConfig(m database.ComponentConfiguration, scenarioID uint
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
smap[s.ID] = sigDup.ID
|
||||
}
|
||||
|
||||
return nil
|
||||
return dup.ID, nil
|
||||
}
|
||||
|
||||
func duplicateDashboard(d database.Dashboard, scenarioID uint, signalMap map[uint]uint) error {
|
||||
func duplicateDashboard(d database.Dashboard, scenarioID uint, signalMap map[uint]uint,
|
||||
configIDmap map[uint]uint, fileIDmap map[uint]uint, icIds map[uint]uint) error {
|
||||
|
||||
var duplicateD database.Dashboard
|
||||
duplicateD.Grid = d.Grid
|
||||
|
@ -351,7 +365,7 @@ func duplicateDashboard(d database.Dashboard, scenarioID uint, signalMap map[uin
|
|||
}
|
||||
for _, w := range widgets {
|
||||
|
||||
err = duplicateWidget(w, duplicateD.ID, signalMap)
|
||||
err = duplicateWidget(w, duplicateD.ID, signalMap, configIDmap, fileIDmap, icIds)
|
||||
if err != nil {
|
||||
log.Printf("error creating duplicate for widget %d: %s", w.ID, err)
|
||||
continue
|
||||
|
@ -361,10 +375,11 @@ func duplicateDashboard(d database.Dashboard, scenarioID uint, signalMap map[uin
|
|||
return nil
|
||||
}
|
||||
|
||||
func duplicateWidget(w database.Widget, dashboardID uint, signalMap map[uint]uint) error {
|
||||
func duplicateWidget(w database.Widget, dashboardID uint, signalMap map[uint]uint,
|
||||
configIDmap map[uint]uint, fileIDmap map[uint]uint, icIds map[uint]uint) error {
|
||||
|
||||
var duplicateW database.Widget
|
||||
duplicateW.DashboardID = dashboardID
|
||||
duplicateW.CustomProperties = w.CustomProperties
|
||||
duplicateW.Height = w.Height
|
||||
duplicateW.Width = w.Width
|
||||
duplicateW.MinHeight = w.MinHeight
|
||||
|
@ -380,6 +395,16 @@ func duplicateWidget(w database.Widget, dashboardID uint, signalMap map[uint]uin
|
|||
duplicateW.SignalIDs = append(duplicateW.SignalIDs, int64(signalMap[uint(id)]))
|
||||
}
|
||||
|
||||
if w.Type == "ICstatus" {
|
||||
duplicateW.CustomProperties = duplicateICStatusCustomProps(w.CustomProperties, icIds)
|
||||
} else if w.Type == "Player" {
|
||||
duplicateW.CustomProperties = duplicatePlayerCustomProps(w.CustomProperties, configIDmap)
|
||||
} else if w.Type == "Image" {
|
||||
duplicateW.CustomProperties = duplicateImageCustomProps(w.CustomProperties, fileIDmap)
|
||||
} else {
|
||||
duplicateW.CustomProperties = w.CustomProperties
|
||||
}
|
||||
|
||||
db := database.GetDB()
|
||||
var dab database.Dashboard
|
||||
err := db.Find(&dab, duplicateW.DashboardID).Error
|
||||
|
@ -398,6 +423,92 @@ func duplicateWidget(w database.Widget, dashboardID uint, signalMap map[uint]uin
|
|||
return err
|
||||
}
|
||||
|
||||
func duplicateICStatusCustomProps(customProps postgres.Jsonb, icIds map[uint]uint) postgres.Jsonb {
|
||||
type ICstatusCustomProps struct {
|
||||
CheckedIDs []uint `json:"checkedIDs"`
|
||||
}
|
||||
|
||||
var props ICstatusCustomProps
|
||||
err := json.Unmarshal(customProps.RawMessage, &props)
|
||||
if err != nil {
|
||||
log.Printf("ICstatus duplication, unmarshalling failed: err: %s", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
var IDs []string
|
||||
for _, id := range props.CheckedIDs {
|
||||
IDs = append(IDs, strconv.FormatUint(uint64(icIds[id]), 10))
|
||||
}
|
||||
|
||||
customProperties := fmt.Sprintf(`{"checkedIDs": [%s]}`, strings.Join(IDs, ","))
|
||||
|
||||
return postgres.Jsonb{RawMessage: json.RawMessage(customProperties)}
|
||||
}
|
||||
|
||||
func duplicatePlayerCustomProps(customProps postgres.Jsonb, configIDmap map[uint]uint) postgres.Jsonb {
|
||||
type PlayerCustomProps struct {
|
||||
ConfigID string `json:"configID"`
|
||||
ConfigIDs []int `json:"configIDs"`
|
||||
UploadResults bool `json:"uploadResults"`
|
||||
}
|
||||
|
||||
var props PlayerCustomProps
|
||||
err := json.Unmarshal(customProps.RawMessage, &props)
|
||||
if err != nil {
|
||||
log.Printf("Player duplication, unmarshalling failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
// get configID of original config
|
||||
u, err := strconv.ParseUint(props.ConfigID, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Player duplication, parsing file ID failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
// get configID of duplicated config, save it in PlayerCustomProps struct
|
||||
props.ConfigID = strconv.FormatUint(uint64(configIDmap[uint(u)]), 10)
|
||||
customProperties, err := json.Marshal(props)
|
||||
if err != nil {
|
||||
log.Printf("Player duplication, marshalling failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
return postgres.Jsonb{RawMessage: customProperties}
|
||||
}
|
||||
|
||||
func duplicateImageCustomProps(customProps postgres.Jsonb, fileIDmap map[uint]uint) postgres.Jsonb {
|
||||
type ImageCustomProps struct {
|
||||
File string `json:"file"`
|
||||
Update bool `json:"update"`
|
||||
Lock bool `json:"lockAspect"`
|
||||
}
|
||||
|
||||
var props ImageCustomProps
|
||||
err := json.Unmarshal(customProps.RawMessage, &props)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Image duplication, unmarshalling failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
// get fileID of original file
|
||||
u, err := strconv.ParseUint(props.File, 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("Image duplication, parsing file ID failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
// get fileID of duplicated file, save it in ImageCustomProps struct
|
||||
props.File = strconv.FormatUint(uint64(fileIDmap[uint(u)]), 10)
|
||||
customProperties, err := json.Marshal(props)
|
||||
if err != nil {
|
||||
log.Printf("Image duplication, marshalling failed: err: %v", err)
|
||||
return customProps
|
||||
}
|
||||
|
||||
return postgres.Jsonb{RawMessage: customProperties}
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
|
@ -512,3 +623,24 @@ func duplicateIC(ic database.InfrastructureComponent, userName string, uuidstr s
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
func isICalreadyDuplicated(ic database.InfrastructureComponent, username string) (bool, uint) {
|
||||
db := database.GetDB()
|
||||
var duplicateICs []database.InfrastructureComponent
|
||||
duplicateName := fmt.Sprintf("%s %s", ic.Name, username)
|
||||
err := db.Find(&duplicateICs, "Name = ?", duplicateName).Error
|
||||
if err != nil {
|
||||
log.Printf("Error looking for duplicated ICs: %s", err)
|
||||
}
|
||||
|
||||
// return the first duplicated IC that was recently updated
|
||||
for _, value := range duplicateICs {
|
||||
lastUpdate := value.UpdatedAt
|
||||
diff := time.Since(lastUpdate)
|
||||
if diff.Seconds() < 60 {
|
||||
return true, value.ID
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0
|
||||
}
|
||||
|
|
|
@ -884,14 +884,26 @@ func TestDuplicateScenarioForUser(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, 99, fileID)
|
||||
|
||||
// add IC
|
||||
err = addIC(session, token)
|
||||
// add ICs (kubernetes simulator plus other)
|
||||
err = addICs(session, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add component config
|
||||
err = addComponentConfig(scenarioID, token)
|
||||
// add two component configs with the same IC (ID 1)
|
||||
// plus one component config with IC != kubernetes simulator (ID 2)
|
||||
err = addComponentConfigs(scenarioID, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
/*
|
||||
* component/IC test setup:
|
||||
* C0 --> IC0
|
||||
* C1 --> IC0
|
||||
* C2 --> IC1
|
||||
* ---- after duplication: ----
|
||||
* C3 --> IC2
|
||||
* C4 --> IC2
|
||||
* C5 --> IC1
|
||||
*/
|
||||
|
||||
// add signals
|
||||
err = addSignals(token)
|
||||
assert.NoError(t, err)
|
||||
|
@ -919,9 +931,7 @@ func TestDuplicateScenarioForUser(t *testing.T) {
|
|||
err = addFakeIC(uuidDup, session, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
if err := <-duplicateScenarioForUser(originalSo, &myUser.User, uuidDup); err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
duplicateScenarioForUser(originalSo, &myUser.User, uuidDup)
|
||||
|
||||
/*** Check duplicated scenario for correctness ***/
|
||||
var dplScenarios []database.Scenario
|
||||
|
@ -947,15 +957,26 @@ func TestDuplicateScenarioForUser(t *testing.T) {
|
|||
var configs []database.ComponentConfiguration
|
||||
err = db.Find(&configs).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(configs))
|
||||
assert.Equal(t, configs[0].Name, configs[1].Name)
|
||||
assert.Equal(t, configs[0].FileIDs, configs[1].FileIDs)
|
||||
assert.Equal(t, configs[0].InputMapping, configs[1].InputMapping)
|
||||
assert.Equal(t, configs[0].OutputMapping, configs[1].OutputMapping)
|
||||
assert.Equal(t, configs[0].StartParameters, configs[1].StartParameters)
|
||||
assert.NotEqual(t, configs[0].ScenarioID, configs[1].ScenarioID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[1].ICID)
|
||||
assert.NotEqual(t, configs[0].ID, configs[1].ID)
|
||||
assert.Equal(t, 6, len(configs))
|
||||
assert.Equal(t, configs[0].Name, configs[3].Name)
|
||||
assert.Equal(t, configs[0].FileIDs, configs[3].FileIDs)
|
||||
assert.Equal(t, configs[0].InputMapping, configs[3].InputMapping)
|
||||
assert.Equal(t, configs[0].OutputMapping, configs[3].OutputMapping)
|
||||
assert.Equal(t, configs[0].StartParameters, configs[3].StartParameters)
|
||||
assert.NotEqual(t, configs[0].ScenarioID, configs[3].ScenarioID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[3].ICID) // original and duplicated IC
|
||||
assert.NotEqual(t, configs[0].ID, configs[3].ID)
|
||||
|
||||
assert.Equal(t, configs[0].ScenarioID, configs[1].ScenarioID)
|
||||
assert.Equal(t, configs[0].ScenarioID, configs[2].ScenarioID)
|
||||
assert.Equal(t, configs[3].ScenarioID, configs[4].ScenarioID)
|
||||
assert.Equal(t, configs[3].ScenarioID, configs[5].ScenarioID)
|
||||
|
||||
assert.Equal(t, configs[0].ICID, configs[1].ICID) // same IC for both configs
|
||||
assert.Equal(t, configs[3].ICID, configs[4].ICID) // same duplicated IC
|
||||
assert.Equal(t, configs[2].ICID, configs[5].ICID) // reused (not duplicated) IC
|
||||
assert.NotEqual(t, configs[0].ICID, configs[2].ICID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[3].ICID)
|
||||
|
||||
// compare original and duplicated signals
|
||||
var signals []database.Signal
|
||||
|
@ -983,17 +1004,28 @@ func TestDuplicateScenarioForUser(t *testing.T) {
|
|||
|
||||
// compare original and duplicated infrastructure component
|
||||
var ics []database.InfrastructureComponent
|
||||
err = db.Find(&ics).Error
|
||||
err = db.Order("created_at asc").Find(&ics).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(ics))
|
||||
assert.Equal(t, ics[0].Name, ics[1].Name)
|
||||
assert.Equal(t, ics[0].Category, ics[1].Category)
|
||||
assert.Equal(t, ics[0].Type, ics[1].Type)
|
||||
assert.Equal(t, ics[0].CreateParameterSchema, ics[1].CreateParameterSchema)
|
||||
assert.Equal(t, ics[0].StartParameterSchema, ics[1].StartParameterSchema)
|
||||
assert.Equal(t, ics[0].Manager, ics[1].Manager)
|
||||
assert.NotEqual(t, ics[0].UUID, ics[1].UUID)
|
||||
assert.NotEqual(t, ics[0].ID, ics[1].ID)
|
||||
assert.Equal(t, 3, len(ics))
|
||||
assert.Equal(t, ics[0].Category, ics[2].Category)
|
||||
assert.Equal(t, ics[0].Type, ics[2].Type)
|
||||
assert.Equal(t, ics[0].CreateParameterSchema, ics[2].CreateParameterSchema)
|
||||
assert.Equal(t, ics[0].StartParameterSchema, ics[2].StartParameterSchema)
|
||||
assert.Equal(t, ics[0].Manager, ics[2].Manager)
|
||||
assert.NotEqual(t, ics[0].UUID, ics[2].UUID)
|
||||
assert.NotEqual(t, ics[0].ID, ics[2].ID)
|
||||
|
||||
assert.NotEqual(t, ics[0].Name, ics[1].Name)
|
||||
assert.NotEqual(t, ics[0].Category, ics[1].Category)
|
||||
assert.NotEqual(t, ics[0].Type, ics[1].Type)
|
||||
|
||||
// check associations between component configs and ICs
|
||||
assert.Equal(t, configs[0].ICID, ics[0].ID)
|
||||
assert.Equal(t, configs[1].ICID, ics[0].ID)
|
||||
assert.Equal(t, configs[2].ICID, ics[1].ID)
|
||||
assert.Equal(t, configs[3].ICID, ics[2].ID)
|
||||
assert.Equal(t, configs[4].ICID, ics[2].ID)
|
||||
assert.Equal(t, configs[5].ICID, ics[1].ID)
|
||||
|
||||
// compare original and duplicated dashboards
|
||||
err = db.Order("created_at asc").Find(&dashboards).Error
|
||||
|
@ -1011,13 +1043,187 @@ func TestDuplicateScenarioForUser(t *testing.T) {
|
|||
assert.NotEqual(t, dashboards[1].ScenarioID, dashboards[3].ScenarioID)
|
||||
assert.NotEqual(t, dashboards[1].ID, dashboards[3].ID)
|
||||
|
||||
// compare original and duplicated widget
|
||||
// compare original and duplicated widget (ICstatus)
|
||||
var widgets []database.Widget
|
||||
err = db.Find(&widgets).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(widgets))
|
||||
assert.Equal(t, widgets[0].Name, widgets[1].Name)
|
||||
assert.Equal(t, widgets[0].CustomProperties, widgets[1].CustomProperties)
|
||||
assert.NotEqual(t, widgets[0].CustomProperties, widgets[1].CustomProperties)
|
||||
assert.Equal(t, widgets[0].MinHeight, widgets[1].MinHeight)
|
||||
assert.Equal(t, widgets[0].MinWidth, widgets[1].MinWidth)
|
||||
assert.NotEqual(t, widgets[0].DashboardID, widgets[1].DashboardID)
|
||||
assert.NotEqual(t, widgets[0].ID, widgets[1].ID)
|
||||
}
|
||||
|
||||
func TestScenarioDuplicationAlreadyDuplicatedIC(t *testing.T) {
|
||||
/*
|
||||
* Test: IC was already duplicated, exists in DB at the time of duplication
|
||||
**/
|
||||
database.DropTables()
|
||||
database.MigrateModels()
|
||||
assert.NoError(t, database.AddTestUsers())
|
||||
|
||||
// connect AMQP client
|
||||
// Make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set
|
||||
host, _ := configuration.GlobalConfig.String("amqp.host")
|
||||
usr, _ := configuration.GlobalConfig.String("amqp.user")
|
||||
pass, _ := configuration.GlobalConfig.String("amqp.pass")
|
||||
amqpURI := "amqp://" + usr + ":" + pass + "@" + host
|
||||
|
||||
// AMQP Connection startup is tested here
|
||||
// Not repeated in other tests because it is only needed once
|
||||
session = helper.NewAMQPSession("villas-test-session", amqpURI, "villas", infrastructure_component.ProcessMessage)
|
||||
SetAMQPSession(session)
|
||||
infrastructure_component.SetAMQPSession(session)
|
||||
|
||||
// authenticate as admin (needed to create original IC)
|
||||
token, err := helper.AuthenticateForTest(router, database.AdminCredentials)
|
||||
assert.NoError(t, err)
|
||||
|
||||
/*** Create original scenario and entities ***/
|
||||
scenarioID := addScenario(token)
|
||||
var originalSo database.Scenario
|
||||
db := database.GetDB()
|
||||
err = db.Find(&originalSo, scenarioID).Error
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add file to scenario
|
||||
fileID, err := addFile(scenarioID, token)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, 99, fileID)
|
||||
|
||||
// add ICs (kubernetes simulator plus other)
|
||||
err = addICs(session, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add two component configs with the same IC (ID 1)
|
||||
// plus one component config with IC != kubernetes simulator (ID 2)
|
||||
err = addComponentConfigs(scenarioID, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
/*
|
||||
* component/IC test setup:
|
||||
* C0 --> IC0
|
||||
* C1 --> IC0
|
||||
* C2 --> IC1
|
||||
* x --> IC2
|
||||
* ---- after duplication: ----
|
||||
* C3 --> IC2
|
||||
* C4 --> IC2
|
||||
* C5 --> IC1
|
||||
*/
|
||||
|
||||
// add dashboards to scenario
|
||||
err = addTwoDashboards(scenarioID, token)
|
||||
assert.NoError(t, err)
|
||||
var dashboards []database.Dashboard
|
||||
err = db.Find(&dashboards).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(dashboards))
|
||||
|
||||
// add widgets
|
||||
dashboardID_forAddingWidget := uint(1)
|
||||
err = addWidget(dashboardID_forAddingWidget, token)
|
||||
assert.NoError(t, err)
|
||||
|
||||
/*** Duplicate scenario for new user ***/
|
||||
username := "Schnittlauch"
|
||||
myUser, err := NewUser(username, "", "schnitti@lauch.de", "User", true)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// add IC which will be seen as the duplicate IC
|
||||
err = addDuplicateIC(token, username)
|
||||
assert.NoError(t, err)
|
||||
log.Println("--------ICs before duplication ------------")
|
||||
var icsqq []database.InfrastructureComponent
|
||||
err = db.Order("created_at asc").Find(&icsqq).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(icsqq))
|
||||
for _, ic := range icsqq {
|
||||
log.Printf("IC (id/name): (%d/%s)", ic.ID, ic.Name)
|
||||
}
|
||||
log.Println("------------------")
|
||||
|
||||
duplicateScenarioForUser(originalSo, &myUser.User, "")
|
||||
|
||||
/*** Check duplicated scenario for correctness ***/
|
||||
var dplScenarios []database.Scenario
|
||||
err = db.Find(&dplScenarios, "name = ?", originalSo.Name+" "+username).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(dplScenarios))
|
||||
assert.Equal(t, originalSo.StartParameters, dplScenarios[0].StartParameters)
|
||||
|
||||
// compare original and duplicated component configs
|
||||
var configs []database.ComponentConfiguration
|
||||
err = db.Find(&configs).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 6, len(configs))
|
||||
assert.Equal(t, configs[0].Name, configs[3].Name)
|
||||
assert.Equal(t, configs[0].FileIDs, configs[3].FileIDs)
|
||||
assert.Equal(t, configs[0].InputMapping, configs[3].InputMapping)
|
||||
assert.Equal(t, configs[0].OutputMapping, configs[3].OutputMapping)
|
||||
assert.Equal(t, configs[0].StartParameters, configs[3].StartParameters)
|
||||
assert.NotEqual(t, configs[0].ScenarioID, configs[3].ScenarioID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[3].ICID) // original and duplicated IC
|
||||
assert.NotEqual(t, configs[0].ID, configs[3].ID)
|
||||
|
||||
assert.Equal(t, configs[0].ScenarioID, configs[1].ScenarioID)
|
||||
assert.Equal(t, configs[0].ScenarioID, configs[2].ScenarioID)
|
||||
assert.Equal(t, configs[3].ScenarioID, configs[4].ScenarioID)
|
||||
assert.Equal(t, configs[3].ScenarioID, configs[5].ScenarioID)
|
||||
|
||||
assert.Equal(t, configs[0].ICID, configs[1].ICID) // same IC for both configs
|
||||
assert.Equal(t, configs[3].ICID, configs[4].ICID) // same duplicated IC
|
||||
assert.Equal(t, configs[2].ICID, configs[5].ICID) // reused (not duplicated) IC
|
||||
log.Println(configs[2].ICID)
|
||||
log.Println(configs[5].ICID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[2].ICID)
|
||||
assert.NotEqual(t, configs[0].ICID, configs[3].ICID)
|
||||
|
||||
// compare original and duplicated infrastructure component
|
||||
var ics []database.InfrastructureComponent
|
||||
err = db.Order("created_at asc").Find(&ics).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3, len(ics))
|
||||
log.Println("--------ICs after duplication ------------")
|
||||
|
||||
for _, ic := range ics {
|
||||
log.Printf("IC (id/name): (%d/%s)", ic.ID, ic.Name)
|
||||
}
|
||||
log.Println("------------------")
|
||||
|
||||
// check associations between component configs and ICs
|
||||
assert.Equal(t, configs[0].ICID, ics[0].ID)
|
||||
assert.Equal(t, configs[1].ICID, ics[0].ID)
|
||||
assert.Equal(t, configs[2].ICID, ics[1].ID)
|
||||
assert.Equal(t, configs[3].ICID, ics[2].ID)
|
||||
assert.Equal(t, configs[4].ICID, ics[2].ID)
|
||||
assert.Equal(t, configs[5].ICID, ics[1].ID)
|
||||
|
||||
// compare original and duplicated dashboards
|
||||
err = db.Order("created_at asc").Find(&dashboards).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 4, len(dashboards))
|
||||
assert.Equal(t, dashboards[0].Name, dashboards[2].Name)
|
||||
assert.Equal(t, dashboards[0].Grid, dashboards[2].Grid)
|
||||
assert.Equal(t, dashboards[0].Height, dashboards[2].Height)
|
||||
assert.NotEqual(t, dashboards[0].ScenarioID, dashboards[2].ScenarioID)
|
||||
assert.NotEqual(t, dashboards[0].ID, dashboards[2].ID)
|
||||
|
||||
assert.Equal(t, dashboards[1].Name, dashboards[3].Name)
|
||||
assert.Equal(t, dashboards[1].Grid, dashboards[3].Grid)
|
||||
assert.Equal(t, dashboards[1].Height, dashboards[3].Height)
|
||||
assert.NotEqual(t, dashboards[1].ScenarioID, dashboards[3].ScenarioID)
|
||||
assert.NotEqual(t, dashboards[1].ID, dashboards[3].ID)
|
||||
|
||||
// compare original and duplicated widget (ICstatus)
|
||||
var widgets []database.Widget
|
||||
err = db.Find(&widgets).Error
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(widgets))
|
||||
assert.Equal(t, widgets[0].Name, widgets[1].Name)
|
||||
assert.NotEqual(t, widgets[0].CustomProperties, widgets[1].CustomProperties)
|
||||
assert.Equal(t, widgets[0].MinHeight, widgets[1].MinHeight)
|
||||
assert.Equal(t, widgets[0].MinWidth, widgets[1].MinWidth)
|
||||
assert.NotEqual(t, widgets[0].DashboardID, widgets[1].DashboardID)
|
||||
|
@ -1155,17 +1361,17 @@ func addWidget(dashboardID uint, token string) error {
|
|||
}
|
||||
|
||||
newWidget := WidgetRequest{
|
||||
Name: "My label",
|
||||
Type: "Label",
|
||||
Name: "IC Status",
|
||||
Type: "ICstatus",
|
||||
Width: 100,
|
||||
Height: 50,
|
||||
MinWidth: 40,
|
||||
MinHeight: 80,
|
||||
Height: 100,
|
||||
MinWidth: 0,
|
||||
MinHeight: 0,
|
||||
X: 10,
|
||||
Y: 10,
|
||||
Z: 200,
|
||||
IsLocked: false,
|
||||
CustomProperties: postgres.Jsonb{RawMessage: json.RawMessage(`{"textSize" : "20", "fontColor" : "#4287f5", "fontColor_opacity": 1}`)},
|
||||
CustomProperties: postgres.Jsonb{RawMessage: json.RawMessage(`{"checkedIDs" : [1]}`)},
|
||||
SignalIDs: []int64{},
|
||||
}
|
||||
|
||||
|
@ -1177,13 +1383,12 @@ func addWidget(dashboardID uint, token string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func newTrue() *bool {
|
||||
b := true
|
||||
func newFalse() *bool {
|
||||
b := false
|
||||
return &b
|
||||
}
|
||||
|
||||
func addIC(session *helper.AMQPsession, token string) error {
|
||||
type ICRequest struct {
|
||||
type ICRequest struct {
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
WebsocketURL string `json:"websocketurl,omitempty"`
|
||||
APIURL string `json:"apiurl,omitempty"`
|
||||
|
@ -1197,9 +1402,10 @@ func addIC(session *helper.AMQPsession, token string) error {
|
|||
CreateParameterSchema postgres.Jsonb `json:"createparameterschema,omitempty"`
|
||||
ManagedExternally *bool `json:"managedexternally"`
|
||||
Manager string `json:"manager,omitempty"`
|
||||
}
|
||||
}
|
||||
|
||||
// create IC
|
||||
func addICs(session *helper.AMQPsession, token string) error {
|
||||
// create ICs
|
||||
var newIC = ICRequest{
|
||||
UUID: "7be0322d-354e-431e-84bd-ae4c9633138b",
|
||||
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
|
||||
|
@ -1212,7 +1418,23 @@ func addIC(session *helper.AMQPsession, token string) error {
|
|||
Description: "A kubernetes simulator for testing purposes",
|
||||
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"startprop1" : "a nice prop"}`)},
|
||||
CreateParameterSchema: postgres.Jsonb{json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
|
||||
ManagedExternally: newTrue(),
|
||||
ManagedExternally: newFalse(),
|
||||
Manager: "7be0322d-354e-431e-84bd-ae4c9633beef",
|
||||
}
|
||||
|
||||
var newIC2 = ICRequest{
|
||||
UUID: "7be0322d-354e-431e-84bd-ae4c9635558b",
|
||||
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
|
||||
APIURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v2",
|
||||
Type: "villas-node",
|
||||
Name: "ACS Demo Signals",
|
||||
Category: "gateway",
|
||||
State: "idle",
|
||||
Location: "k8s",
|
||||
Description: "A signal generator for testing purposes",
|
||||
StartParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"startprop1" : "a nice prop"}`)},
|
||||
CreateParameterSchema: postgres.Jsonb{RawMessage: json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
|
||||
ManagedExternally: newFalse(),
|
||||
Manager: "7be0322d-354e-431e-84bd-ae4c9633beef",
|
||||
}
|
||||
|
||||
|
@ -1244,10 +1466,42 @@ func addIC(session *helper.AMQPsession, token string) error {
|
|||
|
||||
err = session.Send(payload, newIC.Manager)
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = helper.TestEndpoint(router, token,
|
||||
"/api/v2/ic", "POST", helper.KeyModels{"ic": newIC2})
|
||||
return err
|
||||
}
|
||||
|
||||
func addComponentConfig(scenarioID uint, token string) error {
|
||||
func addDuplicateIC(token string, username string) error {
|
||||
|
||||
// create IC
|
||||
var newIC = ICRequest{
|
||||
UUID: "7aaa322d-354e-431e-84bd-ae4c9633138b",
|
||||
WebsocketURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/ws_sig",
|
||||
APIURL: "https://villas.k8s.eonerc.rwth-aachen.de/ws/api/v2",
|
||||
Type: "kubernetes",
|
||||
Name: "Kubernetes Simulator " + username,
|
||||
Category: "simulator",
|
||||
State: "idle",
|
||||
Location: "k8s",
|
||||
Description: "A kubernetes simulator for testing purposes",
|
||||
StartParameterSchema: postgres.Jsonb{json.RawMessage(`{"startprop1" : "a nice prop"}`)},
|
||||
CreateParameterSchema: postgres.Jsonb{json.RawMessage(`{"createprop1" : "a really nice prop"}`)},
|
||||
ManagedExternally: newFalse(),
|
||||
Manager: "7be0322d-354e-431e-84bd-ae4c9633beef",
|
||||
}
|
||||
|
||||
_, _, err := helper.TestEndpoint(router, token,
|
||||
"/api/v2/ic", "POST", helper.KeyModels{"ic": newIC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func addComponentConfigs(scenarioID uint, token string) error {
|
||||
type ConfigRequest struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ScenarioID uint `json:"scenarioID,omitempty"`
|
||||
|
@ -1264,8 +1518,36 @@ func addComponentConfig(scenarioID uint, token string) error {
|
|||
FileIDs: []int64{},
|
||||
}
|
||||
|
||||
var newConfig2 = ConfigRequest{
|
||||
Name: "Example for Signal generator",
|
||||
ScenarioID: scenarioID,
|
||||
ICID: 1,
|
||||
StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)},
|
||||
FileIDs: []int64{},
|
||||
}
|
||||
|
||||
var newConfig3 = ConfigRequest{
|
||||
Name: "Example for Signal generator",
|
||||
ScenarioID: scenarioID,
|
||||
ICID: 2,
|
||||
StartParameters: postgres.Jsonb{RawMessage: json.RawMessage(`{"parameter1" : "testValue1A", "parameter2" : "testValue2A", "parameter3" : 42}`)},
|
||||
FileIDs: []int64{},
|
||||
}
|
||||
|
||||
_, _, err := helper.TestEndpoint(router, token,
|
||||
"/api/v2/configs", "POST", helper.KeyModels{"config": newConfig1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = helper.TestEndpoint(router, token,
|
||||
"/api/v2/configs", "POST", helper.KeyModels{"config": newConfig2})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, err = helper.TestEndpoint(router, token,
|
||||
"/api/v2/configs", "POST", helper.KeyModels{"config": newConfig3})
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue