From 1f0455d9011d01386fb1bc59f73c9a898eda3bda Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 20 Oct 2020 12:57:38 +0200 Subject: [PATCH 01/18] Parameter cleanup in IC model --- amqp/amqpclient.go | 38 ++-- database/models.go | 14 +- doc/api/docs.go | 52 ++++-- doc/api/swagger.json | 50 ++++-- doc/api/swagger.yaml | 43 +++-- helper/test_data.go | 48 ++--- routes/component-configuration/config_test.go | 48 ++--- routes/infrastructure-component/ic_test.go | 169 ++++++++++-------- .../infrastructure-component/ic_validators.go | 58 +++--- routes/signal/signal_test.go | 32 ++-- 10 files changed, 327 insertions(+), 225 deletions(-) diff --git a/amqp/amqpclient.go b/amqp/amqpclient.go index 72d6f4b..8adedad 100644 --- a/amqp/amqpclient.go +++ b/amqp/amqpclient.go @@ -28,7 +28,6 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jinzhu/gorm" - "github.com/jinzhu/gorm/dialects/postgres" "github.com/streadway/amqp" "log" "time" @@ -54,14 +53,16 @@ type Action struct { type ICUpdate struct { State *string `json:"state"` Properties struct { - UUID string `json:"uuid"` - Name *string `json:"name"` - Category *string `json:"category"` - Type *string `json:"type"` - Location *string `json:"location"` - WS_url *string `json:"ws_url"` - API_url *string `json:"api_url"` + UUID string `json:"uuid"` + Name *string `json:"name"` + Category *string `json:"category"` + Type *string `json:"type"` + Location *string `json:"location"` + WS_url *string `json:"ws_url"` + API_url *string `json:"api_url"` + Description *string `json:"description"` } `json:"properties"` + // TODO add JSON start parameter scheme } var client AMQPclient @@ -272,14 +273,18 @@ func processMessage(message amqp.Delivery) { newICReq.InfrastructureComponent.State = "unknown" } if payload.Properties.WS_url != nil { - newICReq.InfrastructureComponent.Host = *payload.Properties.WS_url + newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url } if payload.Properties.API_url != nil { - newICReq.InfrastructureComponent.APIHost = *payload.Properties.API_url + newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url } if payload.Properties.Location != nil { - newICReq.InfrastructureComponent.Properties = postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + newICReq.InfrastructureComponent.Location = *payload.Properties.Location } + if payload.Properties.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme // Validate the new IC err = newICReq.Validate() @@ -319,14 +324,19 @@ func processMessage(message amqp.Delivery) { updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name } if payload.Properties.WS_url != nil { - updatedICReq.InfrastructureComponent.Host = *payload.Properties.WS_url + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url } if payload.Properties.API_url != nil { - updatedICReq.InfrastructureComponent.APIHost = *payload.Properties.API_url + updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url } if payload.Properties.Location != nil { - updatedICReq.InfrastructureComponent.Properties = postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location } + if payload.Properties.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme // Validate the updated IC if err = updatedICReq.Validate(); err != nil { diff --git a/database/models.go b/database/models.go index 8d1f36a..83852b8 100644 --- a/database/models.go +++ b/database/models.go @@ -120,10 +120,10 @@ type InfrastructureComponent struct { UUID string `json:"uuid" gorm:"not null"` // Name of the IC Name string `json:"name" gorm:"default:''"` - // Host if the IC - Host string `json:"host" gorm:"default:''"` - // Host of API for IC - APIHost string `json:"apihost" gorm:"default:''"` + // WebsocketURL if the IC + WebsocketURL string `json:"websocketurl" gorm:"default:''"` + // API URL of API for IC + APIURL string `json:"apiurl" gorm:"default:''"` // Category of IC (simulator, gateway, database, etc.) Category string `json:"category" gorm:"default:''"` // Type of IC (RTDS, VILLASnode, RTDS, etc.) @@ -134,10 +134,12 @@ type InfrastructureComponent struct { State string `json:"state" gorm:"default:''"` // Time of last state update StateUpdateAt string `json:"stateUpdateAt" gorm:"default:''"` + // Location of the IC + Location string `json:"location" gorm:"default:''"` + // Description of the IC + Description string `json:"description" gorm:"default:''"` // Properties of IC as JSON string Properties postgres.Jsonb `json:"properties"` - // Raw properties of IC as JSON string - RawProperties postgres.Jsonb `json:"rawProperties"` // ComponentConfigurations in which the IC is used ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ICID"` } diff --git a/doc/api/docs.go b/doc/api/docs.go index a32cd60..becdbf9 100644 --- a/doc/api/docs.go +++ b/doc/api/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2020-09-25 16:13:15.130920598 +0200 CEST m=+0.092357808 +// 2020-10-20 12:56:57.842992188 +0200 CEST m=+0.084567298 package docs @@ -1078,7 +1078,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/infrastructure_component.addICRequest" + "$ref": "#/definitions/infrastructure_component.AddICRequest" } } ], @@ -1198,7 +1198,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/infrastructure_component.updateICRequest" + "$ref": "#/definitions/infrastructure_component.UpdateICRequest" } }, { @@ -2977,21 +2977,25 @@ var doc = `{ "database.InfrastructureComponent": { "type": "object", "properties": { - "apihost": { - "description": "Host of API for IC", + "apiurl": { + "description": "API URL of API for IC", "type": "string" }, "category": { "description": "Category of IC (simulator, gateway, database, etc.)", "type": "string" }, - "host": { - "description": "Host if the IC", + "description": { + "description": "Description of the IC", "type": "string" }, "id": { "type": "integer" }, + "location": { + "description": "Location of the IC", + "type": "string" + }, "name": { "description": "Name of the IC", "type": "string" @@ -3000,10 +3004,6 @@ var doc = `{ "description": "Properties of IC as JSON string", "type": "string" }, - "rawProperties": { - "description": "Raw properties of IC as JSON string", - "type": "string" - }, "state": { "description": "State of the IC", "type": "string" @@ -3023,6 +3023,10 @@ var doc = `{ "uuid": { "description": "UUID of the IC", "type": "string" + }, + "websocketurl": { + "description": "WebsocketURL if the IC", + "type": "string" } } }, @@ -3351,7 +3355,7 @@ var doc = `{ } } }, - "infrastructure_component.addICRequest": { + "infrastructure_component.AddICRequest": { "type": "object", "properties": { "ic": { @@ -3360,7 +3364,7 @@ var doc = `{ } } }, - "infrastructure_component.updateICRequest": { + "infrastructure_component.UpdateICRequest": { "type": "object", "properties": { "ic": { @@ -3378,13 +3382,16 @@ var doc = `{ "UUID" ], "properties": { - "APIHost": { + "APIURL": { "type": "string" }, "Category": { "type": "string" }, - "Host": { + "Description": { + "type": "string" + }, + "Location": { "type": "string" }, "Name": { @@ -3401,19 +3408,25 @@ var doc = `{ }, "UUID": { "type": "string" + }, + "WebsocketURL": { + "type": "string" } } }, "infrastructure_component.validUpdatedIC": { "type": "object", "properties": { - "APIHost": { + "APIURL": { "type": "string" }, "Category": { "type": "string" }, - "Host": { + "Description": { + "type": "string" + }, + "Location": { "type": "string" }, "Name": { @@ -3430,6 +3443,9 @@ var doc = `{ }, "UUID": { "type": "string" + }, + "WebsocketURL": { + "type": "string" } } }, @@ -3765,7 +3781,7 @@ type swaggerInfo struct { var SwaggerInfo = swaggerInfo{ Version: "2.0", Host: "", - BasePath: "http://localhost:4000/api/v2/", + BasePath: "/api/v2", Schemes: []string{}, Title: "VILLASweb Backend API", Description: "This is the [VILLASweb Backend](https://git.rwth-aachen.de/acs/public/villas/web-backend-go) API v2.0.\nThis documentation is auto-generated based on the API documentation in the code. The tool [swag](https://github.com/swaggo/swag) is used to auto-generate API docs for the [gin-gonic](https://github.com/gin-gonic/gin) framework.\nAuthentication: Use the authenticate endpoint below to obtain a token for your user account, copy the token into to the value field of the dialog showing up for the green Authorize button below and confirm with Done.", diff --git a/doc/api/swagger.json b/doc/api/swagger.json index 20834e5..74f22a7 100644 --- a/doc/api/swagger.json +++ b/doc/api/swagger.json @@ -13,7 +13,7 @@ }, "version": "2.0" }, - "basePath": "http://localhost:4000/api/v2/", + "basePath": "/api/v2", "paths": { "/authenticate": { "post": { @@ -1061,7 +1061,7 @@ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/infrastructure_component.addICRequest" + "$ref": "#/definitions/infrastructure_component.AddICRequest" } } ], @@ -1181,7 +1181,7 @@ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/infrastructure_component.updateICRequest" + "$ref": "#/definitions/infrastructure_component.UpdateICRequest" } }, { @@ -2960,21 +2960,25 @@ "database.InfrastructureComponent": { "type": "object", "properties": { - "apihost": { - "description": "Host of API for IC", + "apiurl": { + "description": "API URL of API for IC", "type": "string" }, "category": { "description": "Category of IC (simulator, gateway, database, etc.)", "type": "string" }, - "host": { - "description": "Host if the IC", + "description": { + "description": "Description of the IC", "type": "string" }, "id": { "type": "integer" }, + "location": { + "description": "Location of the IC", + "type": "string" + }, "name": { "description": "Name of the IC", "type": "string" @@ -2983,10 +2987,6 @@ "description": "Properties of IC as JSON string", "type": "string" }, - "rawProperties": { - "description": "Raw properties of IC as JSON string", - "type": "string" - }, "state": { "description": "State of the IC", "type": "string" @@ -3006,6 +3006,10 @@ "uuid": { "description": "UUID of the IC", "type": "string" + }, + "websocketurl": { + "description": "WebsocketURL if the IC", + "type": "string" } } }, @@ -3334,7 +3338,7 @@ } } }, - "infrastructure_component.addICRequest": { + "infrastructure_component.AddICRequest": { "type": "object", "properties": { "ic": { @@ -3343,7 +3347,7 @@ } } }, - "infrastructure_component.updateICRequest": { + "infrastructure_component.UpdateICRequest": { "type": "object", "properties": { "ic": { @@ -3361,13 +3365,16 @@ "UUID" ], "properties": { - "APIHost": { + "APIURL": { "type": "string" }, "Category": { "type": "string" }, - "Host": { + "Description": { + "type": "string" + }, + "Location": { "type": "string" }, "Name": { @@ -3384,19 +3391,25 @@ }, "UUID": { "type": "string" + }, + "WebsocketURL": { + "type": "string" } } }, "infrastructure_component.validUpdatedIC": { "type": "object", "properties": { - "APIHost": { + "APIURL": { "type": "string" }, "Category": { "type": "string" }, - "Host": { + "Description": { + "type": "string" + }, + "Location": { "type": "string" }, "Name": { @@ -3413,6 +3426,9 @@ }, "UUID": { "type": "string" + }, + "WebsocketURL": { + "type": "string" } } }, diff --git a/doc/api/swagger.yaml b/doc/api/swagger.yaml index 8668a64..b1c2b74 100644 --- a/doc/api/swagger.yaml +++ b/doc/api/swagger.yaml @@ -1,4 +1,4 @@ -basePath: http://localhost:4000/api/v2/ +basePath: /api/v2 definitions: component_configuration.addConfigRequest: properties: @@ -146,26 +146,26 @@ definitions: type: object database.InfrastructureComponent: properties: - apihost: - description: Host of API for IC + apiurl: + description: API URL of API for IC type: string category: description: Category of IC (simulator, gateway, database, etc.) type: string - host: - description: Host if the IC + description: + description: Description of the IC type: string id: type: integer + location: + description: Location of the IC + type: string name: description: Name of the IC type: string properties: description: Properties of IC as JSON string type: string - rawProperties: - description: Raw properties of IC as JSON string - type: string state: description: State of the IC type: string @@ -181,6 +181,9 @@ definitions: uuid: description: UUID of the IC type: string + websocketurl: + description: WebsocketURL if the IC + type: string type: object database.Scenario: properties: @@ -404,13 +407,13 @@ definitions: $ref: '#/definitions/database.Widget' type: array type: object - infrastructure_component.addICRequest: + infrastructure_component.AddICRequest: properties: ic: $ref: '#/definitions/infrastructure_component.validNewIC' type: object type: object - infrastructure_component.updateICRequest: + infrastructure_component.UpdateICRequest: properties: ic: $ref: '#/definitions/infrastructure_component.validUpdatedIC' @@ -418,11 +421,13 @@ definitions: type: object infrastructure_component.validNewIC: properties: - APIHost: + APIURL: type: string Category: type: string - Host: + Description: + type: string + Location: type: string Name: type: string @@ -434,6 +439,8 @@ definitions: type: string UUID: type: string + WebsocketURL: + type: string required: - Category - Name @@ -442,11 +449,13 @@ definitions: type: object infrastructure_component.validUpdatedIC: properties: - APIHost: + APIURL: type: string Category: type: string - Host: + Description: + type: string + Location: type: string Name: type: string @@ -458,6 +467,8 @@ definitions: type: string UUID: type: string + WebsocketURL: + type: string type: object scenario.addScenarioRequest: properties: @@ -1357,7 +1368,7 @@ paths: name: inputIC required: true schema: - $ref: '#/definitions/infrastructure_component.addICRequest' + $ref: '#/definitions/infrastructure_component.AddICRequest' type: object produces: - application/json @@ -1470,7 +1481,7 @@ paths: name: inputIC required: true schema: - $ref: '#/definitions/infrastructure_component.updateICRequest' + $ref: '#/definitions/infrastructure_component.UpdateICRequest' type: object - description: InfrastructureComponent ID in: path diff --git a/helper/test_data.go b/helper/test_data.go index 0d6f304..60a050b 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -88,34 +88,36 @@ var NewUserC = UserRequest{ // Infrastructure components -var propertiesA = json.RawMessage(`{"location" : "ACSlab"}`) -var propertiesB = json.RawMessage(`{"location" : "ACSlab"}`) +var propertiesA = json.RawMessage(`{"prop1" : "a nice prop"}`) +var propertiesB = json.RawMessage(`{"prop1" : "not so nice"}`) var ICA = database.InfrastructureComponent{ - UUID: "4854af30-325f-44a5-ad59-b67b2597de68", - Host: "xxx.yyy.zzz.aaa", - Type: "DPsim", - Category: "Simulator", - Name: "Test DPsim Simulator", - Uptime: 0, - State: "running", - StateUpdateAt: time.Now().Format(time.RFC1123), - Properties: postgres.Jsonb{propertiesA}, - RawProperties: postgres.Jsonb{propertiesA}, + UUID: "4854af30-325f-44a5-ad59-b67b2597de68", + WebsocketURL: "xxx.yyy.zzz.aaa", + Type: "DPsim", + Category: "Simulator", + Name: "Test DPsim Simulator", + Uptime: 0, + State: "running", + Location: "ACS Laboratory", + Description: "This is a test description", + //StateUpdateAt: time.Now().Format(time.RFC1123), + Properties: postgres.Jsonb{propertiesA}, } var ICB = database.InfrastructureComponent{ - UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", - Host: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig", - APIHost: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api", - Type: "VILLASnode Signal Generator", - Category: "Signal Generator", - Name: "ACS Demo Signals", - Uptime: 0, - State: "idle", - StateUpdateAt: time.Now().Format(time.RFC1123), - Properties: postgres.Jsonb{propertiesB}, - RawProperties: postgres.Jsonb{propertiesB}, + UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", + WebsocketURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig", + APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api", + Type: "VILLASnode Signal Generator", + Category: "Signal Generator", + Name: "ACS Demo Signals", + Uptime: 0, + State: "idle", + Location: "k8s", + Description: "A signal generator for testing purposes", + //StateUpdateAt: time.Now().Format(time.RFC1123), + Properties: postgres.Jsonb{propertiesB}, } // Scenarios diff --git a/routes/component-configuration/config_test.go b/routes/component-configuration/config_test.go index e283f99..08571f1 100644 --- a/routes/component-configuration/config_test.go +++ b/routes/component-configuration/config_test.go @@ -49,13 +49,15 @@ type ConfigRequest struct { } type ICRequest struct { - UUID string `json:"uuid,omitempty"` - Host string `json:"host,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Category string `json:"category,omitempty"` - State string `json:"state,omitempty"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + Properties postgres.Jsonb `json:"properties,omitempty"` } type ScenarioRequest struct { @@ -72,13 +74,15 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { // POST $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -88,13 +92,15 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { // POST a second IC to change to that IC during testing newICB := ICRequest{ - UUID: helper.ICB.UUID, - Host: helper.ICB.Host, - Type: helper.ICB.Type, - Name: helper.ICB.Name, - Category: helper.ICB.Category, - State: helper.ICB.State, - Properties: helper.ICB.Properties, + UUID: helper.ICB.UUID, + WebsocketURL: helper.ICB.WebsocketURL, + Type: helper.ICB.Type, + Name: helper.ICB.Name, + Category: helper.ICB.Category, + State: helper.ICB.State, + Location: helper.ICB.Location, + Description: helper.ICB.Description, + Properties: helper.ICB.Properties, } _, resp, _ = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 178fb9b..79cc91c 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -39,14 +39,16 @@ import ( var router *gin.Engine type ICRequest struct { - UUID string `json:"uuid,omitempty"` - Host string `json:"host,omitempty"` - APIHost string `json:"apihost,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Category string `json:"category,omitempty"` - State string `json:"state,omitempty"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + APIURL string `json:"apiurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + Properties postgres.Jsonb `json:"properties,omitempty"` } func TestMain(m *testing.M) { @@ -100,13 +102,16 @@ func TestAddICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + APIURL: helper.ICB.APIURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -152,13 +157,15 @@ func TestAddICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } // This should fail with unprocessable entity 422 error code @@ -181,13 +188,15 @@ func TestUpdateICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -210,7 +219,7 @@ func TestUpdateICAsAdmin(t *testing.T) { assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) // Test PUT IC - newIC.Host = "ThisIsMyNewHost" + newIC.WebsocketURL = "ThisIsMyNewURL" code, resp, err = helper.TestEndpoint(router, token, fmt.Sprintf("/api/ic/%v", newICID), "PUT", helper.KeyModels{"ic": newIC}) assert.NoError(t, err) @@ -245,13 +254,15 @@ func TestUpdateICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -269,7 +280,7 @@ func TestUpdateICAsUser(t *testing.T) { // Test PUT IC // This should fail with unprocessable entity status code 422 - newIC.Host = "ThisIsMyNewHost" + newIC.WebsocketURL = "ThisIsMyNewURL" code, resp, err = helper.TestEndpoint(router, token, fmt.Sprintf("/api/ic/%v", newICID), "PUT", helper.KeyModels{"ic": newIC}) assert.NoError(t, err) @@ -289,13 +300,15 @@ func TestDeleteICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -341,13 +354,15 @@ func TestDeleteICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -365,7 +380,7 @@ func TestDeleteICAsUser(t *testing.T) { // Test DELETE ICs // This should fail with unprocessable entity status code 422 - newIC.Host = "ThisIsMyNewHost" + newIC.WebsocketURL = "ThisIsMyNewURL" code, resp, err = helper.TestEndpoint(router, token, fmt.Sprintf("/api/ic/%v", newICID), "DELETE", nil) assert.NoError(t, err) @@ -389,13 +404,15 @@ func TestGetAllICs(t *testing.T) { // test POST ic/ $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -404,13 +421,15 @@ func TestGetAllICs(t *testing.T) { // test POST ic/ $newICB newICB := ICRequest{ - UUID: helper.ICB.UUID, - Host: helper.ICB.Host, - Type: helper.ICB.Type, - Name: helper.ICB.Name, - Category: helper.ICB.Category, - State: helper.ICB.State, - Properties: helper.ICB.Properties, + UUID: helper.ICB.UUID, + WebsocketURL: helper.ICB.WebsocketURL, + Type: helper.ICB.Type, + Name: helper.ICB.Name, + Category: helper.ICB.Category, + State: helper.ICB.State, + Location: helper.ICB.Location, + Description: helper.ICB.Description, + Properties: helper.ICB.Properties, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) @@ -449,13 +468,15 @@ func TestGetConfigsOfIC(t *testing.T) { // test POST ic/ $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index 6ecc03b..248baaf 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -32,25 +32,29 @@ import ( var validate *validator.Validate type validNewIC struct { - UUID string `form:"UUID" validate:"required"` - Host string `form:"Host" validate:"omitempty"` - APIHost string `form:"APIHost" validate:"omitempty"` - Type string `form:"Type" validate:"required"` - Name string `form:"Name" validate:"required"` - Category string `form:"Category" validate:"required"` - Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` - State string `form:"State" validate:"omitempty"` + UUID string `form:"UUID" validate:"required"` + WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` + APIURL string `form:"APIURL" validate:"omitempty"` + Type string `form:"Type" validate:"required"` + Name string `form:"Name" validate:"required"` + Category string `form:"Category" validate:"required"` + Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` + State string `form:"State" validate:"omitempty"` + Location string `form:"Location" validate:"omitempty"` + Description string `form:"Description" validate:"omitempty"` } type validUpdatedIC struct { - UUID string `form:"UUID" validate:"omitempty"` - Host string `form:"Host" validate:"omitempty"` - APIHost string `form:"APIHost" validate:"omitempty"` - Type string `form:"Type" validate:"omitempty"` - Name string `form:"Name" validate:"omitempty"` - Category string `form:"Category" validate:"omitempty"` - Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` - State string `form:"State" validate:"omitempty"` + UUID string `form:"UUID" validate:"omitempty"` + WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` + APIURL string `form:"APIURL" validate:"omitempty"` + Type string `form:"Type" validate:"omitempty"` + Name string `form:"Name" validate:"omitempty"` + Category string `form:"Category" validate:"omitempty"` + Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` + State string `form:"State" validate:"omitempty"` + Location string `form:"Location" validate:"omitempty"` + Description string `form:"Description" validate:"omitempty"` } type AddICRequest struct { @@ -77,11 +81,13 @@ func (r *AddICRequest) CreateIC() InfrastructureComponent { var s InfrastructureComponent s.UUID = r.InfrastructureComponent.UUID - s.Host = r.InfrastructureComponent.Host - s.APIHost = r.InfrastructureComponent.APIHost + s.WebsocketURL = r.InfrastructureComponent.WebsocketURL + s.APIURL = r.InfrastructureComponent.APIURL s.Type = r.InfrastructureComponent.Type s.Name = r.InfrastructureComponent.Name s.Category = r.InfrastructureComponent.Category + s.Location = r.InfrastructureComponent.Location + s.Description = r.InfrastructureComponent.Description s.Properties = r.InfrastructureComponent.Properties if r.InfrastructureComponent.State != "" { s.State = r.InfrastructureComponent.State @@ -102,12 +108,12 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur s.UUID = r.InfrastructureComponent.UUID } - if r.InfrastructureComponent.Host != "" { - s.Host = r.InfrastructureComponent.Host + if r.InfrastructureComponent.WebsocketURL != "" { + s.WebsocketURL = r.InfrastructureComponent.WebsocketURL } - if r.InfrastructureComponent.APIHost != "" { - s.APIHost = r.InfrastructureComponent.APIHost + if r.InfrastructureComponent.APIURL != "" { + s.APIURL = r.InfrastructureComponent.APIURL } if r.InfrastructureComponent.Type != "" { @@ -126,6 +132,14 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur s.State = r.InfrastructureComponent.State } + if r.InfrastructureComponent.Location != "" { + s.Location = r.InfrastructureComponent.Location + } + + if r.InfrastructureComponent.Description != "" { + s.Description = r.InfrastructureComponent.Description + } + // set last update time s.StateUpdateAt = time.Now().Format(time.RFC1123) diff --git a/routes/signal/signal_test.go b/routes/signal/signal_test.go index 9ab5ef1..5840ba9 100644 --- a/routes/signal/signal_test.go +++ b/routes/signal/signal_test.go @@ -56,13 +56,15 @@ type ConfigRequest struct { } type ICRequest struct { - UUID string `json:"uuid,omitempty"` - Host string `json:"host,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Category string `json:"category,omitempty"` - State string `json:"state,omitempty"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + Properties postgres.Jsonb `json:"properties,omitempty"` } type ScenarioRequest struct { @@ -79,13 +81,15 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) { // POST $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - Host: helper.ICA.Host, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + Properties: helper.ICA.Properties, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) From adabfc8e9ca71dd7eef4039e4d31eb0ac6838f90 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 20 Oct 2020 15:27:36 +0200 Subject: [PATCH 02/18] Transform Properties into StartParameterScheme --- database/models.go | 4 +- doc/api/docs.go | 10 +- doc/api/swagger.json | 8 +- doc/api/swagger.yaml | 8 +- helper/test_data.go | 4 +- routes/component-configuration/config_test.go | 54 ++--- routes/infrastructure-component/ic_test.go | 184 +++++++++--------- .../infrastructure-component/ic_validators.go | 46 ++--- routes/signal/signal_test.go | 36 ++-- 9 files changed, 177 insertions(+), 177 deletions(-) diff --git a/database/models.go b/database/models.go index 83852b8..1bb61e4 100644 --- a/database/models.go +++ b/database/models.go @@ -138,8 +138,8 @@ type InfrastructureComponent struct { Location string `json:"location" gorm:"default:''"` // Description of the IC Description string `json:"description" gorm:"default:''"` - // Properties of IC as JSON string - Properties postgres.Jsonb `json:"properties"` + // JSON scheme of start parameters for IC + StartParameterScheme postgres.Jsonb `json:"startparameterscheme"` // ComponentConfigurations in which the IC is used ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ICID"` } diff --git a/doc/api/docs.go b/doc/api/docs.go index becdbf9..e5d15c6 100644 --- a/doc/api/docs.go +++ b/doc/api/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2020-10-20 12:56:57.842992188 +0200 CEST m=+0.084567298 +// 2020-10-20 15:23:51.266197121 +0200 CEST m=+0.098130845 package docs @@ -3000,8 +3000,8 @@ var doc = `{ "description": "Name of the IC", "type": "string" }, - "properties": { - "description": "Properties of IC as JSON string", + "startparameterscheme": { + "description": "JSON scheme of start parameters for IC", "type": "string" }, "state": { @@ -3397,7 +3397,7 @@ var doc = `{ "Name": { "type": "string" }, - "Properties": { + "StartParameterScheme": { "type": "string" }, "State": { @@ -3432,7 +3432,7 @@ var doc = `{ "Name": { "type": "string" }, - "Properties": { + "StartParameterScheme": { "type": "string" }, "State": { diff --git a/doc/api/swagger.json b/doc/api/swagger.json index 74f22a7..5bbbc4a 100644 --- a/doc/api/swagger.json +++ b/doc/api/swagger.json @@ -2983,8 +2983,8 @@ "description": "Name of the IC", "type": "string" }, - "properties": { - "description": "Properties of IC as JSON string", + "startparameterscheme": { + "description": "JSON scheme of start parameters for IC", "type": "string" }, "state": { @@ -3380,7 +3380,7 @@ "Name": { "type": "string" }, - "Properties": { + "StartParameterScheme": { "type": "string" }, "State": { @@ -3415,7 +3415,7 @@ "Name": { "type": "string" }, - "Properties": { + "StartParameterScheme": { "type": "string" }, "State": { diff --git a/doc/api/swagger.yaml b/doc/api/swagger.yaml index b1c2b74..c59428e 100644 --- a/doc/api/swagger.yaml +++ b/doc/api/swagger.yaml @@ -163,8 +163,8 @@ definitions: name: description: Name of the IC type: string - properties: - description: Properties of IC as JSON string + startparameterscheme: + description: JSON scheme of start parameters for IC type: string state: description: State of the IC @@ -431,7 +431,7 @@ definitions: type: string Name: type: string - Properties: + StartParameterScheme: type: string State: type: string @@ -459,7 +459,7 @@ definitions: type: string Name: type: string - Properties: + StartParameterScheme: type: string State: type: string diff --git a/helper/test_data.go b/helper/test_data.go index 60a050b..32ef8fc 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -102,7 +102,7 @@ var ICA = database.InfrastructureComponent{ Location: "ACS Laboratory", Description: "This is a test description", //StateUpdateAt: time.Now().Format(time.RFC1123), - Properties: postgres.Jsonb{propertiesA}, + StartParameterScheme: postgres.Jsonb{propertiesA}, } var ICB = database.InfrastructureComponent{ @@ -117,7 +117,7 @@ var ICB = database.InfrastructureComponent{ Location: "k8s", Description: "A signal generator for testing purposes", //StateUpdateAt: time.Now().Format(time.RFC1123), - Properties: postgres.Jsonb{propertiesB}, + StartParameterScheme: postgres.Jsonb{propertiesB}, } // Scenarios diff --git a/routes/component-configuration/config_test.go b/routes/component-configuration/config_test.go index 08571f1..065136d 100644 --- a/routes/component-configuration/config_test.go +++ b/routes/component-configuration/config_test.go @@ -49,15 +49,15 @@ type ConfigRequest struct { } type ICRequest struct { - UUID string `json:"uuid,omitempty"` - WebsocketURL string `json:"websocketurl,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Category string `json:"category,omitempty"` - State string `json:"state,omitempty"` - Location string `json:"location,omitempty"` - Description string `json:"description,omitempty"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` } type ScenarioRequest struct { @@ -74,15 +74,15 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { // POST $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -92,15 +92,15 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { // POST a second IC to change to that IC during testing newICB := ICRequest{ - UUID: helper.ICB.UUID, - WebsocketURL: helper.ICB.WebsocketURL, - Type: helper.ICB.Type, - Name: helper.ICB.Name, - Category: helper.ICB.Category, - State: helper.ICB.State, - Location: helper.ICB.Location, - Description: helper.ICB.Description, - Properties: helper.ICB.Properties, + UUID: helper.ICB.UUID, + WebsocketURL: helper.ICB.WebsocketURL, + Type: helper.ICB.Type, + Name: helper.ICB.Name, + Category: helper.ICB.Category, + State: helper.ICB.State, + Location: helper.ICB.Location, + Description: helper.ICB.Description, + StartParameterScheme: helper.ICB.StartParameterScheme, } _, resp, _ = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 79cc91c..a475082 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -39,16 +39,16 @@ import ( var router *gin.Engine 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"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + APIURL string `json:"apiurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` } func TestMain(m *testing.M) { @@ -102,16 +102,16 @@ func TestAddICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - APIURL: helper.ICB.APIURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + APIURL: helper.ICB.APIURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -157,15 +157,15 @@ func TestAddICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } // This should fail with unprocessable entity 422 error code @@ -188,15 +188,15 @@ func TestUpdateICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -254,15 +254,15 @@ func TestUpdateICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -300,15 +300,15 @@ func TestDeleteICAsAdmin(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -354,15 +354,15 @@ func TestDeleteICAsUser(t *testing.T) { // test POST ic/ $newIC newIC := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -404,15 +404,15 @@ func TestGetAllICs(t *testing.T) { // test POST ic/ $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -421,15 +421,15 @@ func TestGetAllICs(t *testing.T) { // test POST ic/ $newICB newICB := ICRequest{ - UUID: helper.ICB.UUID, - WebsocketURL: helper.ICB.WebsocketURL, - Type: helper.ICB.Type, - Name: helper.ICB.Name, - Category: helper.ICB.Category, - State: helper.ICB.State, - Location: helper.ICB.Location, - Description: helper.ICB.Description, - Properties: helper.ICB.Properties, + UUID: helper.ICB.UUID, + WebsocketURL: helper.ICB.WebsocketURL, + Type: helper.ICB.Type, + Name: helper.ICB.Name, + Category: helper.ICB.Category, + State: helper.ICB.State, + Location: helper.ICB.Location, + Description: helper.ICB.Description, + StartParameterScheme: helper.ICB.StartParameterScheme, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) @@ -468,15 +468,15 @@ func TestGetConfigsOfIC(t *testing.T) { // test POST ic/ $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index 248baaf..7aefd54 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -32,29 +32,29 @@ import ( var validate *validator.Validate type validNewIC struct { - UUID string `form:"UUID" validate:"required"` - WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` - APIURL string `form:"APIURL" validate:"omitempty"` - Type string `form:"Type" validate:"required"` - Name string `form:"Name" validate:"required"` - Category string `form:"Category" validate:"required"` - Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` - State string `form:"State" validate:"omitempty"` - Location string `form:"Location" validate:"omitempty"` - Description string `form:"Description" validate:"omitempty"` + UUID string `form:"UUID" validate:"required"` + WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` + APIURL string `form:"APIURL" validate:"omitempty"` + Type string `form:"Type" validate:"required"` + Name string `form:"Name" validate:"required"` + Category string `form:"Category" validate:"required"` + StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` + State string `form:"State" validate:"omitempty"` + Location string `form:"Location" validate:"omitempty"` + Description string `form:"Description" validate:"omitempty"` } type validUpdatedIC struct { - UUID string `form:"UUID" validate:"omitempty"` - WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` - APIURL string `form:"APIURL" validate:"omitempty"` - Type string `form:"Type" validate:"omitempty"` - Name string `form:"Name" validate:"omitempty"` - Category string `form:"Category" validate:"omitempty"` - Properties postgres.Jsonb `form:"Properties" validate:"omitempty"` - State string `form:"State" validate:"omitempty"` - Location string `form:"Location" validate:"omitempty"` - Description string `form:"Description" validate:"omitempty"` + UUID string `form:"UUID" validate:"omitempty"` + WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` + APIURL string `form:"APIURL" validate:"omitempty"` + Type string `form:"Type" validate:"omitempty"` + Name string `form:"Name" validate:"omitempty"` + Category string `form:"Category" validate:"omitempty"` + StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` + State string `form:"State" validate:"omitempty"` + Location string `form:"Location" validate:"omitempty"` + Description string `form:"Description" validate:"omitempty"` } type AddICRequest struct { @@ -88,7 +88,7 @@ func (r *AddICRequest) CreateIC() InfrastructureComponent { s.Category = r.InfrastructureComponent.Category s.Location = r.InfrastructureComponent.Location s.Description = r.InfrastructureComponent.Description - s.Properties = r.InfrastructureComponent.Properties + s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme if r.InfrastructureComponent.State != "" { s.State = r.InfrastructureComponent.State } else { @@ -147,11 +147,11 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur var emptyJson postgres.Jsonb // Serialize empty json and params emptyJson_ser, _ := json.Marshal(emptyJson) - startParams_ser, _ := json.Marshal(r.InfrastructureComponent.Properties) + startParams_ser, _ := json.Marshal(r.InfrastructureComponent.StartParameterScheme) opts := jsondiff.DefaultConsoleOptions() diff, _ := jsondiff.Compare(emptyJson_ser, startParams_ser, &opts) if diff.String() != "FullMatch" { - s.Properties = r.InfrastructureComponent.Properties + s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme } return s diff --git a/routes/signal/signal_test.go b/routes/signal/signal_test.go index 5840ba9..bdc5e46 100644 --- a/routes/signal/signal_test.go +++ b/routes/signal/signal_test.go @@ -56,15 +56,15 @@ type ConfigRequest struct { } type ICRequest struct { - UUID string `json:"uuid,omitempty"` - WebsocketURL string `json:"websocketurl,omitempty"` - Type string `json:"type,omitempty"` - Name string `json:"name,omitempty"` - Category string `json:"category,omitempty"` - State string `json:"state,omitempty"` - Location string `json:"location,omitempty"` - Description string `json:"description,omitempty"` - Properties postgres.Jsonb `json:"properties,omitempty"` + UUID string `json:"uuid,omitempty"` + WebsocketURL string `json:"websocketurl,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + State string `json:"state,omitempty"` + Location string `json:"location,omitempty"` + Description string `json:"description,omitempty"` + StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` } type ScenarioRequest struct { @@ -81,15 +81,15 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) { // POST $newICA newICA := ICRequest{ - UUID: helper.ICA.UUID, - WebsocketURL: helper.ICA.WebsocketURL, - Type: helper.ICA.Type, - Name: helper.ICA.Name, - Category: helper.ICA.Category, - State: helper.ICA.State, - Location: helper.ICA.Location, - Description: helper.ICA.Description, - Properties: helper.ICA.Properties, + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) From 4922d8c4cedb9a156d7005e59c75b8d33345b16f Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 20 Oct 2020 16:20:04 +0200 Subject: [PATCH 03/18] Add field ManagedExternally to IC data model --- amqp/amqpclient.go | 230 ++++++++++-------- database/models.go | 2 + helper/test_data.go | 2 + routes/component-configuration/config_test.go | 3 + routes/infrastructure-component/ic_test.go | 10 + .../infrastructure-component/ic_validators.go | 11 +- routes/signal/signal_test.go | 2 + 7 files changed, 152 insertions(+), 108 deletions(-) diff --git a/amqp/amqpclient.go b/amqp/amqpclient.go index 8adedad..a5358e0 100644 --- a/amqp/amqpclient.go +++ b/amqp/amqpclient.go @@ -126,7 +126,10 @@ func ConnectAMQP(uri string) error { go func() { for { for message := range client.replies { - processMessage(message) + err = processMessage(message) + if err != nil { + log.Println(err.Error()) + } } time.Sleep(2) // sleep for 2 sek } @@ -229,23 +232,21 @@ func StartAMQP(AMQPurl string, api *gin.RouterGroup) error { return nil } -func processMessage(message amqp.Delivery) { +func processMessage(message amqp.Delivery) error { log.Println("Processing AMQP message: ", string(message.Body)) var payload ICUpdate err := json.Unmarshal(message.Body, &payload) if err != nil { - log.Println("AMQP: Could not unmarshal message to JSON:", string(message.Body), "err: ", err) - return + return fmt.Errorf("AMQP: Could not unmarshal message to JSON: %v err: %v", string(message.Body), err) } ICUUID := payload.Properties.UUID _, err = uuid.Parse(ICUUID) if err != nil { - log.Printf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) - return + return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) } var sToBeUpdated infrastructure_component.InfrastructureComponent @@ -253,107 +254,124 @@ func processMessage(message amqp.Delivery) { if err == gorm.ErrRecordNotFound { // create new record - var newICReq infrastructure_component.AddICRequest - newICReq.InfrastructureComponent.UUID = payload.Properties.UUID - if payload.Properties.Name == nil || - payload.Properties.Category == nil || - payload.Properties.Type == nil { - // cannot create new IC because required information (name, type, and/or category missing) - log.Println("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") - return - } - newICReq.InfrastructureComponent.Name = *payload.Properties.Name - newICReq.InfrastructureComponent.Category = *payload.Properties.Category - newICReq.InfrastructureComponent.Type = *payload.Properties.Type - - // add optional params - if payload.State != nil { - newICReq.InfrastructureComponent.State = *payload.State - } else { - newICReq.InfrastructureComponent.State = "unknown" - } - if payload.Properties.WS_url != nil { - newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - newICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - newICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // Validate the new IC - err = newICReq.Validate() - if err != nil { - log.Println("AMQP: Validation of new IC failed:", err) - return - } - - // Create the new IC - newIC := newICReq.CreateIC() - - // save IC - err = newIC.Save() - if err != nil { - log.Println("AMQP: Saving new IC to DB failed:", err) - return - } - - log.Println("AMQP: Created IC ", newIC.Name) - + err = createNewIC(payload) } else if err != nil { - log.Println("AMQP: Database error for IC", ICUUID, " DB error message: ", err) - return + // database error + err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) } else { - - var updatedICReq infrastructure_component.UpdateICRequest - if payload.State != nil { - updatedICReq.InfrastructureComponent.State = *payload.State - } - if payload.Properties.Type != nil { - updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type - } - if payload.Properties.Category != nil { - updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category - } - if payload.Properties.Name != nil { - updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name - } - if payload.Properties.WS_url != nil { - updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} - updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // Validate the updated IC - if err = updatedICReq.Validate(); err != nil { - log.Println("AMQP: Validation of updated IC failed:", err) - return - } - - // Create the updated IC from old IC - updatedIC := updatedICReq.UpdatedIC(sToBeUpdated) - - // Finally update the IC in the DB - err = sToBeUpdated.Update(updatedIC) - if err != nil { - log.Println("AMQP: Unable to update IC", sToBeUpdated.Name, "in DB: ", err) - return - } - - log.Println("AMQP: Updated IC ", sToBeUpdated.Name) + // update record + err = updateIC(payload, sToBeUpdated) } + + return err +} + +func createNewIC(payload ICUpdate) error { + + var newICReq infrastructure_component.AddICRequest + newICReq.InfrastructureComponent.UUID = payload.Properties.UUID + if payload.Properties.Name == nil || + payload.Properties.Category == nil || + payload.Properties.Type == nil { + // cannot create new IC because required information (name, type, and/or category missing) + return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") + } + newICReq.InfrastructureComponent.Name = *payload.Properties.Name + newICReq.InfrastructureComponent.Category = *payload.Properties.Category + newICReq.InfrastructureComponent.Type = *payload.Properties.Type + + // add optional params + if payload.State != nil { + newICReq.InfrastructureComponent.State = *payload.State + } else { + newICReq.InfrastructureComponent.State = "unknown" + } + if payload.Properties.WS_url != nil { + newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + newICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is created via AMQP + newICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the new IC + err := newICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) + } + + // Create the new IC + newIC := newICReq.CreateIC() + + // save IC + err = newIC.Save() + if err != nil { + return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) + } + + return nil +} + +func updateIC(payload ICUpdate, sToBeUpdated infrastructure_component.InfrastructureComponent) error { + var updatedICReq infrastructure_component.UpdateICRequest + if payload.State != nil { + updatedICReq.InfrastructureComponent.State = *payload.State + } + if payload.Properties.Type != nil { + updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type + } + if payload.Properties.Category != nil { + updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category + } + if payload.Properties.Name != nil { + updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name + } + if payload.Properties.WS_url != nil { + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is updated via AMQP + updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the updated IC + err := updatedICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) + } + + // Create the updated IC from old IC + updatedIC := updatedICReq.UpdatedIC(sToBeUpdated) + + // Finally update the IC in the DB + err = sToBeUpdated.Update(updatedIC) + if err != nil { + return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", sToBeUpdated.Name, err) + } + + return err +} + +func newTrue() *bool { + b := true + return &b } diff --git a/database/models.go b/database/models.go index 1bb61e4..8ffa86d 100644 --- a/database/models.go +++ b/database/models.go @@ -140,6 +140,8 @@ type InfrastructureComponent struct { Description string `json:"description" gorm:"default:''"` // JSON scheme of start parameters for IC StartParameterScheme postgres.Jsonb `json:"startparameterscheme"` + // Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller) + ManagedExternally bool `json:"managedexternally" gorm:"default:false"` // ComponentConfigurations in which the IC is used ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ICID"` } diff --git a/helper/test_data.go b/helper/test_data.go index 32ef8fc..e8d3ced 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -103,6 +103,7 @@ var ICA = database.InfrastructureComponent{ Description: "This is a test description", //StateUpdateAt: time.Now().Format(time.RFC1123), StartParameterScheme: postgres.Jsonb{propertiesA}, + ManagedExternally: true, } var ICB = database.InfrastructureComponent{ @@ -118,6 +119,7 @@ var ICB = database.InfrastructureComponent{ Description: "A signal generator for testing purposes", //StateUpdateAt: time.Now().Format(time.RFC1123), StartParameterScheme: postgres.Jsonb{propertiesB}, + ManagedExternally: false, } // Scenarios diff --git a/routes/component-configuration/config_test.go b/routes/component-configuration/config_test.go index 065136d..4098551 100644 --- a/routes/component-configuration/config_test.go +++ b/routes/component-configuration/config_test.go @@ -58,6 +58,7 @@ type ICRequest struct { Location string `json:"location,omitempty"` Description string `json:"description,omitempty"` StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` + ManagedExternally *bool `json:"managedexternally,omitempty"` } type ScenarioRequest struct { @@ -83,6 +84,7 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -101,6 +103,7 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { Location: helper.ICB.Location, Description: helper.ICB.Description, StartParameterScheme: helper.ICB.StartParameterScheme, + ManagedExternally: &helper.ICB.ManagedExternally, } _, resp, _ = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index a475082..334746e 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -49,6 +49,7 @@ type ICRequest struct { Location string `json:"location,omitempty"` Description string `json:"description,omitempty"` StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` + ManagedExternally *bool `json:"managedexternally,omitempty"` } func TestMain(m *testing.M) { @@ -112,6 +113,7 @@ func TestAddICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -166,6 +168,7 @@ func TestAddICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } // This should fail with unprocessable entity 422 error code @@ -197,6 +200,7 @@ func TestUpdateICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -263,6 +267,7 @@ func TestUpdateICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -309,6 +314,7 @@ func TestDeleteICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -363,6 +369,7 @@ func TestDeleteICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -413,6 +420,7 @@ func TestGetAllICs(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -430,6 +438,7 @@ func TestGetAllICs(t *testing.T) { Location: helper.ICB.Location, Description: helper.ICB.Description, StartParameterScheme: helper.ICB.StartParameterScheme, + ManagedExternally: &helper.ICB.ManagedExternally, } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) @@ -477,6 +486,7 @@ func TestGetConfigsOfIC(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index 7aefd54..da3484b 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -38,10 +38,11 @@ type validNewIC struct { Type string `form:"Type" validate:"required"` Name string `form:"Name" validate:"required"` Category string `form:"Category" validate:"required"` - StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` State string `form:"State" validate:"omitempty"` Location string `form:"Location" validate:"omitempty"` Description string `form:"Description" validate:"omitempty"` + StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` + ManagedExternally *bool `form:"ManagedExternally" validate:"required"` } type validUpdatedIC struct { @@ -51,10 +52,11 @@ type validUpdatedIC struct { Type string `form:"Type" validate:"omitempty"` Name string `form:"Name" validate:"omitempty"` Category string `form:"Category" validate:"omitempty"` - StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` State string `form:"State" validate:"omitempty"` Location string `form:"Location" validate:"omitempty"` Description string `form:"Description" validate:"omitempty"` + StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` + ManagedExternally *bool `form:"ManagedExternally" validate:"required"` } type AddICRequest struct { @@ -89,6 +91,7 @@ func (r *AddICRequest) CreateIC() InfrastructureComponent { s.Location = r.InfrastructureComponent.Location s.Description = r.InfrastructureComponent.Description s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme + s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally if r.InfrastructureComponent.State != "" { s.State = r.InfrastructureComponent.State } else { @@ -140,6 +143,10 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur s.Description = r.InfrastructureComponent.Description } + if r.InfrastructureComponent.ManagedExternally != nil { + s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally + } + // set last update time s.StateUpdateAt = time.Now().Format(time.RFC1123) diff --git a/routes/signal/signal_test.go b/routes/signal/signal_test.go index bdc5e46..045d53c 100644 --- a/routes/signal/signal_test.go +++ b/routes/signal/signal_test.go @@ -65,6 +65,7 @@ type ICRequest struct { Location string `json:"location,omitempty"` Description string `json:"description,omitempty"` StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` + ManagedExternally *bool `json:"managedexternally,omitempty"` } type ScenarioRequest struct { @@ -90,6 +91,7 @@ func addScenarioAndICAndConfig() (scenarioID uint, ICID uint, configID uint) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, } _, resp, _ := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) From f9cef090d4cd3f7b77ee22e45b6a8b4b7b3ef76c Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 20 Oct 2020 16:51:07 +0200 Subject: [PATCH 04/18] merge amqp module into infrastructure-component module to avoid circular deps, add TODOs in IC endpoints --- amqp/amqp_endpoints.go | 85 ------------ routes/healthz/healthz_endpoint.go | 4 +- routes/healthz/healthz_test.go | 4 +- .../infrastructure-component}/amqpclient.go | 121 +----------------- .../infrastructure-component/ic_endpoints.go | 61 +++++++++ routes/infrastructure-component/ic_methods.go | 114 +++++++++++++++++ start.go | 4 +- 7 files changed, 186 insertions(+), 207 deletions(-) delete mode 100644 amqp/amqp_endpoints.go rename {amqp => routes/infrastructure-component}/amqpclient.go (59%) diff --git a/amqp/amqp_endpoints.go b/amqp/amqp_endpoints.go deleted file mode 100644 index 8082859..0000000 --- a/amqp/amqp_endpoints.go +++ /dev/null @@ -1,85 +0,0 @@ -/** AMQP package, endpoints. -* -* @author Sonja Happ -* @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 . -*********************************************************************************/ -package amqp - -import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" - "github.com/gin-gonic/gin" - "log" - "net/http" -) - -func RegisterAMQPEndpoint(r *gin.RouterGroup) { - r.POST("/:ICID/action", sendActionToIC) -} - -// sendActionToIC godoc -// @Summary Send an action to IC (only available if backend server is started with -amqp parameter) -// @ID sendActionToIC -// @Tags AMQP -// @Produce json -// @Param inputAction query string true "Action for IC" -// @Success 200 {object} docs.ResponseError "Action sent successfully" -// @Failure 400 {object} docs.ResponseError "Bad request" -// @Failure 404 {object} docs.ResponseError "Not found" -// @Failure 422 {object} docs.ResponseError "Unprocessable entity" -// @Failure 500 {object} docs.ResponseError "Internal server error" -// @Param ICID path int true "InfrastructureComponent ID" -// @Router /ic/{ICID}/action [post] -// @Security Bearer -func sendActionToIC(c *gin.Context) { - - ok, s := infrastructure_component.CheckPermissions(c, database.ModelInfrastructureComponentAction, database.Update, true) - if !ok { - return - } - - var actions []Action - err := c.BindJSON(&actions) - if err != nil { - helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error()) - return - } - - //now := time.Now() - log.Println("AMQP: Will attempt to send the following actions:", actions) - - for _, action := range actions { - /*if action.When == 0 { - action.When = float32(now.Unix()) - }*/ - action.UUID = new(string) - *action.UUID = s.UUID - err = SendActionAMQP(action) - if err != nil { - helper.InternalServerError(c, "Unable to send actions to IC: "+err.Error()) - return - } - } - - c.JSON(http.StatusOK, gin.H{ - "success": true, - "message": "OK.", - }) -} diff --git a/routes/healthz/healthz_endpoint.go b/routes/healthz/healthz_endpoint.go index 5d2276c..e9928ab 100644 --- a/routes/healthz/healthz_endpoint.go +++ b/routes/healthz/healthz_endpoint.go @@ -22,10 +22,10 @@ package healthz import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "log" "net/http" @@ -68,7 +68,7 @@ func getHealth(c *gin.Context) { } if len(url) != 0 { - err = amqp.CheckConnection() + err = infrastructure_component.CheckConnection() if err != nil { log.Println(err.Error()) c.JSON(http.StatusInternalServerError, gin.H{ diff --git a/routes/healthz/healthz_test.go b/routes/healthz/healthz_test.go index 24e4a61..6bdada4 100644 --- a/routes/healthz/healthz_test.go +++ b/routes/healthz/healthz_test.go @@ -22,10 +22,10 @@ package healthz import ( - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "log" @@ -78,7 +78,7 @@ func TestHealthz(t *testing.T) { amqpURI := "amqp://" + user + ":" + pass + "@" + host log.Println("AMQP URI is", amqpURI) - err = amqp.ConnectAMQP(amqpURI) + err = infrastructure_component.ConnectAMQP(amqpURI) assert.NoError(t, err) // test healthz endpoint for connected DB and AMQP client diff --git a/amqp/amqpclient.go b/routes/infrastructure-component/amqpclient.go similarity index 59% rename from amqp/amqpclient.go rename to routes/infrastructure-component/amqpclient.go index a5358e0..7bf89dd 100644 --- a/amqp/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -19,12 +19,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . *********************************************************************************/ -package amqp +package infrastructure_component import ( "encoding/json" "fmt" - infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/jinzhu/gorm" @@ -249,129 +248,19 @@ func processMessage(message amqp.Delivery) error { return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) } - var sToBeUpdated infrastructure_component.InfrastructureComponent + var sToBeUpdated InfrastructureComponent err = sToBeUpdated.ByUUID(ICUUID) if err == gorm.ErrRecordNotFound { // create new record - err = createNewIC(payload) + err = createNewICviaAMQP(payload) } else if err != nil { // database error err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) } else { - // update record - err = updateIC(payload, sToBeUpdated) + // update record based on payload + err = sToBeUpdated.updateICviaAMQP(payload) } return err } - -func createNewIC(payload ICUpdate) error { - - var newICReq infrastructure_component.AddICRequest - newICReq.InfrastructureComponent.UUID = payload.Properties.UUID - if payload.Properties.Name == nil || - payload.Properties.Category == nil || - payload.Properties.Type == nil { - // cannot create new IC because required information (name, type, and/or category missing) - return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") - } - newICReq.InfrastructureComponent.Name = *payload.Properties.Name - newICReq.InfrastructureComponent.Category = *payload.Properties.Category - newICReq.InfrastructureComponent.Type = *payload.Properties.Type - - // add optional params - if payload.State != nil { - newICReq.InfrastructureComponent.State = *payload.State - } else { - newICReq.InfrastructureComponent.State = "unknown" - } - if payload.Properties.WS_url != nil { - newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - newICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - newICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is created via AMQP - newICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the new IC - err := newICReq.Validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) - } - - // Create the new IC - newIC := newICReq.CreateIC() - - // save IC - err = newIC.Save() - if err != nil { - return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) - } - - return nil -} - -func updateIC(payload ICUpdate, sToBeUpdated infrastructure_component.InfrastructureComponent) error { - var updatedICReq infrastructure_component.UpdateICRequest - if payload.State != nil { - updatedICReq.InfrastructureComponent.State = *payload.State - } - if payload.Properties.Type != nil { - updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type - } - if payload.Properties.Category != nil { - updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category - } - if payload.Properties.Name != nil { - updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name - } - if payload.Properties.WS_url != nil { - updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url - } - if payload.Properties.API_url != nil { - updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url - } - if payload.Properties.Location != nil { - //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} - updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location - } - if payload.Properties.Description != nil { - updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is updated via AMQP - updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the updated IC - err := updatedICReq.Validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) - } - - // Create the updated IC from old IC - updatedIC := updatedICReq.UpdatedIC(sToBeUpdated) - - // Finally update the IC in the DB - err = sToBeUpdated.Update(updatedIC) - if err != nil { - return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", sToBeUpdated.Name, err) - } - - return err -} - -func newTrue() *bool { - b := true - return &b -} diff --git a/routes/infrastructure-component/ic_endpoints.go b/routes/infrastructure-component/ic_endpoints.go index afc3b32..0328ff7 100644 --- a/routes/infrastructure-component/ic_endpoints.go +++ b/routes/infrastructure-component/ic_endpoints.go @@ -24,6 +24,7 @@ package infrastructure_component import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/gin-gonic/gin" + "log" "net/http" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" @@ -38,6 +39,10 @@ func RegisterICEndpoints(r *gin.RouterGroup) { r.GET("/:ICID/configs", getConfigsOfIC) } +func RegisterAMQPEndpoint(r *gin.RouterGroup) { + r.POST("/:ICID/action", sendActionToIC) +} + // getICs godoc // @Summary Get all infrastructure components // @ID getICs @@ -96,6 +101,8 @@ func addIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Create the new IC from the request newIC := req.CreateIC() @@ -142,6 +149,8 @@ func updateIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Create the updatedIC from oldIC updatedIC := req.UpdatedIC(oldIC) @@ -196,6 +205,8 @@ func deleteIC(c *gin.Context) { return } + // TODO add case distinction here for externally managed IC + // Delete the IC err := s.delete() if !helper.DBError(c, err) { @@ -231,3 +242,53 @@ func getConfigsOfIC(c *gin.Context) { } } + +// sendActionToIC godoc +// @Summary Send an action to IC (only available if backend server is started with -amqp parameter) +// @ID sendActionToIC +// @Tags infrastructure-components +// @Produce json +// @Param inputAction query string true "Action for IC" +// @Success 200 {object} docs.ResponseError "Action sent successfully" +// @Failure 400 {object} docs.ResponseError "Bad request" +// @Failure 404 {object} docs.ResponseError "Not found" +// @Failure 422 {object} docs.ResponseError "Unprocessable entity" +// @Failure 500 {object} docs.ResponseError "Internal server error" +// @Param ICID path int true "InfrastructureComponent ID" +// @Router /ic/{ICID}/action [post] +// @Security Bearer +func sendActionToIC(c *gin.Context) { + + ok, s := CheckPermissions(c, database.ModelInfrastructureComponentAction, database.Update, true) + if !ok { + return + } + + var actions []Action + err := c.BindJSON(&actions) + if err != nil { + helper.BadRequestError(c, "Error binding form data to JSON: "+err.Error()) + return + } + + //now := time.Now() + log.Println("AMQP: Will attempt to send the following actions:", actions) + + for _, action := range actions { + /*if action.When == 0 { + action.When = float32(now.Unix()) + }*/ + action.UUID = new(string) + *action.UUID = s.UUID + err = SendActionAMQP(action) + if err != nil { + helper.InternalServerError(c, "Unable to send actions to IC: "+err.Error()) + return + } + } + + c.JSON(http.StatusOK, gin.H{ + "success": true, + "message": "OK.", + }) +} diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index 62a2449..674fd8f 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -77,3 +77,117 @@ func (s *InfrastructureComponent) getConfigs() ([]database.ComponentConfiguratio err := db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error return configs, len(configs), err } + +func createNewICviaAMQP(payload ICUpdate) error { + + var newICReq AddICRequest + newICReq.InfrastructureComponent.UUID = payload.Properties.UUID + if payload.Properties.Name == nil || + payload.Properties.Category == nil || + payload.Properties.Type == nil { + // cannot create new IC because required information (name, type, and/or category missing) + return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") + } + newICReq.InfrastructureComponent.Name = *payload.Properties.Name + newICReq.InfrastructureComponent.Category = *payload.Properties.Category + newICReq.InfrastructureComponent.Type = *payload.Properties.Type + + // add optional params + if payload.State != nil { + newICReq.InfrastructureComponent.State = *payload.State + } else { + newICReq.InfrastructureComponent.State = "unknown" + } + // TODO check if state is "gone" and abort creation of IC in this case + + if payload.Properties.WS_url != nil { + newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + newICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is created via AMQP + newICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the new IC + err := newICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) + } + + // Create the new IC + newIC := newICReq.CreateIC() + + // save IC + err = newIC.Save() + if err != nil { + return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) + } + + return nil +} + +func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { + var updatedICReq UpdateICRequest + if payload.State != nil { + updatedICReq.InfrastructureComponent.State = *payload.State + // TODO check if state is "gone" and attempt to remove IC from DB if it still exists + // TODO if state is different from "gone", continue to update the IC + } + if payload.Properties.Type != nil { + updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type + } + if payload.Properties.Category != nil { + updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category + } + if payload.Properties.Name != nil { + updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name + } + if payload.Properties.WS_url != nil { + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + } + if payload.Properties.API_url != nil { + updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + } + if payload.Properties.Location != nil { + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location + } + if payload.Properties.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is updated via AMQP + updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the updated IC + err := updatedICReq.Validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) + } + + // Create the updated IC from old IC + updatedIC := updatedICReq.UpdatedIC(*s) + + // Finally update the IC in the DB + err = s.Update(updatedIC) + if err != nil { + return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", s.Name, err) + } + + return err +} + +func newTrue() *bool { + b := true + return &b +} diff --git a/start.go b/start.go index c229163..e222f90 100644 --- a/start.go +++ b/start.go @@ -23,12 +23,12 @@ package main import ( "fmt" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/amqp" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" apidocs "git.rwth-aachen.de/acs/public/villas/web-backend-go/doc/api" // doc/api folder is used by Swag CLI, you have to import it "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "log" ) @@ -104,7 +104,7 @@ func main() { if amqphost != "" { // create amqp URL based on username, password and host amqpurl := "amqp://" + amqpuser + ":" + amqppass + "@" + amqphost - err = amqp.StartAMQP(amqpurl, api) + err = infrastructure_component.StartAMQP(amqpurl, api) if err != nil { panic(err) } From b07cd23a7087b2bc779c327edfd9cf90527289d5 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 21 Oct 2020 17:16:15 +0200 Subject: [PATCH 05/18] continue integration of new AMQP functionality #31 #41 #42 --- helper/test_data.go | 4 +- .../component-configuration/config_methods.go | 30 ++++- routes/component-configuration/config_test.go | 17 ++- routes/infrastructure-component/amqpclient.go | 38 ++++-- .../infrastructure-component/ic_endpoints.go | 75 +++++++---- routes/infrastructure-component/ic_methods.go | 43 ++++-- .../infrastructure-component/ic_validators.go | 125 +++++++++++++----- 7 files changed, 252 insertions(+), 80 deletions(-) diff --git a/helper/test_data.go b/helper/test_data.go index e8d3ced..9bad50e 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -103,7 +103,7 @@ var ICA = database.InfrastructureComponent{ Description: "This is a test description", //StateUpdateAt: time.Now().Format(time.RFC1123), StartParameterScheme: postgres.Jsonb{propertiesA}, - ManagedExternally: true, + ManagedExternally: false, } var ICB = database.InfrastructureComponent{ @@ -119,7 +119,7 @@ var ICB = database.InfrastructureComponent{ Description: "A signal generator for testing purposes", //StateUpdateAt: time.Now().Format(time.RFC1123), StartParameterScheme: postgres.Jsonb{propertiesB}, - ManagedExternally: false, + ManagedExternally: true, } // Scenarios diff --git a/routes/component-configuration/config_methods.go b/routes/component-configuration/config_methods.go index c48473f..f6a222d 100644 --- a/routes/component-configuration/config_methods.go +++ b/routes/component-configuration/config_methods.go @@ -121,9 +121,37 @@ func (m *ComponentConfiguration) delete() error { return err } + var ic infrastructure_component.InfrastructureComponent + err = ic.ByID(m.ICID) + if err != nil { + return err + } + // remove association between ComponentConfiguration and Scenario // ComponentConfiguration itself is not deleted from DB, it remains as "dangling" err = db.Model(&so).Association("ComponentConfigurations").Delete(m).Error + if err != nil { + return err + } - return err + // remove association between Infrastructure component and config + err = db.Model(&ic).Association("ComponentConfigurations").Delete(m).Error + if err != nil { + return err + } + + // delete component configuration + err = db.Delete(m).Error + if err != nil { + return err + } + + // if IC has state gone and there is no component configuration associated with it: delete IC + no_configs := db.Model(ic).Association("ComponentConfigurations").Count() + if no_configs == 0 && ic.State == "gone" { + err = db.Delete(ic).Error + return err + } + + return nil } diff --git a/routes/component-configuration/config_test.go b/routes/component-configuration/config_test.go index 4098551..579f13c 100644 --- a/routes/component-configuration/config_test.go +++ b/routes/component-configuration/config_test.go @@ -86,8 +86,11 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { StartParameterScheme: helper.ICA.StartParameterScheme, ManagedExternally: &helper.ICA.ManagedExternally, } - _, resp, _ := helper.TestEndpoint(router, token, + code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) + if code != 200 || err != nil { + fmt.Println("Adding IC returned code", code, err, resp) + } // Read newIC's ID from the response newICID, _ := helper.GetResponseID(resp) @@ -103,10 +106,13 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { Location: helper.ICB.Location, Description: helper.ICB.Description, StartParameterScheme: helper.ICB.StartParameterScheme, - ManagedExternally: &helper.ICB.ManagedExternally, + ManagedExternally: &helper.ICA.ManagedExternally, } - _, resp, _ = helper.TestEndpoint(router, token, + code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) + if code != 200 || err != nil { + fmt.Println("Adding IC returned code", code, err, resp) + } // authenticate as normal user token, _ = helper.AuthenticateForTest(router, @@ -118,8 +124,11 @@ func addScenarioAndIC() (scenarioID uint, ICID uint) { Running: helper.ScenarioA.Running, StartParameters: helper.ScenarioA.StartParameters, } - _, resp, _ = helper.TestEndpoint(router, token, + code, resp, err = helper.TestEndpoint(router, token, "/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) + if code != 200 || err != nil { + fmt.Println("Adding Scenario returned code", code, err, resp) + } // Read newScenario's ID from the response newScenarioID, _ := helper.GetResponseID(resp) diff --git a/routes/infrastructure-component/amqpclient.go b/routes/infrastructure-component/amqpclient.go index 7bf89dd..7cee748 100644 --- a/routes/infrastructure-component/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -41,12 +41,18 @@ type AMQPclient struct { } type Action struct { - Act string `json:"action"` - When float32 `json:"when"` - Parameters struct{} `json:"parameters"` - UUID *string `json:"uuid"` - //Model struct{} `json:"model"` - //Results struct{} `json:"results"` + Act string `json:"action"` + When int64 `json:"when"` + Properties struct { + UUID *string `json:"uuid"` + Name *string `json:"name"` + Category *string `json:"category"` + Type *string `json:"type"` + Location *string `json:"location"` + WS_url *string `json:"ws_url"` + API_url *string `json:"api_url"` + Description *string `json:"description"` + } `json:"properties"` } type ICUpdate struct { @@ -139,7 +145,7 @@ func ConnectAMQP(uri string) error { return nil } -func SendActionAMQP(action Action) error { +func sendActionAMQP(action Action) error { payload, err := json.Marshal(action) if err != nil { @@ -155,6 +161,20 @@ func SendActionAMQP(action Action) error { Body: payload, } + // set message headers + var headers map[string]interface{} + headers = make(map[string]interface{}) // empty map + if action.Properties.UUID != nil { + headers["uuid"] = *action.Properties.UUID + } + if action.Properties.Type != nil { + headers["type"] = *action.Properties.Type + } + if action.Properties.Category != nil { + headers["category"] = *action.Properties.Category + } + msg.Headers = headers + err = CheckConnection() if err != nil { return err @@ -175,9 +195,9 @@ func PingAMQP() error { var a Action a.Act = "ping" - *a.UUID = "" + *a.Properties.UUID = "" - err := SendActionAMQP(a) + err := sendActionAMQP(a) return err } diff --git a/routes/infrastructure-component/ic_endpoints.go b/routes/infrastructure-component/ic_endpoints.go index 0328ff7..a542a63 100644 --- a/routes/infrastructure-component/ic_endpoints.go +++ b/routes/infrastructure-component/ic_endpoints.go @@ -22,12 +22,12 @@ package infrastructure_component import ( + "fmt" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/gin-gonic/gin" "log" "net/http" - - "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" ) func RegisterICEndpoints(r *gin.RouterGroup) { @@ -96,22 +96,28 @@ func addIC(c *gin.Context) { } // Validate the request - if err = req.Validate(); err != nil { + if err = req.validate(); err != nil { helper.UnprocessableEntityError(c, err.Error()) return } - // TODO add case distinction here for externally managed IC - // Create the new IC from the request - newIC := req.CreateIC() - - // Save new IC to DB - err = newIC.Save() - if !helper.DBError(c, err) { - c.JSON(http.StatusOK, gin.H{"ic": newIC.InfrastructureComponent}) + newIC, err := req.createIC(false) + if err != nil { + helper.InternalServerError(c, "Unable to send create action: "+err.Error()) + return } + if !newIC.ManagedExternally { + // Save new IC to DB if not managed externally + err = newIC.Save() + + if helper.DBError(c, err) { + return + } + } + + c.JSON(http.StatusOK, gin.H{"ic": newIC.InfrastructureComponent}) } // updateIC godoc @@ -144,15 +150,20 @@ func updateIC(c *gin.Context) { } // Validate the request - if err = req.Validate(); err != nil { + if err = req.validate(); err != nil { helper.UnprocessableEntityError(c, err.Error()) return } - // TODO add case distinction here for externally managed IC - // Create the updatedIC from oldIC - updatedIC := req.UpdatedIC(oldIC) + updatedIC, err := req.updatedIC(oldIC, false) + if err != nil { + c.JSON(http.StatusForbidden, gin.H{ + "success": false, + "message": fmt.Sprintf("%v", err), + }) + return + } // Finally update the IC in the DB err = oldIC.Update(updatedIC) @@ -205,14 +216,16 @@ func deleteIC(c *gin.Context) { return } - // TODO add case distinction here for externally managed IC - // Delete the IC - err := s.delete() - if !helper.DBError(c, err) { - c.JSON(http.StatusOK, gin.H{"ic": s.InfrastructureComponent}) + err := s.delete(false) + if helper.DBError(c, err) { + return + } else if err != nil { + helper.InternalServerError(c, "Unable to send delete action: "+err.Error()) + return } + c.JSON(http.StatusOK, gin.H{"ic": s.InfrastructureComponent}) } // getConfigsOfIC godoc @@ -278,9 +291,25 @@ func sendActionToIC(c *gin.Context) { /*if action.When == 0 { action.When = float32(now.Unix()) }*/ - action.UUID = new(string) - *action.UUID = s.UUID - err = SendActionAMQP(action) + // make sure that the important properties are set correctly so that the message can be identified by the receiver + if action.Properties.UUID == nil { + action.Properties.UUID = new(string) + *action.Properties.UUID = s.UUID + } + if action.Properties.Type == nil { + action.Properties.Type = new(string) + *action.Properties.Type = s.Type + } + if action.Properties.Category == nil { + action.Properties.Category = new(string) + *action.Properties.Category = s.Category + } + if action.Properties.Name == nil { + action.Properties.Name = new(string) + *action.Properties.Name = s.Name + } + + err = sendActionAMQP(action) if err != nil { helper.InternalServerError(c, "Unable to send actions to IC: "+err.Error()) return diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index 674fd8f..13fda99 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -23,8 +23,9 @@ package infrastructure_component import ( "fmt" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "log" + "time" ) type InfrastructureComponent struct { @@ -57,7 +58,18 @@ func (s *InfrastructureComponent) Update(updatedIC InfrastructureComponent) erro return err } -func (s *InfrastructureComponent) delete() error { +func (s *InfrastructureComponent) delete(receivedViaAMQP bool) error { + if s.ManagedExternally && !receivedViaAMQP { + var action Action + action.Act = "delete" + action.When = time.Now().Unix() + action.Properties.UUID = new(string) + *action.Properties.UUID = s.UUID + + err := sendActionAMQP(action) + return err + } + db := database.GetDB() no_configs := db.Model(s).Association("ComponentConfigurations").Count() @@ -118,13 +130,16 @@ func createNewICviaAMQP(payload ICUpdate) error { newICReq.InfrastructureComponent.ManagedExternally = newTrue() // Validate the new IC - err := newICReq.Validate() + err := newICReq.validate() if err != nil { return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) } // Create the new IC - newIC := newICReq.CreateIC() + newIC, err := newICReq.createIC(true) + if err != nil { + return fmt.Errorf("AMQP: Creating new IC failed: %v", err) + } // save IC err = newIC.Save() @@ -139,8 +154,17 @@ func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { var updatedICReq UpdateICRequest if payload.State != nil { updatedICReq.InfrastructureComponent.State = *payload.State - // TODO check if state is "gone" and attempt to remove IC from DB if it still exists - // TODO if state is different from "gone", continue to update the IC + + if *payload.State == "gone" { + // remove IC from DB + err := s.delete(true) + if err != nil { + // if component could not be deleted there are still configurations using it in the DB + // continue with the update to save the new state of the component and get back to the deletion later + log.Println("Could not delete IC because there is a config using it, deletion postponed") + } + + } } if payload.Properties.Type != nil { updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type @@ -170,13 +194,16 @@ func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() // Validate the updated IC - err := updatedICReq.Validate() + err := updatedICReq.validate() if err != nil { return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) } // Create the updated IC from old IC - updatedIC := updatedICReq.UpdatedIC(*s) + updatedIC, err := updatedICReq.updatedIC(*s, true) + if err != nil { + return fmt.Errorf("AMQP: Unable to update IC %v : %v", s.Name, err) + } // Finally update the IC in the DB err = s.Update(updatedIC) diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index da3484b..af28299 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -23,6 +23,8 @@ package infrastructure_component import ( "encoding/json" + "fmt" + "github.com/google/uuid" "github.com/jinzhu/gorm/dialects/postgres" "github.com/nsf/jsondiff" "gopkg.in/go-playground/validator.v9" @@ -32,7 +34,7 @@ import ( var validate *validator.Validate type validNewIC struct { - UUID string `form:"UUID" validate:"required"` + UUID string `form:"UUID" validate:"omitempty"` WebsocketURL string `form:"WebsocketURL" validate:"omitempty"` APIURL string `form:"APIURL" validate:"omitempty"` Type string `form:"Type" validate:"required"` @@ -67,46 +69,103 @@ type UpdateICRequest struct { InfrastructureComponent validUpdatedIC `json:"ic"` } -func (r *AddICRequest) Validate() error { +func (r *AddICRequest) validate() error { validate = validator.New() errs := validate.Struct(r) - return errs -} - -func (r *UpdateICRequest) Validate() error { - validate = validator.New() - errs := validate.Struct(r) - return errs -} - -func (r *AddICRequest) CreateIC() InfrastructureComponent { - var s InfrastructureComponent - - s.UUID = r.InfrastructureComponent.UUID - s.WebsocketURL = r.InfrastructureComponent.WebsocketURL - s.APIURL = r.InfrastructureComponent.APIURL - s.Type = r.InfrastructureComponent.Type - s.Name = r.InfrastructureComponent.Name - s.Category = r.InfrastructureComponent.Category - s.Location = r.InfrastructureComponent.Location - s.Description = r.InfrastructureComponent.Description - s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme - s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally - if r.InfrastructureComponent.State != "" { - s.State = r.InfrastructureComponent.State - } else { - s.State = "unknown" + if errs != nil { + return errs } - // set last update to creation time of IC - s.StateUpdateAt = time.Now().Format(time.RFC1123) - return s + // check if uuid is valid + _, errs = uuid.Parse(r.InfrastructureComponent.UUID) + return errs } -func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) InfrastructureComponent { +func (r *UpdateICRequest) validate() error { + validate = validator.New() + errs := validate.Struct(r) + return errs +} + +func (r *AddICRequest) createIC(receivedViaAMQP bool) (InfrastructureComponent, error) { + var s InfrastructureComponent + var err error + err = nil + + // case distinction for externally managed IC + if *r.InfrastructureComponent.ManagedExternally && !receivedViaAMQP { + var action Action + action.Act = "create" + action.When = time.Now().Unix() + action.Properties.Type = new(string) + action.Properties.Name = new(string) + action.Properties.Category = new(string) + + *action.Properties.Type = r.InfrastructureComponent.Type + *action.Properties.Name = r.InfrastructureComponent.Name + *action.Properties.Category = r.InfrastructureComponent.Category + + // set optional properties + if r.InfrastructureComponent.Description != "" { + action.Properties.Description = new(string) + *action.Properties.Description = r.InfrastructureComponent.Description + } + + if r.InfrastructureComponent.Location != "" { + action.Properties.Location = new(string) + *action.Properties.Location = r.InfrastructureComponent.Location + } + + if r.InfrastructureComponent.APIURL != "" { + action.Properties.API_url = new(string) + *action.Properties.API_url = r.InfrastructureComponent.APIURL + } + + if r.InfrastructureComponent.WebsocketURL != "" { + action.Properties.WS_url = new(string) + *action.Properties.WS_url = r.InfrastructureComponent.WebsocketURL + } + + if r.InfrastructureComponent.UUID != "" { + action.Properties.UUID = new(string) + *action.Properties.UUID = r.InfrastructureComponent.UUID + } + + err = sendActionAMQP(action) + + // s remains empty + + } else { + s.UUID = r.InfrastructureComponent.UUID + s.WebsocketURL = r.InfrastructureComponent.WebsocketURL + s.APIURL = r.InfrastructureComponent.APIURL + s.Type = r.InfrastructureComponent.Type + s.Name = r.InfrastructureComponent.Name + s.Category = r.InfrastructureComponent.Category + s.Location = r.InfrastructureComponent.Location + s.Description = r.InfrastructureComponent.Description + s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme + s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally + if r.InfrastructureComponent.State != "" { + s.State = r.InfrastructureComponent.State + } else { + s.State = "unknown" + } + // set last update to creation time of IC + s.StateUpdateAt = time.Now().Format(time.RFC1123) + } + + return s, err +} + +func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent, receivedViaAMQP bool) (InfrastructureComponent, error) { // Use the old InfrastructureComponent as a basis for the updated InfrastructureComponent `s` s := oldIC + if s.ManagedExternally && !receivedViaAMQP { + // externally managed IC cannot be updated via API, only via AMQP + return s, fmt.Errorf("cannot update externally managed IC %v", s.Name) + } if r.InfrastructureComponent.UUID != "" { s.UUID = r.InfrastructureComponent.UUID } @@ -161,5 +220,5 @@ func (r *UpdateICRequest) UpdatedIC(oldIC InfrastructureComponent) Infrastructur s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme } - return s + return s, nil } From cf8775b39d7e85f0249fb451ae9b8ee496c39551 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 21 Oct 2020 17:27:45 +0200 Subject: [PATCH 06/18] connect to AMQP broker in IC tests --- routes/infrastructure-component/ic_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 334746e..6bcba3a 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -26,6 +26,7 @@ import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/jinzhu/gorm/dialects/postgres" "github.com/stretchr/testify/assert" + "log" "os" "testing" @@ -70,6 +71,17 @@ func TestMain(m *testing.M) { user.RegisterAuthenticate(api.Group("/authenticate")) api.Use(user.Authentication(true)) RegisterICEndpoints(api.Group("/ic")) + RegisterAMQPEndpoint(api.Group("/ic")) + + // connect AMQP client (make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set via command line parameters) + host, err := configuration.GolbalConfig.String("amqp.host") + user, err := configuration.GolbalConfig.String("amqp.user") + pass, err := configuration.GolbalConfig.String("amqp.pass") + + amqpURI := "amqp://" + user + ":" + pass + "@" + host + log.Println("AMQP URI is", amqpURI) + + err = ConnectAMQP(amqpURI) os.Exit(m.Run()) } From 03341dbe424f44212f3958979b6d069cfd6c06fc Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 26 Oct 2020 12:03:02 +0100 Subject: [PATCH 07/18] check if state field exists before processing a message --- routes/infrastructure-component/amqpclient.go | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/routes/infrastructure-component/amqpclient.go b/routes/infrastructure-component/amqpclient.go index 7cee748..19cd17e 100644 --- a/routes/infrastructure-component/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -261,26 +261,27 @@ func processMessage(message amqp.Delivery) error { return fmt.Errorf("AMQP: Could not unmarshal message to JSON: %v err: %v", string(message.Body), err) } - ICUUID := payload.Properties.UUID - _, err = uuid.Parse(ICUUID) + if payload.State != nil { + // if a message contains a "state" field, it is an update for an IC + ICUUID := payload.Properties.UUID + _, err = uuid.Parse(ICUUID) - if err != nil { - return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) + if err != nil { + return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) + } + var sToBeUpdated InfrastructureComponent + err = sToBeUpdated.ByUUID(ICUUID) + + if err == gorm.ErrRecordNotFound { + // create new record + err = createNewICviaAMQP(payload) + } else if err != nil { + // database error + err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) + } else { + // update record based on payload + err = sToBeUpdated.updateICviaAMQP(payload) + } } - - var sToBeUpdated InfrastructureComponent - err = sToBeUpdated.ByUUID(ICUUID) - - if err == gorm.ErrRecordNotFound { - // create new record - err = createNewICviaAMQP(payload) - } else if err != nil { - // database error - err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) - } else { - // update record based on payload - err = sToBeUpdated.updateICviaAMQP(payload) - } - return err } From 9543f041d30861faabc5def285c8cc8c0be54693 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 26 Oct 2020 16:58:20 +0100 Subject: [PATCH 08/18] connect to AMQP broker in register endpoint tests --- routes/register_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/routes/register_test.go b/routes/register_test.go index e8a5445..7523b17 100644 --- a/routes/register_test.go +++ b/routes/register_test.go @@ -25,6 +25,7 @@ package routes import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + infrastructure_component "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "os" @@ -47,6 +48,15 @@ func TestMain(m *testing.M) { router = gin.Default() + // connect AMQP client (make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set via command line parameters) + host, err := configuration.GolbalConfig.String("amqp.host") + user, err := configuration.GolbalConfig.String("amqp.user") + pass, err := configuration.GolbalConfig.String("amqp.pass") + + amqpURI := "amqp://" + user + ":" + pass + "@" + host + + err = infrastructure_component.ConnectAMQP(amqpURI) + os.Exit(m.Run()) } From 1540029aaeba18803332c701072aea556aa2d62c Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 30 Oct 2020 14:27:43 +0100 Subject: [PATCH 09/18] switch content of ICA and ICB in test data (exept for managedexternally value) --- helper/test_data.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/helper/test_data.go b/helper/test_data.go index 9bad50e..3d53c3f 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -92,21 +92,6 @@ var propertiesA = json.RawMessage(`{"prop1" : "a nice prop"}`) var propertiesB = json.RawMessage(`{"prop1" : "not so nice"}`) var ICA = database.InfrastructureComponent{ - UUID: "4854af30-325f-44a5-ad59-b67b2597de68", - WebsocketURL: "xxx.yyy.zzz.aaa", - Type: "DPsim", - Category: "Simulator", - Name: "Test DPsim Simulator", - Uptime: 0, - State: "running", - Location: "ACS Laboratory", - Description: "This is a test description", - //StateUpdateAt: time.Now().Format(time.RFC1123), - StartParameterScheme: postgres.Jsonb{propertiesA}, - ManagedExternally: false, -} - -var ICB = database.InfrastructureComponent{ UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", WebsocketURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig", APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api", @@ -118,6 +103,21 @@ var ICB = database.InfrastructureComponent{ Location: "k8s", Description: "A signal generator for testing purposes", //StateUpdateAt: time.Now().Format(time.RFC1123), + StartParameterScheme: postgres.Jsonb{propertiesA}, + ManagedExternally: false, +} + +var ICB = database.InfrastructureComponent{ + UUID: "4854af30-325f-44a5-ad59-b67b2597de68", + WebsocketURL: "xxx.yyy.zzz.aaa", + Type: "DPsim", + Category: "Simulator", + Name: "Test DPsim Simulator", + Uptime: 0, + State: "running", + Location: "ACS Laboratory", + Description: "This is a test description", + //StateUpdateAt: time.Now().Format(time.RFC1123), StartParameterScheme: postgres.Jsonb{propertiesB}, ManagedExternally: true, } From fb757b1172f78ce6e3a6aefa5486619a6d2afb90 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 30 Oct 2020 14:28:46 +0100 Subject: [PATCH 10/18] Add test ICB (ManagedExternally=true) only if AMQP is active, test mode working again if AMQP broker is not available --- routes/register.go | 12 +++++++----- routes/register_test.go | 3 ++- start.go | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/routes/register.go b/routes/register.go index 4cc5983..d801de5 100644 --- a/routes/register.go +++ b/routes/register.go @@ -74,7 +74,7 @@ func RegisterEndpoints(router *gin.Engine, api *gin.RouterGroup) { } // Uses API endpoints to add test data to the backend; All endpoints have to be registered before invoking this function. -func AddTestData(basePath string, router *gin.Engine) (*bytes.Buffer, error) { +func AddTestData(basePath string, router *gin.Engine, amqphost string) (*bytes.Buffer, error) { database.MigrateModels() // Create entries of each model (data defined in test_data.go) @@ -109,9 +109,11 @@ func AddTestData(basePath string, router *gin.Engine) (*bytes.Buffer, error) { if code != http.StatusOK { return resp, fmt.Errorf("error adding IC A") } - code, resp, err = helper.TestEndpoint(router, token, basePath+"/ic", "POST", helper.KeyModels{"ic": helper.ICB}) - if code != http.StatusOK { - return resp, fmt.Errorf("error adding IC B") + if amqphost != "" { + code, resp, err = helper.TestEndpoint(router, token, basePath+"/ic", "POST", helper.KeyModels{"ic": helper.ICB}) + if code != http.StatusOK { + return resp, fmt.Errorf("error adding IC B") + } } // add scenarios @@ -147,7 +149,7 @@ func AddTestData(basePath string, router *gin.Engine) (*bytes.Buffer, error) { configB := helper.ConfigB configA.ScenarioID = 1 configB.ScenarioID = 1 - configA.ICID = 2 + configA.ICID = 1 configB.ICID = 1 code, resp, err = helper.TestEndpoint(router, token, basePath+"/configs", "POST", helper.KeyModels{"config": configA}) if code != http.StatusOK { diff --git a/routes/register_test.go b/routes/register_test.go index 7523b17..85418c3 100644 --- a/routes/register_test.go +++ b/routes/register_test.go @@ -33,6 +33,7 @@ import ( ) var router *gin.Engine +var amqpURI string func TestMain(m *testing.M) { err := configuration.InitConfig() @@ -69,6 +70,6 @@ func TestRegisterEndpoints(t *testing.T) { } func TestAddTestData(t *testing.T) { - resp, err := AddTestData("/api", router) + resp, err := AddTestData("/api", router, amqpURI) assert.NoError(t, err, "Response body: %v", resp) } diff --git a/start.go b/start.go index e222f90..57c3fc7 100644 --- a/start.go +++ b/start.go @@ -33,13 +33,13 @@ import ( "log" ) -func addData(router *gin.Engine, mode string, basePath string) error { +func addData(router *gin.Engine, mode string, basePath string, amqphost string) error { if mode == "test" { // test mode: drop all tables and add test data to DB database.DropTables() log.Println("Database tables dropped, using API to add test data") - resp, err := routes.AddTestData(basePath, router) + resp, err := routes.AddTestData(basePath, router, amqphost) if err != nil { fmt.Println("error: testdata could not be added to DB:", err.Error(), "Response body: ", resp) return err @@ -95,7 +95,7 @@ func main() { apidocs.SwaggerInfo.BasePath = basePath // add data to DB (if any) - err = addData(r, mode, basePath) + err = addData(r, mode, basePath, amqphost) if err != nil { panic(err) } From e2bebbc6763875bc89ebc2a77c5edb599fc8920c Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 30 Oct 2020 14:49:56 +0100 Subject: [PATCH 11/18] Do not create ICs with state gone, add some logging info for AMQP-related actions #31 #41 --- routes/infrastructure-component/ic_methods.go | 10 ++++++++-- routes/infrastructure-component/ic_validators.go | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index 13fda99..c6a55ff 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -110,7 +110,11 @@ func createNewICviaAMQP(payload ICUpdate) error { } else { newICReq.InfrastructureComponent.State = "unknown" } - // TODO check if state is "gone" and abort creation of IC in this case + if newICReq.InfrastructureComponent.State == "gone" { + // Check if state is "gone" and abort creation of IC in this case + log.Println("########## AMQP: Aborting creation of IC with state gone") + return nil + } if payload.Properties.WS_url != nil { newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url @@ -151,17 +155,19 @@ func createNewICviaAMQP(payload ICUpdate) error { } func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { + var updatedICReq UpdateICRequest if payload.State != nil { updatedICReq.InfrastructureComponent.State = *payload.State if *payload.State == "gone" { // remove IC from DB + log.Println("########## AMQP: Deleting IC with state gone") err := s.delete(true) if err != nil { // if component could not be deleted there are still configurations using it in the DB // continue with the update to save the new state of the component and get back to the deletion later - log.Println("Could not delete IC because there is a config using it, deletion postponed") + log.Println("########## AMQP: Deletion of IC postponed (config(s) associated to it)") } } diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index af28299..46a9eff 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -28,6 +28,7 @@ import ( "github.com/jinzhu/gorm/dialects/postgres" "github.com/nsf/jsondiff" "gopkg.in/go-playground/validator.v9" + "log" "time" ) @@ -131,6 +132,7 @@ func (r *AddICRequest) createIC(receivedViaAMQP bool) (InfrastructureComponent, *action.Properties.UUID = r.InfrastructureComponent.UUID } + log.Println("########## AMQP: Sending request to create new IC") err = sendActionAMQP(action) // s remains empty From d0c6cca76c8d01e15d73bc297095adacb75545e3 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Fri, 30 Oct 2020 15:16:53 +0100 Subject: [PATCH 12/18] Adapt layout of AMQP update to VILLAScontroller protocol, add uptime in IC creation and updates via AMQP; need to check data type of uptime #31 --- database/models.go | 2 +- helper/test_data.go | 4 +- routes/infrastructure-component/amqpclient.go | 27 +++---- routes/infrastructure-component/ic_methods.go | 76 ++++++++++--------- .../infrastructure-component/ic_validators.go | 3 + 5 files changed, 61 insertions(+), 51 deletions(-) diff --git a/database/models.go b/database/models.go index 8ffa86d..787a7c3 100644 --- a/database/models.go +++ b/database/models.go @@ -129,7 +129,7 @@ type InfrastructureComponent struct { // Type of IC (RTDS, VILLASnode, RTDS, etc.) Type string `json:"type" gorm:"default:''"` // Uptime of the IC - Uptime int `json:"uptime" gorm:"default:0"` + Uptime float64 `json:"uptime" gorm:"default:0"` // State of the IC State string `json:"state" gorm:"default:''"` // Time of last state update diff --git a/helper/test_data.go b/helper/test_data.go index 3d53c3f..5dbcde4 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -98,7 +98,7 @@ var ICA = database.InfrastructureComponent{ Type: "VILLASnode Signal Generator", Category: "Signal Generator", Name: "ACS Demo Signals", - Uptime: 0, + Uptime: -1.0, State: "idle", Location: "k8s", Description: "A signal generator for testing purposes", @@ -113,7 +113,7 @@ var ICB = database.InfrastructureComponent{ Type: "DPsim", Category: "Simulator", Name: "Test DPsim Simulator", - Uptime: 0, + Uptime: -1.0, State: "running", Location: "ACS Laboratory", Description: "This is a test description", diff --git a/routes/infrastructure-component/amqpclient.go b/routes/infrastructure-component/amqpclient.go index 19cd17e..1d064b3 100644 --- a/routes/infrastructure-component/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -56,17 +56,18 @@ type Action struct { } type ICUpdate struct { - State *string `json:"state"` - Properties struct { - UUID string `json:"uuid"` - Name *string `json:"name"` - Category *string `json:"category"` - Type *string `json:"type"` - Location *string `json:"location"` - WS_url *string `json:"ws_url"` - API_url *string `json:"api_url"` - Description *string `json:"description"` - } `json:"properties"` + Status *struct { + UUID string `json:"uuid"` + State *string `json:"state"` + Name *string `json:"name"` + Category *string `json:"category"` + Type *string `json:"type"` + Location *string `json:"location"` + WS_url *string `json:"ws_url"` + API_url *string `json:"api_url"` + Description *string `json:"description"` + Uptime *float64 `json:"uptime"` // TODO check if data type of uptime is float64 or int + } `json:"status"` // TODO add JSON start parameter scheme } @@ -261,9 +262,9 @@ func processMessage(message amqp.Delivery) error { return fmt.Errorf("AMQP: Could not unmarshal message to JSON: %v err: %v", string(message.Body), err) } - if payload.State != nil { + if payload.Status != nil { // if a message contains a "state" field, it is an update for an IC - ICUUID := payload.Properties.UUID + ICUUID := payload.Status.UUID _, err = uuid.Parse(ICUUID) if err != nil { diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index c6a55ff..3a757b2 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -93,20 +93,20 @@ func (s *InfrastructureComponent) getConfigs() ([]database.ComponentConfiguratio func createNewICviaAMQP(payload ICUpdate) error { var newICReq AddICRequest - newICReq.InfrastructureComponent.UUID = payload.Properties.UUID - if payload.Properties.Name == nil || - payload.Properties.Category == nil || - payload.Properties.Type == nil { + newICReq.InfrastructureComponent.UUID = payload.Status.UUID + if payload.Status.Name == nil || + payload.Status.Category == nil || + payload.Status.Type == nil { // cannot create new IC because required information (name, type, and/or category missing) return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") } - newICReq.InfrastructureComponent.Name = *payload.Properties.Name - newICReq.InfrastructureComponent.Category = *payload.Properties.Category - newICReq.InfrastructureComponent.Type = *payload.Properties.Type + newICReq.InfrastructureComponent.Name = *payload.Status.Name + newICReq.InfrastructureComponent.Category = *payload.Status.Category + newICReq.InfrastructureComponent.Type = *payload.Status.Type // add optional params - if payload.State != nil { - newICReq.InfrastructureComponent.State = *payload.State + if payload.Status.State != nil { + newICReq.InfrastructureComponent.State = *payload.Status.State } else { newICReq.InfrastructureComponent.State = "unknown" } @@ -116,17 +116,20 @@ func createNewICviaAMQP(payload ICUpdate) error { return nil } - if payload.Properties.WS_url != nil { - newICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + if payload.Status.WS_url != nil { + newICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url } - if payload.Properties.API_url != nil { - newICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + if payload.Status.API_url != nil { + newICReq.InfrastructureComponent.APIURL = *payload.Status.API_url } - if payload.Properties.Location != nil { - newICReq.InfrastructureComponent.Location = *payload.Properties.Location + if payload.Status.Location != nil { + newICReq.InfrastructureComponent.Location = *payload.Status.Location } - if payload.Properties.Description != nil { - newICReq.InfrastructureComponent.Description = *payload.Properties.Description + if payload.Status.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Status.Description + } + if payload.Status.Uptime != nil { + newICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime } // TODO add JSON start parameter scheme @@ -157,10 +160,10 @@ func createNewICviaAMQP(payload ICUpdate) error { func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { var updatedICReq UpdateICRequest - if payload.State != nil { - updatedICReq.InfrastructureComponent.State = *payload.State + if payload.Status.State != nil { + updatedICReq.InfrastructureComponent.State = *payload.Status.State - if *payload.State == "gone" { + if *payload.Status.State == "gone" { // remove IC from DB log.Println("########## AMQP: Deleting IC with state gone") err := s.delete(true) @@ -172,27 +175,30 @@ func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { } } - if payload.Properties.Type != nil { - updatedICReq.InfrastructureComponent.Type = *payload.Properties.Type + if payload.Status.Type != nil { + updatedICReq.InfrastructureComponent.Type = *payload.Status.Type } - if payload.Properties.Category != nil { - updatedICReq.InfrastructureComponent.Category = *payload.Properties.Category + if payload.Status.Category != nil { + updatedICReq.InfrastructureComponent.Category = *payload.Status.Category } - if payload.Properties.Name != nil { - updatedICReq.InfrastructureComponent.Name = *payload.Properties.Name + if payload.Status.Name != nil { + updatedICReq.InfrastructureComponent.Name = *payload.Status.Name } - if payload.Properties.WS_url != nil { - updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Properties.WS_url + if payload.Status.WS_url != nil { + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url } - if payload.Properties.API_url != nil { - updatedICReq.InfrastructureComponent.APIURL = *payload.Properties.API_url + if payload.Status.API_url != nil { + updatedICReq.InfrastructureComponent.APIURL = *payload.Status.API_url } - if payload.Properties.Location != nil { - //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Properties.Location + `"}`)} - updatedICReq.InfrastructureComponent.Location = *payload.Properties.Location + if payload.Status.Location != nil { + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Status.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Status.Location } - if payload.Properties.Description != nil { - updatedICReq.InfrastructureComponent.Description = *payload.Properties.Description + if payload.Status.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Status.Description + } + if payload.Status.Uptime != nil { + updatedICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime } // TODO add JSON start parameter scheme diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index 46a9eff..9f6bb05 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -46,6 +46,7 @@ type validNewIC struct { Description string `form:"Description" validate:"omitempty"` StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` ManagedExternally *bool `form:"ManagedExternally" validate:"required"` + Uptime float64 `form:"Uptime" validate:"omitempty"` } type validUpdatedIC struct { @@ -60,6 +61,7 @@ type validUpdatedIC struct { Description string `form:"Description" validate:"omitempty"` StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` ManagedExternally *bool `form:"ManagedExternally" validate:"required"` + Uptime float64 `form:"Uptime" validate:"omitempty"` } type AddICRequest struct { @@ -148,6 +150,7 @@ func (r *AddICRequest) createIC(receivedViaAMQP bool) (InfrastructureComponent, s.Description = r.InfrastructureComponent.Description s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally + s.Uptime = -1.0 // no uptime available if r.InfrastructureComponent.State != "" { s.State = r.InfrastructureComponent.State } else { From 2e649468699897a0d6d81c9eeb60c7f5308f8511 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Mon, 2 Nov 2020 16:55:22 +0100 Subject: [PATCH 13/18] Work on testing for AMQP --- routes/infrastructure-component/amqpclient.go | 26 +-- routes/infrastructure-component/ic_test.go | 190 ++++++++++++++++++ 2 files changed, 204 insertions(+), 12 deletions(-) diff --git a/routes/infrastructure-component/amqpclient.go b/routes/infrastructure-component/amqpclient.go index 1d064b3..13e7f6f 100644 --- a/routes/infrastructure-component/amqpclient.go +++ b/routes/infrastructure-component/amqpclient.go @@ -55,19 +55,21 @@ type Action struct { } `json:"properties"` } +type ICStatus struct { + UUID string `json:"uuid"` + State *string `json:"state"` + Name *string `json:"name"` + Category *string `json:"category"` + Type *string `json:"type"` + Location *string `json:"location"` + WS_url *string `json:"ws_url"` + API_url *string `json:"api_url"` + Description *string `json:"description"` + Uptime *float64 `json:"uptime"` // TODO check if data type of uptime is float64 or int +} + type ICUpdate struct { - Status *struct { - UUID string `json:"uuid"` - State *string `json:"state"` - Name *string `json:"name"` - Category *string `json:"category"` - Type *string `json:"type"` - Location *string `json:"location"` - WS_url *string `json:"ws_url"` - API_url *string `json:"api_url"` - Description *string `json:"description"` - Uptime *float64 `json:"uptime"` // TODO check if data type of uptime is float64 or int - } `json:"status"` + Status *ICStatus `json:"status"` // TODO add JSON start parameter scheme } diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 6bcba3a..a9f1489 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -22,13 +22,16 @@ package infrastructure_component import ( + "encoding/json" "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/jinzhu/gorm/dialects/postgres" + "github.com/streadway/amqp" "github.com/stretchr/testify/assert" "log" "os" "testing" + "time" "github.com/gin-gonic/gin" @@ -53,6 +56,21 @@ type ICRequest struct { ManagedExternally *bool `json:"managedexternally,omitempty"` } +type ICAction struct { + Act string `json:"action,omitempty"` + When int64 `json:"when,omitempty"` + Properties struct { + UUID *string `json:"uuid,omitempty"` + Name *string `json:"name,omitempty"` + Category *string `json:"category,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + WS_url *string `json:"ws_url,omitempty"` + API_url *string `json:"api_url,omitempty"` + Description *string `json:"description,omitempty"` + } `json:"properties,omitempty"` +} + func TestMain(m *testing.M) { err := configuration.InitConfig() if err != nil { @@ -82,6 +100,9 @@ func TestMain(m *testing.M) { log.Println("AMQP URI is", amqpURI) err = ConnectAMQP(amqpURI) + if err != nil { + panic(m) + } os.Exit(m.Run()) } @@ -539,3 +560,172 @@ func TestGetConfigsOfIC(t *testing.T) { assert.NoError(t, err) assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) } + +func TestSendActionToIC(t *testing.T) { + database.DropTables() + database.MigrateModels() + assert.NoError(t, helper.DBAddAdminAndUserAndGuest()) + + // authenticate as admin + token, err := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.AdminCredentials) + assert.NoError(t, err) + + // test POST ic/ $newICA + newICA := ICRequest{ + UUID: helper.ICA.UUID, + WebsocketURL: helper.ICA.WebsocketURL, + Type: helper.ICA.Type, + Name: helper.ICA.Name, + Category: helper.ICA.Category, + State: helper.ICA.State, + Location: helper.ICA.Location, + Description: helper.ICA.Description, + StartParameterScheme: helper.ICA.StartParameterScheme, + ManagedExternally: &helper.ICA.ManagedExternally, + } + code, resp, err := helper.TestEndpoint(router, token, + "/api/ic", "POST", helper.KeyModels{"ic": newICA}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Read newIC's ID from the response + newICID, err := helper.GetResponseID(resp) + assert.NoError(t, err) + + // create action to be sent to IC + action1 := ICAction{ + Act: "start", + When: time.Now().Unix(), + } + action1.Properties.UUID = new(string) + *action1.Properties.UUID = newICA.UUID + actions := [1]ICAction{action1} + + // Send action to IC + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/ic/%v/action", newICID), "POST", actions) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Send malformed actions array to IC (should yield bad request) + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/ic/%v/action", newICID), "POST", action1) + assert.NoError(t, err) + assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) +} + +func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { + + database.DropTables() + database.MigrateModels() + assert.NoError(t, helper.DBAddAdminAndUserAndGuest()) + + // fake an IC update message + var update ICUpdate + update.Status = new(ICStatus) + update.Status.UUID = helper.ICA.UUID + update.Status.State = new(string) + *update.Status.State = "idle" + update.Status.Name = new(string) + *update.Status.Name = helper.ICA.Name + update.Status.Category = new(string) + *update.Status.Category = helper.ICA.Category + update.Status.Type = new(string) + *update.Status.Type = helper.ICA.Type + + payload, err := json.Marshal(update) + assert.NoError(t, err) + + msg := amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = CheckConnection() + assert.NoError(t, err) + + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + time.Sleep(4 * time.Second) + // authenticate as admin + token, err := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.AdminCredentials) + assert.NoError(t, err) + + // get the length of the GET all ICs response for user + number, err := helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 1, number) + + // modify status update + *update.Status.Name = "This is the new name" + payload, err = json.Marshal(update) + assert.NoError(t, err) + + msg = amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + time.Sleep(4 * time.Second) + // get the length of the GET all ICs response for user + number, err = helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 1, number) + + // modify status update to state "gone" + *update.Status.State = "gone" + payload, err = json.Marshal(update) + assert.NoError(t, err) + + msg = amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + time.Sleep(4 * time.Second) + // get the length of the GET all ICs response for user + number, err = helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 0, number) + +} + +func TestCreateDeleteViaAMQPSend(t *testing.T) { + +} From cc51913163811de8d42c76be05b442b0f6a2c791 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 3 Nov 2020 15:30:24 +0100 Subject: [PATCH 14/18] Add more test for AMQP functions, improve code structure --- .../component-configuration/config_methods.go | 25 +- .../{amqpclient.go => ic_amqpclient.go} | 173 +++++++- .../infrastructure-component/ic_endpoints.go | 23 +- routes/infrastructure-component/ic_methods.go | 150 +------ .../infrastructure-component/ic_middleware.go | 2 +- routes/infrastructure-component/ic_test.go | 405 +++++++++++++++--- .../infrastructure-component/ic_validators.go | 57 +-- 7 files changed, 565 insertions(+), 270 deletions(-) rename routes/infrastructure-component/{amqpclient.go => ic_amqpclient.go} (57%) diff --git a/routes/component-configuration/config_methods.go b/routes/component-configuration/config_methods.go index f6a222d..767b76c 100644 --- a/routes/component-configuration/config_methods.go +++ b/routes/component-configuration/config_methods.go @@ -23,7 +23,6 @@ package component_configuration import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" ) @@ -61,8 +60,11 @@ func (m *ComponentConfiguration) addToScenario() error { } // associate IC with component configuration - var ic infrastructure_component.InfrastructureComponent - err = ic.ByID(m.ICID) + var ic database.InfrastructureComponent + err = db.Find(&ic, m.ICID).Error + if err != nil { + return err + } err = db.Model(&ic).Association("ComponentConfigurations").Append(m).Error if err != nil { return err @@ -80,23 +82,24 @@ func (m *ComponentConfiguration) Update(modifiedConfig ComponentConfiguration) e // check if IC has been updated if m.ICID != modifiedConfig.ICID { // update IC - var s infrastructure_component.InfrastructureComponent - var s_old infrastructure_component.InfrastructureComponent - err := s.ByID(modifiedConfig.ICID) + var ic database.InfrastructureComponent + var ic_old database.InfrastructureComponent + err := db.Find(&ic, modifiedConfig.ICID).Error if err != nil { return err } - err = s_old.ByID(m.ICID) + err = db.Find(&ic_old, m.ICID).Error if err != nil { return err } + // remove component configuration from old IC - err = db.Model(&s_old).Association("ComponentConfigurations").Delete(m).Error + err = db.Model(&ic_old).Association("ComponentConfigurations").Delete(m).Error if err != nil { return err } // add component configuration to new IC - err = db.Model(&s).Association("ComponentConfigurations").Append(m).Error + err = db.Model(&ic).Association("ComponentConfigurations").Append(m).Error if err != nil { return err } @@ -121,8 +124,8 @@ func (m *ComponentConfiguration) delete() error { return err } - var ic infrastructure_component.InfrastructureComponent - err = ic.ByID(m.ICID) + var ic database.InfrastructureComponent + err = db.Find(&ic, m.ICID).Error if err != nil { return err } diff --git a/routes/infrastructure-component/amqpclient.go b/routes/infrastructure-component/ic_amqpclient.go similarity index 57% rename from routes/infrastructure-component/amqpclient.go rename to routes/infrastructure-component/ic_amqpclient.go index 13e7f6f..1adf68e 100644 --- a/routes/infrastructure-component/amqpclient.go +++ b/routes/infrastructure-component/ic_amqpclient.go @@ -183,7 +183,7 @@ func sendActionAMQP(action Action) error { return err } - log.Println("AMQP: Sending message", string(msg.Body)) + //log.Println("AMQP: Sending message", string(msg.Body)) err = client.channel.Publish(VILLAS_EXCHANGE, "", false, @@ -193,16 +193,16 @@ func sendActionAMQP(action Action) error { } -func PingAMQP() error { - log.Println("AMQP: sending ping command to all ICs") - - var a Action - a.Act = "ping" - *a.Properties.UUID = "" - - err := sendActionAMQP(a) - return err -} +//func PingAMQP() error { +// log.Println("AMQP: sending ping command to all ICs") +// +// var a Action +// a.Act = "ping" +// *a.Properties.UUID = "" +// +// err := sendActionAMQP(a) +// return err +//} func CheckConnection() error { @@ -256,8 +256,6 @@ func StartAMQP(AMQPurl string, api *gin.RouterGroup) error { func processMessage(message amqp.Delivery) error { - log.Println("Processing AMQP message: ", string(message.Body)) - var payload ICUpdate err := json.Unmarshal(message.Body, &payload) if err != nil { @@ -265,6 +263,7 @@ func processMessage(message amqp.Delivery) error { } if payload.Status != nil { + //log.Println("Processing AMQP message: ", string(message.Body)) // if a message contains a "state" field, it is an update for an IC ICUUID := payload.Status.UUID _, err = uuid.Parse(ICUUID) @@ -273,18 +272,160 @@ func processMessage(message amqp.Delivery) error { return fmt.Errorf("AMQP: UUID not valid: %v, message ignored: %v \n", ICUUID, string(message.Body)) } var sToBeUpdated InfrastructureComponent - err = sToBeUpdated.ByUUID(ICUUID) + err = sToBeUpdated.byUUID(ICUUID) if err == gorm.ErrRecordNotFound { // create new record - err = createNewICviaAMQP(payload) + err = createExternalIC(payload) } else if err != nil { // database error err = fmt.Errorf("AMQP: Database error for IC %v DB error message: %v", ICUUID, err) } else { // update record based on payload - err = sToBeUpdated.updateICviaAMQP(payload) + err = sToBeUpdated.updateExternalIC(payload) } } return err } + +func createExternalIC(payload ICUpdate) error { + + var newICReq AddICRequest + newICReq.InfrastructureComponent.UUID = payload.Status.UUID + if payload.Status.Name == nil || + payload.Status.Category == nil || + payload.Status.Type == nil { + // cannot create new IC because required information (name, type, and/or category missing) + return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") + } + newICReq.InfrastructureComponent.Name = *payload.Status.Name + newICReq.InfrastructureComponent.Category = *payload.Status.Category + newICReq.InfrastructureComponent.Type = *payload.Status.Type + + // add optional params + if payload.Status.State != nil { + newICReq.InfrastructureComponent.State = *payload.Status.State + } else { + newICReq.InfrastructureComponent.State = "unknown" + } + if newICReq.InfrastructureComponent.State == "gone" { + // Check if state is "gone" and abort creation of IC in this case + log.Println("AMQP: Aborting creation of IC with state gone") + return nil + } + + if payload.Status.WS_url != nil { + newICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url + } + if payload.Status.API_url != nil { + newICReq.InfrastructureComponent.APIURL = *payload.Status.API_url + } + if payload.Status.Location != nil { + newICReq.InfrastructureComponent.Location = *payload.Status.Location + } + if payload.Status.Description != nil { + newICReq.InfrastructureComponent.Description = *payload.Status.Description + } + if payload.Status.Uptime != nil { + newICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime + } + // TODO add JSON start parameter scheme + + // set managed externally to true because this IC is created via AMQP + newICReq.InfrastructureComponent.ManagedExternally = newTrue() + + // Validate the new IC + err := newICReq.validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) + } + + // Create the new IC + newIC, err := newICReq.createIC(true) + if err != nil { + return fmt.Errorf("AMQP: Creating new IC failed: %v", err) + } + + // save IC + err = newIC.save() + if err != nil { + return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) + } + + log.Println("AMQP: Created IC with UUID ", newIC.UUID) + return nil +} + +func (s *InfrastructureComponent) updateExternalIC(payload ICUpdate) error { + + var updatedICReq UpdateICRequest + if payload.Status.State != nil { + updatedICReq.InfrastructureComponent.State = *payload.Status.State + + if *payload.Status.State == "gone" { + // remove IC from DB + log.Println("AMQP: Deleting IC with state gone") + err := s.delete(true) + if err != nil { + // if component could not be deleted there are still configurations using it in the DB + // continue with the update to save the new state of the component and get back to the deletion later + log.Println("AMQP: Deletion of IC postponed (config(s) associated to it)") + } + + } + } + if payload.Status.Type != nil { + updatedICReq.InfrastructureComponent.Type = *payload.Status.Type + } + if payload.Status.Category != nil { + updatedICReq.InfrastructureComponent.Category = *payload.Status.Category + } + if payload.Status.Name != nil { + updatedICReq.InfrastructureComponent.Name = *payload.Status.Name + } + if payload.Status.WS_url != nil { + updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url + } + if payload.Status.API_url != nil { + updatedICReq.InfrastructureComponent.APIURL = *payload.Status.API_url + } + if payload.Status.Location != nil { + //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Status.Location + `"}`)} + updatedICReq.InfrastructureComponent.Location = *payload.Status.Location + } + if payload.Status.Description != nil { + updatedICReq.InfrastructureComponent.Description = *payload.Status.Description + } + if payload.Status.Uptime != nil { + updatedICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime + } + // TODO add JSON start parameter scheme + + // Validate the updated IC + err := updatedICReq.validate() + if err != nil { + return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) + } + + // Create the updated IC from old IC + updatedIC := updatedICReq.updatedIC(*s) + + // Finally update the IC in the DB + err = s.update(updatedIC) + if err != nil { + return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", s.Name, err) + } + + log.Println("AMQP: Updated IC with UUID ", s.UUID) + return err +} + +func newTrue() *bool { + b := true + return &b +} + +func newFalse() *bool { + b := false + return &b +} diff --git a/routes/infrastructure-component/ic_endpoints.go b/routes/infrastructure-component/ic_endpoints.go index a542a63..2b9f5b8 100644 --- a/routes/infrastructure-component/ic_endpoints.go +++ b/routes/infrastructure-component/ic_endpoints.go @@ -22,7 +22,6 @@ package infrastructure_component import ( - "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "github.com/gin-gonic/gin" @@ -108,9 +107,9 @@ func addIC(c *gin.Context) { return } - if !newIC.ManagedExternally { + if !(newIC.ManagedExternally) { // Save new IC to DB if not managed externally - err = newIC.Save() + err = newIC.save() if helper.DBError(c, err) { return @@ -142,6 +141,11 @@ func updateIC(c *gin.Context) { return } + if oldIC.ManagedExternally { + helper.ForbiddenError(c, "Cannot update externally managed component via API") + return + } + var req UpdateICRequest err := c.BindJSON(&req) if err != nil { @@ -156,17 +160,10 @@ func updateIC(c *gin.Context) { } // Create the updatedIC from oldIC - updatedIC, err := req.updatedIC(oldIC, false) - if err != nil { - c.JSON(http.StatusForbidden, gin.H{ - "success": false, - "message": fmt.Sprintf("%v", err), - }) - return - } + updatedIC := req.updatedIC(oldIC) // Finally update the IC in the DB - err = oldIC.Update(updatedIC) + err = oldIC.update(updatedIC) if !helper.DBError(c, err) { c.JSON(http.StatusOK, gin.H{"ic": updatedIC.InfrastructureComponent}) } @@ -285,7 +282,7 @@ func sendActionToIC(c *gin.Context) { } //now := time.Now() - log.Println("AMQP: Will attempt to send the following actions:", actions) + log.Println("AMQP: Sending actions:", actions) for _, action := range actions { /*if action.When == 0 { diff --git a/routes/infrastructure-component/ic_methods.go b/routes/infrastructure-component/ic_methods.go index 3a757b2..b8d06c1 100644 --- a/routes/infrastructure-component/ic_methods.go +++ b/routes/infrastructure-component/ic_methods.go @@ -32,25 +32,25 @@ type InfrastructureComponent struct { database.InfrastructureComponent } -func (s *InfrastructureComponent) Save() error { +func (s *InfrastructureComponent) save() error { db := database.GetDB() err := db.Create(s).Error return err } -func (s *InfrastructureComponent) ByID(id uint) error { +func (s *InfrastructureComponent) byID(id uint) error { db := database.GetDB() err := db.Find(s, id).Error return err } -func (s *InfrastructureComponent) ByUUID(uuid string) error { +func (s *InfrastructureComponent) byUUID(uuid string) error { db := database.GetDB() err := db.Find(s, "UUID = ?", uuid).Error return err } -func (s *InfrastructureComponent) Update(updatedIC InfrastructureComponent) error { +func (s *InfrastructureComponent) update(updatedIC InfrastructureComponent) error { db := database.GetDB() err := db.Model(s).Updates(updatedIC).Error @@ -66,6 +66,7 @@ func (s *InfrastructureComponent) delete(receivedViaAMQP bool) error { action.Properties.UUID = new(string) *action.Properties.UUID = s.UUID + log.Println("AMQP: Sending request to delete IC with UUID", s.UUID) err := sendActionAMQP(action) return err } @@ -89,144 +90,3 @@ func (s *InfrastructureComponent) getConfigs() ([]database.ComponentConfiguratio err := db.Order("ID asc").Model(s).Related(&configs, "ComponentConfigurations").Error return configs, len(configs), err } - -func createNewICviaAMQP(payload ICUpdate) error { - - var newICReq AddICRequest - newICReq.InfrastructureComponent.UUID = payload.Status.UUID - if payload.Status.Name == nil || - payload.Status.Category == nil || - payload.Status.Type == nil { - // cannot create new IC because required information (name, type, and/or category missing) - return fmt.Errorf("AMQP: Cannot create new IC, required field(s) is/are missing: name, type, category") - } - newICReq.InfrastructureComponent.Name = *payload.Status.Name - newICReq.InfrastructureComponent.Category = *payload.Status.Category - newICReq.InfrastructureComponent.Type = *payload.Status.Type - - // add optional params - if payload.Status.State != nil { - newICReq.InfrastructureComponent.State = *payload.Status.State - } else { - newICReq.InfrastructureComponent.State = "unknown" - } - if newICReq.InfrastructureComponent.State == "gone" { - // Check if state is "gone" and abort creation of IC in this case - log.Println("########## AMQP: Aborting creation of IC with state gone") - return nil - } - - if payload.Status.WS_url != nil { - newICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url - } - if payload.Status.API_url != nil { - newICReq.InfrastructureComponent.APIURL = *payload.Status.API_url - } - if payload.Status.Location != nil { - newICReq.InfrastructureComponent.Location = *payload.Status.Location - } - if payload.Status.Description != nil { - newICReq.InfrastructureComponent.Description = *payload.Status.Description - } - if payload.Status.Uptime != nil { - newICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is created via AMQP - newICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the new IC - err := newICReq.validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of new IC failed: %v", err) - } - - // Create the new IC - newIC, err := newICReq.createIC(true) - if err != nil { - return fmt.Errorf("AMQP: Creating new IC failed: %v", err) - } - - // save IC - err = newIC.Save() - if err != nil { - return fmt.Errorf("AMQP: Saving new IC to DB failed: %v", err) - } - - return nil -} - -func (s *InfrastructureComponent) updateICviaAMQP(payload ICUpdate) error { - - var updatedICReq UpdateICRequest - if payload.Status.State != nil { - updatedICReq.InfrastructureComponent.State = *payload.Status.State - - if *payload.Status.State == "gone" { - // remove IC from DB - log.Println("########## AMQP: Deleting IC with state gone") - err := s.delete(true) - if err != nil { - // if component could not be deleted there are still configurations using it in the DB - // continue with the update to save the new state of the component and get back to the deletion later - log.Println("########## AMQP: Deletion of IC postponed (config(s) associated to it)") - } - - } - } - if payload.Status.Type != nil { - updatedICReq.InfrastructureComponent.Type = *payload.Status.Type - } - if payload.Status.Category != nil { - updatedICReq.InfrastructureComponent.Category = *payload.Status.Category - } - if payload.Status.Name != nil { - updatedICReq.InfrastructureComponent.Name = *payload.Status.Name - } - if payload.Status.WS_url != nil { - updatedICReq.InfrastructureComponent.WebsocketURL = *payload.Status.WS_url - } - if payload.Status.API_url != nil { - updatedICReq.InfrastructureComponent.APIURL = *payload.Status.API_url - } - if payload.Status.Location != nil { - //postgres.Jsonb{json.RawMessage(`{"location" : " ` + *payload.Status.Location + `"}`)} - updatedICReq.InfrastructureComponent.Location = *payload.Status.Location - } - if payload.Status.Description != nil { - updatedICReq.InfrastructureComponent.Description = *payload.Status.Description - } - if payload.Status.Uptime != nil { - updatedICReq.InfrastructureComponent.Uptime = *payload.Status.Uptime - } - // TODO add JSON start parameter scheme - - // set managed externally to true because this IC is updated via AMQP - updatedICReq.InfrastructureComponent.ManagedExternally = newTrue() - - // Validate the updated IC - err := updatedICReq.validate() - if err != nil { - return fmt.Errorf("AMQP: Validation of updated IC failed: %v", err) - } - - // Create the updated IC from old IC - updatedIC, err := updatedICReq.updatedIC(*s, true) - if err != nil { - return fmt.Errorf("AMQP: Unable to update IC %v : %v", s.Name, err) - } - - // Finally update the IC in the DB - err = s.Update(updatedIC) - if err != nil { - return fmt.Errorf("AMQP: Unable to update IC %v in DB: %v", s.Name, err) - } - - return err -} - -func newTrue() *bool { - b := true - return &b -} diff --git a/routes/infrastructure-component/ic_middleware.go b/routes/infrastructure-component/ic_middleware.go index a5c7d5a..3125d5b 100644 --- a/routes/infrastructure-component/ic_middleware.go +++ b/routes/infrastructure-component/ic_middleware.go @@ -45,7 +45,7 @@ func CheckPermissions(c *gin.Context, modeltype database.ModelName, operation da return false, s } - err = s.ByID(uint(ICID)) + err = s.byID(uint(ICID)) if helper.DBError(c, err) { return false, s } diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index a9f1489..4204d26 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -25,10 +25,11 @@ import ( "encoding/json" "fmt" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" + component_configuration "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" "github.com/jinzhu/gorm/dialects/postgres" "github.com/streadway/amqp" "github.com/stretchr/testify/assert" - "log" "os" "testing" "time" @@ -41,6 +42,8 @@ import ( ) var router *gin.Engine +var api *gin.RouterGroup +var waitingTime time.Duration = 2 type ICRequest struct { UUID string `json:"uuid,omitempty"` @@ -53,7 +56,21 @@ type ICRequest struct { Location string `json:"location,omitempty"` Description string `json:"description,omitempty"` StartParameterScheme postgres.Jsonb `json:"startparameterscheme,omitempty"` - ManagedExternally *bool `json:"managedexternally,omitempty"` + ManagedExternally *bool `json:"managedexternally"` +} + +type ScenarioRequest struct { + Name string `json:"name,omitempty"` + Running bool `json:"running,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` +} + +type ConfigRequest struct { + Name string `json:"name,omitempty"` + ScenarioID uint `json:"scenarioID,omitempty"` + ICID uint `json:"icID,omitempty"` + StartParameters postgres.Jsonb `json:"startParameters,omitempty"` + FileIDs []int64 `json:"fileIDs,omitempty"` } type ICAction struct { @@ -84,25 +101,16 @@ func TestMain(m *testing.M) { defer database.DBpool.Close() router = gin.Default() - api := router.Group("/api") + api = router.Group("/api") user.RegisterAuthenticate(api.Group("/authenticate")) api.Use(user.Authentication(true)) RegisterICEndpoints(api.Group("/ic")) - RegisterAMQPEndpoint(api.Group("/ic")) - - // connect AMQP client (make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set via command line parameters) - host, err := configuration.GolbalConfig.String("amqp.host") - user, err := configuration.GolbalConfig.String("amqp.user") - pass, err := configuration.GolbalConfig.String("amqp.pass") - - amqpURI := "amqp://" + user + ":" + pass + "@" + host - log.Println("AMQP URI is", amqpURI) - - err = ConnectAMQP(amqpURI) - if err != nil { - panic(m) - } + // component configuration endpoints required to associate an IC with a component config + component_configuration.RegisterComponentConfigurationEndpoints(api.Group("/configs")) + // scenario endpoints required here to first add a scenario to the DB + // that can be associated with a new component configuration + scenario.RegisterScenarioEndpoints(api.Group("/scenarios")) os.Exit(m.Run()) } @@ -112,6 +120,22 @@ func TestAddICAsAdmin(t *testing.T) { database.MigrateModels() assert.NoError(t, helper.DBAddAdminAndUserAndGuest()) + // check AMQP connection + err := CheckConnection() + assert.Errorf(t, err, "connection is nil") + + // connect AMQP client + // Make sure that AMQP_HOST, AMQP_USER, AMQP_PASS are set + host, err := configuration.GolbalConfig.String("amqp.host") + user, err := configuration.GolbalConfig.String("amqp.user") + pass, err := configuration.GolbalConfig.String("amqp.pass") + amqpURI := "amqp://" + user + ":" + pass + "@" + host + + // AMQP Connection startup is tested here + // Not repeated in other tests because it is only needed once + err = StartAMQP(amqpURI, api) + assert.NoError(t, err) + // authenticate as admin token, err := helper.AuthenticateForTest(router, "/api/authenticate", "POST", helper.AdminCredentials) @@ -146,7 +170,7 @@ func TestAddICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -178,6 +202,30 @@ func TestAddICAsAdmin(t *testing.T) { fmt.Sprintf("/api/ic/%v", newICID+1), "GET", nil) assert.NoError(t, err) assert.Equalf(t, 404, code, "Response body: \n%v\n", resp) + + newExternalIC := ICRequest{ + UUID: helper.ICB.UUID, + WebsocketURL: helper.ICB.WebsocketURL, + APIURL: helper.ICB.APIURL, + Type: helper.ICB.Type, + Name: helper.ICB.Name, + Category: helper.ICB.Category, + State: helper.ICB.State, + Location: helper.ICB.Location, + Description: helper.ICB.Description, + StartParameterScheme: helper.ICB.StartParameterScheme, + ManagedExternally: newTrue(), + } + + // test creation of external IC (should lead to emission of AMQP message to VILLAS) + code, resp, err = helper.TestEndpoint(router, token, + "/api/ic", "POST", helper.KeyModels{"ic": newExternalIC}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newExternalIC + err = helper.CompareResponse(resp, helper.KeyModels{"ic": newExternalIC}) + assert.NoError(t, err) } func TestAddICAsUser(t *testing.T) { @@ -201,7 +249,7 @@ func TestAddICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } // This should fail with unprocessable entity 422 error code @@ -233,7 +281,7 @@ func TestUpdateICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -277,6 +325,54 @@ func TestUpdateICAsAdmin(t *testing.T) { err = helper.CompareResponse(resp, helper.KeyModels{"ic": newIC}) assert.NoError(t, err) + // fake an IC update (create) message + var update ICUpdate + update.Status = new(ICStatus) + update.Status.UUID = helper.ICB.UUID + update.Status.State = new(string) + *update.Status.State = "idle" + update.Status.Name = new(string) + *update.Status.Name = helper.ICB.Name + update.Status.Category = new(string) + *update.Status.Category = helper.ICB.Category + update.Status.Type = new(string) + *update.Status.Type = helper.ICB.Type + + payload, err := json.Marshal(update) + assert.NoError(t, err) + + msg := amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = CheckConnection() + assert.NoError(t, err) + + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + // Wait until externally managed IC is created (happens async) + time.Sleep(waitingTime * time.Second) + + // try to update this IC + var updatedIC ICRequest + updatedIC.Name = "a new name" + updatedIC.ManagedExternally = newTrue() + + // Should result in forbidden return code 403 + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/ic/%v", 2), "PUT", helper.KeyModels{"ic": updatedIC}) + assert.NoError(t, err) + assert.Equalf(t, 403, code, "Response body: \n%v\n", resp) } func TestUpdateICAsUser(t *testing.T) { @@ -300,7 +396,7 @@ func TestUpdateICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -347,7 +443,7 @@ func TestDeleteICAsAdmin(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -379,6 +475,58 @@ func TestDeleteICAsAdmin(t *testing.T) { assert.NoError(t, err) assert.Equal(t, finalNumber, initialNumber-1) + + // fake an IC update (create) message + var update ICUpdate + update.Status = new(ICStatus) + update.Status.UUID = helper.ICB.UUID + update.Status.State = new(string) + *update.Status.State = "idle" + update.Status.Name = new(string) + *update.Status.Name = helper.ICB.Name + update.Status.Category = new(string) + *update.Status.Category = helper.ICB.Category + update.Status.Type = new(string) + *update.Status.Type = helper.ICB.Type + + payload, err := json.Marshal(update) + assert.NoError(t, err) + + msg := amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = CheckConnection() + assert.NoError(t, err) + + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + // Wait until externally managed IC is created (happens async) + time.Sleep(waitingTime * time.Second) + + // Delete the added external IC (triggers an AMQP message, but should not remove the IC from the DB) + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/ic/%v", 2), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Again count the number of all the ICs returned + finalNumberAfterExtneralDelete, err := helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + + assert.Equal(t, finalNumber+1, finalNumberAfterExtneralDelete) + } func TestDeleteICAsUser(t *testing.T) { @@ -402,7 +550,7 @@ func TestDeleteICAsUser(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newIC}) @@ -453,7 +601,7 @@ func TestGetAllICs(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -471,8 +619,9 @@ func TestGetAllICs(t *testing.T) { Location: helper.ICB.Location, Description: helper.ICB.Description, StartParameterScheme: helper.ICB.StartParameterScheme, - ManagedExternally: &helper.ICB.ManagedExternally, + ManagedExternally: newFalse(), } + code, resp, err = helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICB}) assert.NoError(t, err) @@ -519,7 +668,7 @@ func TestGetConfigsOfIC(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -531,7 +680,6 @@ func TestGetConfigsOfIC(t *testing.T) { assert.NoError(t, err) // test GET ic/ID/confis - // TODO how to properly test this without using component configuration endpoints? numberOfConfigs, err := helper.LengthOfResponse(router, token, fmt.Sprintf("/api/ic/%v/configs", newICID), "GET", nil) assert.NoError(t, err) @@ -545,7 +693,6 @@ func TestGetConfigsOfIC(t *testing.T) { assert.NoError(t, err) // test GET ic/ID/configs - // TODO how to properly test this without using component configuration endpoints? numberOfConfigs, err = helper.LengthOfResponse(router, token, fmt.Sprintf("/api/ic/%v/configs", newICID), "GET", nil) assert.NoError(t, err) @@ -582,7 +729,7 @@ func TestSendActionToIC(t *testing.T) { Location: helper.ICA.Location, Description: helper.ICA.Description, StartParameterScheme: helper.ICA.StartParameterScheme, - ManagedExternally: &helper.ICA.ManagedExternally, + ManagedExternally: newFalse(), } code, resp, err := helper.TestEndpoint(router, token, "/api/ic", "POST", helper.KeyModels{"ic": newICA}) @@ -615,24 +762,23 @@ func TestSendActionToIC(t *testing.T) { assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) } -func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { +func TestCreateUpdateViaAMQPRecv(t *testing.T) { database.DropTables() database.MigrateModels() assert.NoError(t, helper.DBAddAdminAndUserAndGuest()) + // authenticate as admin + token, err := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.AdminCredentials) + assert.NoError(t, err) + // fake an IC update message var update ICUpdate update.Status = new(ICStatus) update.Status.UUID = helper.ICA.UUID update.Status.State = new(string) *update.Status.State = "idle" - update.Status.Name = new(string) - *update.Status.Name = helper.ICA.Name - update.Status.Category = new(string) - *update.Status.Category = helper.ICA.Category - update.Status.Type = new(string) - *update.Status.Type = helper.ICA.Type payload, err := json.Marshal(update) assert.NoError(t, err) @@ -648,6 +794,50 @@ func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { err = CheckConnection() assert.NoError(t, err) + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + time.Sleep(waitingTime * time.Second) + + // get the length of the GET all ICs response for user + number, err := helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 0, number) + + // complete the (required) data of an IC + update.Status.Name = new(string) + *update.Status.Name = helper.ICA.Name + update.Status.Category = new(string) + *update.Status.Category = helper.ICA.Category + update.Status.Type = new(string) + *update.Status.Type = helper.ICA.Type + update.Status.Uptime = new(float64) + *update.Status.Uptime = -1.0 + update.Status.WS_url = new(string) + *update.Status.WS_url = helper.ICA.WebsocketURL + update.Status.API_url = new(string) + *update.Status.API_url = helper.ICA.APIURL + update.Status.Description = new(string) + *update.Status.Description = helper.ICA.Description + update.Status.Location = new(string) + *update.Status.Location = helper.ICA.Location + + payload, err = json.Marshal(update) + assert.NoError(t, err) + + msg = amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } err = client.channel.Publish(VILLAS_EXCHANGE, "", @@ -656,14 +846,10 @@ func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { msg) assert.NoError(t, err) - time.Sleep(4 * time.Second) - // authenticate as admin - token, err := helper.AuthenticateForTest(router, - "/api/authenticate", "POST", helper.AdminCredentials) - assert.NoError(t, err) + time.Sleep(waitingTime * time.Second) // get the length of the GET all ICs response for user - number, err := helper.LengthOfResponse(router, token, + number, err = helper.LengthOfResponse(router, token, "/api/ic", "GET", nil) assert.NoError(t, err) assert.Equal(t, 1, number) @@ -689,13 +875,121 @@ func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { msg) assert.NoError(t, err) - time.Sleep(4 * time.Second) + time.Sleep(waitingTime * time.Second) // get the length of the GET all ICs response for user number, err = helper.LengthOfResponse(router, token, "/api/ic", "GET", nil) assert.NoError(t, err) assert.Equal(t, 1, number) +} + +func TestDeleteICViaAMQPRecv(t *testing.T) { + + database.DropTables() + database.MigrateModels() + assert.NoError(t, helper.DBAddAdminAndUserAndGuest()) + + // authenticate as admin + token, err := helper.AuthenticateForTest(router, + "/api/authenticate", "POST", helper.AdminCredentials) + assert.NoError(t, err) + + // fake an IC update message + var update ICUpdate + update.Status = new(ICStatus) + update.Status.UUID = helper.ICA.UUID + update.Status.State = new(string) + *update.Status.State = "idle" + // complete the (required) data of an IC + update.Status.Name = new(string) + *update.Status.Name = helper.ICA.Name + update.Status.Category = new(string) + *update.Status.Category = helper.ICA.Category + update.Status.Type = new(string) + *update.Status.Type = helper.ICA.Type + update.Status.Uptime = new(float64) + *update.Status.Uptime = -1.0 + update.Status.WS_url = new(string) + *update.Status.WS_url = helper.ICA.WebsocketURL + update.Status.API_url = new(string) + *update.Status.API_url = helper.ICA.APIURL + update.Status.Description = new(string) + *update.Status.Description = helper.ICA.Description + update.Status.Location = new(string) + *update.Status.Location = helper.ICA.Location + + payload, err := json.Marshal(update) + assert.NoError(t, err) + + msg := amqp.Publishing{ + DeliveryMode: 2, + Timestamp: time.Now(), + ContentType: "application/json", + ContentEncoding: "utf-8", + Priority: 0, + Body: payload, + } + + err = CheckConnection() + assert.NoError(t, err) + err = client.channel.Publish(VILLAS_EXCHANGE, + "", + false, + false, + msg) + assert.NoError(t, err) + + time.Sleep(waitingTime * time.Second) + + // get the length of the GET all ICs response for user + number, err := helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 1, number) + + // add scenario + newScenario := ScenarioRequest{ + Name: helper.ScenarioA.Name, + Running: helper.ScenarioA.Running, + StartParameters: helper.ScenarioA.StartParameters, + } + + code, resp, err := helper.TestEndpoint(router, token, + "/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newScenario + err = helper.CompareResponse(resp, helper.KeyModels{"scenario": newScenario}) + assert.NoError(t, err) + + // Read newScenario's ID from the response + newScenarioID, err := helper.GetResponseID(resp) + assert.NoError(t, err) + + // Add component config and associate with IC and scenario + newConfig := ConfigRequest{ + Name: helper.ConfigA.Name, + ScenarioID: uint(newScenarioID), + ICID: 1, + StartParameters: helper.ConfigA.StartParameters, + FileIDs: helper.ConfigA.FileIDs, + } + + code, resp, err = helper.TestEndpoint(router, token, + "/api/configs", "POST", helper.KeyModels{"config": newConfig}) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare POST's response with the newConfig + err = helper.CompareResponse(resp, helper.KeyModels{"config": newConfig}) + assert.NoError(t, err) + + // Read newConfig's ID from the response + newConfigID, err := helper.GetResponseID(resp) + assert.NoError(t, err) + // modify status update to state "gone" *update.Status.State = "gone" payload, err = json.Marshal(update) @@ -710,6 +1004,7 @@ func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { Body: payload, } + // attempt to delete IC (should not work immediately because IC is still associated with component config) err = client.channel.Publish(VILLAS_EXCHANGE, "", false, @@ -717,15 +1012,27 @@ func TestCreateUpdateDeleteViaAMQPRecv(t *testing.T) { msg) assert.NoError(t, err) - time.Sleep(4 * time.Second) + time.Sleep(waitingTime * time.Second) + + // get the length of the GET all ICs response for user + number, err = helper.LengthOfResponse(router, token, + "/api/ic", "GET", nil) + assert.NoError(t, err) + assert.Equal(t, 1, number) + + // Delete component config from earlier + code, resp, err = helper.TestEndpoint(router, token, + fmt.Sprintf("/api/configs/%v", newConfigID), "DELETE", nil) + assert.NoError(t, err) + assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) + + // Compare DELETE's response with the newConfig + err = helper.CompareResponse(resp, helper.KeyModels{"config": newConfig}) + assert.NoError(t, err) + // get the length of the GET all ICs response for user number, err = helper.LengthOfResponse(router, token, "/api/ic", "GET", nil) assert.NoError(t, err) assert.Equal(t, 0, number) - -} - -func TestCreateDeleteViaAMQPSend(t *testing.T) { - } diff --git a/routes/infrastructure-component/ic_validators.go b/routes/infrastructure-component/ic_validators.go index 9f6bb05..fae7369 100644 --- a/routes/infrastructure-component/ic_validators.go +++ b/routes/infrastructure-component/ic_validators.go @@ -23,7 +23,6 @@ package infrastructure_component import ( "encoding/json" - "fmt" "github.com/google/uuid" "github.com/jinzhu/gorm/dialects/postgres" "github.com/nsf/jsondiff" @@ -60,7 +59,6 @@ type validUpdatedIC struct { Location string `form:"Location" validate:"omitempty"` Description string `form:"Description" validate:"omitempty"` StartParameterScheme postgres.Jsonb `form:"StartParameterScheme" validate:"omitempty"` - ManagedExternally *bool `form:"ManagedExternally" validate:"required"` Uptime float64 `form:"Uptime" validate:"omitempty"` } @@ -134,43 +132,36 @@ func (r *AddICRequest) createIC(receivedViaAMQP bool) (InfrastructureComponent, *action.Properties.UUID = r.InfrastructureComponent.UUID } - log.Println("########## AMQP: Sending request to create new IC") + log.Println("AMQP: Sending request to create new IC") err = sendActionAMQP(action) - - // s remains empty - - } else { - s.UUID = r.InfrastructureComponent.UUID - s.WebsocketURL = r.InfrastructureComponent.WebsocketURL - s.APIURL = r.InfrastructureComponent.APIURL - s.Type = r.InfrastructureComponent.Type - s.Name = r.InfrastructureComponent.Name - s.Category = r.InfrastructureComponent.Category - s.Location = r.InfrastructureComponent.Location - s.Description = r.InfrastructureComponent.Description - s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme - s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally - s.Uptime = -1.0 // no uptime available - if r.InfrastructureComponent.State != "" { - s.State = r.InfrastructureComponent.State - } else { - s.State = "unknown" - } - // set last update to creation time of IC - s.StateUpdateAt = time.Now().Format(time.RFC1123) } + s.UUID = r.InfrastructureComponent.UUID + s.WebsocketURL = r.InfrastructureComponent.WebsocketURL + s.APIURL = r.InfrastructureComponent.APIURL + s.Type = r.InfrastructureComponent.Type + s.Name = r.InfrastructureComponent.Name + s.Category = r.InfrastructureComponent.Category + s.Location = r.InfrastructureComponent.Location + s.Description = r.InfrastructureComponent.Description + s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme + s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally + s.Uptime = -1.0 // no uptime available + if r.InfrastructureComponent.State != "" { + s.State = r.InfrastructureComponent.State + } else { + s.State = "unknown" + } + // set last update to creation time of IC + s.StateUpdateAt = time.Now().Format(time.RFC1123) + return s, err } -func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent, receivedViaAMQP bool) (InfrastructureComponent, error) { +func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent) InfrastructureComponent { // Use the old InfrastructureComponent as a basis for the updated InfrastructureComponent `s` s := oldIC - if s.ManagedExternally && !receivedViaAMQP { - // externally managed IC cannot be updated via API, only via AMQP - return s, fmt.Errorf("cannot update externally managed IC %v", s.Name) - } if r.InfrastructureComponent.UUID != "" { s.UUID = r.InfrastructureComponent.UUID } @@ -207,10 +198,6 @@ func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent, receivedViaAM s.Description = r.InfrastructureComponent.Description } - if r.InfrastructureComponent.ManagedExternally != nil { - s.ManagedExternally = *r.InfrastructureComponent.ManagedExternally - } - // set last update time s.StateUpdateAt = time.Now().Format(time.RFC1123) @@ -225,5 +212,5 @@ func (r *UpdateICRequest) updatedIC(oldIC InfrastructureComponent, receivedViaAM s.StartParameterScheme = r.InfrastructureComponent.StartParameterScheme } - return s, nil + return s } From 392cb2fbff83c7ebf0209b85d68e287e29de9354 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Tue, 3 Nov 2020 15:35:42 +0100 Subject: [PATCH 15/18] remove obsolete line in IC test --- routes/infrastructure-component/ic_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/infrastructure-component/ic_test.go b/routes/infrastructure-component/ic_test.go index 4204d26..21a064f 100644 --- a/routes/infrastructure-component/ic_test.go +++ b/routes/infrastructure-component/ic_test.go @@ -366,7 +366,6 @@ func TestUpdateICAsAdmin(t *testing.T) { // try to update this IC var updatedIC ICRequest updatedIC.Name = "a new name" - updatedIC.ManagedExternally = newTrue() // Should result in forbidden return code 403 code, resp, err = helper.TestEndpoint(router, token, From d00778153563336da7b8be2de4910cf27c88be91 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Wed, 11 Nov 2020 16:34:06 +0100 Subject: [PATCH 16/18] Update API doc with swaggo --- doc/api/docs.go | 23 ++++++++++++++++++----- doc/api/swagger.json | 21 +++++++++++++++++---- doc/api/swagger.yaml | 15 ++++++++++++--- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/doc/api/docs.go b/doc/api/docs.go index e5d15c6..a381b6b 100644 --- a/doc/api/docs.go +++ b/doc/api/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2020-10-20 15:23:51.266197121 +0200 CEST m=+0.098130845 +// 2020-11-11 16:32:47.799676915 +0100 CET m=+0.126448240 package docs @@ -1310,7 +1310,7 @@ var doc = `{ "application/json" ], "tags": [ - "AMQP" + "infrastructure-components" ], "summary": "Send an action to IC (only available if backend server is started with -amqp parameter)", "operationId": "sendActionToIC", @@ -2996,6 +2996,10 @@ var doc = `{ "description": "Location of the IC", "type": "string" }, + "managedexternally": { + "description": "Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)", + "type": "boolean" + }, "name": { "description": "Name of the IC", "type": "string" @@ -3018,7 +3022,7 @@ var doc = `{ }, "uptime": { "description": "Uptime of the IC", - "type": "integer" + "type": "number" }, "uuid": { "description": "UUID of the IC", @@ -3377,9 +3381,9 @@ var doc = `{ "type": "object", "required": [ "Category", + "ManagedExternally", "Name", - "Type", - "UUID" + "Type" ], "properties": { "APIURL": { @@ -3394,6 +3398,9 @@ var doc = `{ "Location": { "type": "string" }, + "ManagedExternally": { + "type": "boolean" + }, "Name": { "type": "string" }, @@ -3409,6 +3416,9 @@ var doc = `{ "UUID": { "type": "string" }, + "Uptime": { + "type": "number" + }, "WebsocketURL": { "type": "string" } @@ -3444,6 +3454,9 @@ var doc = `{ "UUID": { "type": "string" }, + "Uptime": { + "type": "number" + }, "WebsocketURL": { "type": "string" } diff --git a/doc/api/swagger.json b/doc/api/swagger.json index 5bbbc4a..f992ef0 100644 --- a/doc/api/swagger.json +++ b/doc/api/swagger.json @@ -1293,7 +1293,7 @@ "application/json" ], "tags": [ - "AMQP" + "infrastructure-components" ], "summary": "Send an action to IC (only available if backend server is started with -amqp parameter)", "operationId": "sendActionToIC", @@ -2979,6 +2979,10 @@ "description": "Location of the IC", "type": "string" }, + "managedexternally": { + "description": "Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller)", + "type": "boolean" + }, "name": { "description": "Name of the IC", "type": "string" @@ -3001,7 +3005,7 @@ }, "uptime": { "description": "Uptime of the IC", - "type": "integer" + "type": "number" }, "uuid": { "description": "UUID of the IC", @@ -3360,9 +3364,9 @@ "type": "object", "required": [ "Category", + "ManagedExternally", "Name", - "Type", - "UUID" + "Type" ], "properties": { "APIURL": { @@ -3377,6 +3381,9 @@ "Location": { "type": "string" }, + "ManagedExternally": { + "type": "boolean" + }, "Name": { "type": "string" }, @@ -3392,6 +3399,9 @@ "UUID": { "type": "string" }, + "Uptime": { + "type": "number" + }, "WebsocketURL": { "type": "string" } @@ -3427,6 +3437,9 @@ "UUID": { "type": "string" }, + "Uptime": { + "type": "number" + }, "WebsocketURL": { "type": "string" } diff --git a/doc/api/swagger.yaml b/doc/api/swagger.yaml index c59428e..7cb52c3 100644 --- a/doc/api/swagger.yaml +++ b/doc/api/swagger.yaml @@ -160,6 +160,9 @@ definitions: location: description: Location of the IC type: string + managedexternally: + description: Boolean indicating if IC is managed externally (via AMQP/ VILLAScontroller) + type: boolean name: description: Name of the IC type: string @@ -177,7 +180,7 @@ definitions: type: string uptime: description: Uptime of the IC - type: integer + type: number uuid: description: UUID of the IC type: string @@ -429,6 +432,8 @@ definitions: type: string Location: type: string + ManagedExternally: + type: boolean Name: type: string StartParameterScheme: @@ -439,13 +444,15 @@ definitions: type: string UUID: type: string + Uptime: + type: number WebsocketURL: type: string required: - Category + - ManagedExternally - Name - Type - - UUID type: object infrastructure_component.validUpdatedIC: properties: @@ -467,6 +474,8 @@ definitions: type: string UUID: type: string + Uptime: + type: number WebsocketURL: type: string type: object @@ -1558,7 +1567,7 @@ paths: summary: Send an action to IC (only available if backend server is started with -amqp parameter) tags: - - AMQP + - infrastructure-components /ic/{ICID}/configs: get: operationId: getConfigsOfIC From defc3a87f71037accbba2fbca0c3929a33339b21 Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 12 Nov 2020 14:17:30 +0100 Subject: [PATCH 17/18] Enable test mode to work with AMQP, adapt IC test data --- helper/test_data.go | 10 +++++----- start.go | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/helper/test_data.go b/helper/test_data.go index 5dbcde4..08c3379 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -94,9 +94,9 @@ var propertiesB = json.RawMessage(`{"prop1" : "not so nice"}`) var ICA = database.InfrastructureComponent{ UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", WebsocketURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig", - APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api", - Type: "VILLASnode Signal Generator", - Category: "Signal Generator", + APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api/v2/node/ws_sig", + Type: "villas-node", + Category: "gateway", Name: "ACS Demo Signals", Uptime: -1.0, State: "idle", @@ -110,8 +110,8 @@ var ICA = database.InfrastructureComponent{ var ICB = database.InfrastructureComponent{ UUID: "4854af30-325f-44a5-ad59-b67b2597de68", WebsocketURL: "xxx.yyy.zzz.aaa", - Type: "DPsim", - Category: "Simulator", + Type: "dpsim", + Category: "simulator", Name: "Test DPsim Simulator", Uptime: -1.0, State: "running", diff --git a/start.go b/start.go index 57c3fc7..0960bd3 100644 --- a/start.go +++ b/start.go @@ -94,12 +94,6 @@ func main() { apidocs.SwaggerInfo.Host = baseHost apidocs.SwaggerInfo.BasePath = basePath - // add data to DB (if any) - err = addData(r, mode, basePath, amqphost) - if err != nil { - panic(err) - } - //Start AMQP client if amqphost != "" { // create amqp URL based on username, password and host @@ -110,6 +104,12 @@ func main() { } } + // add data to DB (if any) + err = addData(r, mode, basePath, amqphost) + if err != nil { + panic(err) + } + // server at port 4000 to match frontend's redirect path r.Run(":" + port) } From 8ddafc29d8ebee1c70caee0d0f9c9fa1b78acfcc Mon Sep 17 00:00:00 2001 From: Sonja Happ Date: Thu, 12 Nov 2020 15:12:21 +0100 Subject: [PATCH 18/18] Fix API URL of signal generator --- helper/test_data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/test_data.go b/helper/test_data.go index 08c3379..cdc5fa9 100644 --- a/helper/test_data.go +++ b/helper/test_data.go @@ -94,7 +94,7 @@ var propertiesB = json.RawMessage(`{"prop1" : "not so nice"}`) var ICA = database.InfrastructureComponent{ UUID: "7be0322d-354e-431e-84bd-ae4c9633138b", WebsocketURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/ws_sig", - APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api/v2/node/ws_sig", + APIURL: "https://villas-new.k8s.eonerc.rwth-aachen.de/ws/api/v2", Type: "villas-node", Category: "gateway", Name: "ACS Demo Signals",