Fix duplication: synchronous duplication, proper duplication of widgets which use IDs, reuse duplicated IC (if alive)

This commit is contained in:
Iris Marie Köster 2021-12-02 18:37:52 +01:00
parent 61ead46392
commit 4db2f91ea5
4 changed files with 569 additions and 156 deletions

View file

@ -100,5 +100,7 @@ deploy:
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
--destination ${DOCKER_IMAGE}:${DOCKER_TAG}
--snapshotMode=redo
--single-snapshot
dependencies:
- test

View file

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

View file

@ -27,102 +27,109 @@ 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)
func duplicateScenarioForUser(s database.Scenario, user *database.User, uuidstr string) {
go func() {
// get all component configs of the scenario
db := database.GetDB()
var configs []database.ComponentConfiguration
err := db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error
if err != nil {
log.Printf("Warning: scenario to duplicate (id=%d) has no component configurations", s.ID)
}
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
// iterate over component configs to check for ICs to duplicate
for _, config := range configs {
icID := config.ICID
if _, ok := duplicatedICuuids[icID]; ok { // this IC was already added
log.Println("IC already added while ranging configs")
continue
}
var ic database.InfrastructureComponent
err = db.Find(&ic, icID).Error
// get all component configs of the scenario
db := database.GetDB()
var configs []database.ComponentConfiguration
err := db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error
if err != nil {
log.Printf("Warning: scenario to duplicate (id=%d) has no component configurations", s.ID)
log.Printf("Cannot find IC with id %d in DB, will not duplicate for User %s: %s", icID, user.Username, err)
continue
}
// iterate over component configs to check for ICs to duplicate
duplicatedICuuids := make(map[uint]string) // key: original icID; value: UUID of duplicate
var externalUUIDs []string // external ICs to wait for
for _, config := range configs {
icID := config.ICID
if duplicatedICuuids[icID] != "" { // this IC was already added
continue
}
var ic database.InfrastructureComponent
err = db.Find(&ic, icID).Error
alreadyDuplicated, dupID := isICalreadyDuplicated(ic, user.Username)
// create new kubernetes simulator OR use existing IC
if ic.Category == "simulator" && ic.Type == "kubernetes" && !alreadyDuplicated {
duplicateUUID, err := duplicateIC(ic, user.Username, uuidstr)
if err != nil {
log.Printf("Cannot find IC with id %d in DB, will not duplicate for User %s: %s", icID, user.Username, err)
log.Printf("duplication of IC (id=%d) unsuccessful, err: %s", icID, err)
continue
}
// create new kubernetes simulator OR use existing IC
if ic.Category == "simulator" && ic.Type == "kubernetes" {
duplicateUUID, err := duplicateIC(ic, user.Username, uuidstr)
if err != nil {
errs <- fmt.Errorf("duplication of IC (id=%d) unsuccessful, err: %s", icID, err)
continue
}
duplicatedICuuids[ic.ID] = duplicateUUID
} else if alreadyDuplicated {
duplicatedICuuids[ic.ID] = "alreadyduplicated"
icIdmap[ic.ID] = dupID
err = nil
} else { // use existing IC
duplicatedICuuids[ic.ID] = "useoriginal"
icIdmap[ic.ID] = ic.ID
err = nil
}
}
duplicatedICuuids[ic.ID] = duplicateUUID
externalUUIDs = append(externalUUIDs, duplicateUUID)
} else { // use existing IC
duplicatedICuuids[ic.ID] = ""
err = nil
// copy scenario after all new external ICs are in DB
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
log.Printf("Number of ICs to wait for: %d", icsToWaitFor)
if icsToWaitFor <= 0 {
err := duplicateScenario(s, icIdmap, user)
if err != nil {
log.Printf("duplicate scenario %v fails with error %v", s.Name, err.Error())
}
return
} else {
time.Sleep(1 * time.Second)
}
// copy scenario after all new external ICs are in DB
icsToWaitFor := len(externalUUIDs)
var timeout = 20 // seconds
// check for new ICs with previously created UUIDs
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)
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)
if err != nil {
errs <- fmt.Errorf("duplicate scenario %v fails with error %v", s.Name, err.Error())
}
close(errs)
return
// 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 {
log.Printf("Didn't find duplicated IC: %s, keep waiting for it..", err)
} else {
time.Sleep(1 * time.Second)
}
// check for new ICs with previously created UUIDs
for _, uuid_r := range externalUUIDs {
if uuid_r == "" {
continue
}
log.Printf("Looking for duplicated IC with UUID %s", uuid_r)
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)
} else {
icsToWaitFor--
uuid_r = ""
}
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
}

View file

@ -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,29 +1383,29 @@ 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 {
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
APIURL string `json:"apiurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterSchema postgres.Jsonb `json:"startparameterschema,omitempty"`
CreateParameterSchema postgres.Jsonb `json:"createparameterschema,omitempty"`
ManagedExternally *bool `json:"managedexternally"`
Manager string `json:"manager,omitempty"`
}
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
WebsocketURL string `json:"websocketurl,omitempty"`
APIURL string `json:"apiurl,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Location string `json:"location,omitempty"`
Description string `json:"description,omitempty"`
StartParameterSchema postgres.Jsonb `json:"startparameterschema,omitempty"`
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
}