diff --git a/configuration/config.go b/configuration/config.go index 786da05..cabfb1d 100644 --- a/configuration/config.go +++ b/configuration/config.go @@ -77,6 +77,7 @@ func InitConfig() error { contactMail = flag.String("contact-mail", "svogel2@eonerc.rwth-aachen.de", "EMail of the administrative contact") testDataPath = flag.String("test-data-path", "", "The path to a test data json file") groupsPath = flag.String("groups-path", "configuration/groups.yaml", "The path to a YAML file that maps user groups to scenario IDs") + apiUpdateInterval = flag.String("api-update-interval", "10s" /* 10 sec */, "Interval in which API URL is queried for status updates of ICs") ) flag.Parse() @@ -108,6 +109,7 @@ func InitConfig() error { "test.datapath": *testDataPath, "groups.path": *groupsPath, "config.file": *configFile, + "apiupdateinterval": *apiUpdateInterval, } if *dbClear == true { diff --git a/go.mod b/go.mod index e9a3cfa..1204e0c 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/gin-gonic/gin v1.4.0 github.com/go-ini/ini v1.51.0 // indirect github.com/go-playground/universal-translator v0.17.0 // indirect + github.com/go-resty/resty/v2 v2.6.0 github.com/google/uuid v1.1.2 github.com/jinzhu/gorm v1.9.11 github.com/kr/pretty v0.2.1 // indirect diff --git a/go.sum b/go.sum index bfc3179..3d049df 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4= +github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -238,6 +240,8 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -259,6 +263,10 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/nt golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/routes/infrastructure-component/ic_apiquery.go b/routes/infrastructure-component/ic_apiquery.go new file mode 100644 index 0000000..99d3472 --- /dev/null +++ b/routes/infrastructure-component/ic_apiquery.go @@ -0,0 +1,147 @@ +/** infrastructure-component package, API queries. +* +* @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 infrastructure_component + +import ( + "encoding/json" + "fmt" + "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" + "github.com/go-resty/resty/v2" + "github.com/jinzhu/gorm/dialects/postgres" + "log" + "strconv" + "strings" + "time" +) + +func QueryICAPIs(d time.Duration) { + + client := resty.New() + //client.SetDebug(true) + + go func() { + + for _ = range time.Tick(d) { + //log.Println("Querying IC APIs at time:", x) + var err error + + db := database.GetDB() + var ics []database.InfrastructureComponent + err = db.Order("ID asc").Find(&ics).Error + if err != nil { + log.Println("Error getting ICs from DB:", err.Error()) + continue + } + + // iterate over ICs in DB + for _, ic := range ics { + + if ic.ManagedExternally { + continue + } + + if ic.APIURL == "" || (!strings.HasPrefix(ic.APIURL, "http://") && !strings.HasPrefix(ic.APIURL, "https://")) { + continue + } + + if ic.Category == "gateway" && ic.Type == "villas-node" { + + log.Println("External API: checking for IC", ic.Name) + statusResponse, err := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/status") + if err != nil { + log.Println("Error querying status of", ic.Name, err) + continue + } + var status map[string]interface{} + err = json.Unmarshal(statusResponse.Body(), &status) + if err != nil { + log.Println("Error unmarshalling status of", ic.Name, err) + continue + } + + parts := strings.Split(ic.WebsocketURL, "/") + if len(parts) > 0 && parts[len(parts)-1] != "" { + + configResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1]) + statsResponse, _ := client.R().SetHeader("Accept", "application/json").Get(ic.APIURL + "/node/" + parts[len(parts)-1] + "/stats") + + var config map[string]interface{} + err = json.Unmarshal(configResponse.Body(), &config) + if err == nil { + status["config"] = config + } + var stats map[string]interface{} + err = json.Unmarshal(statsResponse.Body(), &stats) + if err == nil { + status["statistics"] = stats + } + } + + var updatedIC UpdateICRequest + statusRaw, _ := json.Marshal(status) + updatedIC.InfrastructureComponent.StatusUpdateRaw = postgres.Jsonb{RawMessage: statusRaw} + updatedIC.InfrastructureComponent.State = fmt.Sprintf("%v", status["state"]) + updatedIC.InfrastructureComponent.UUID = fmt.Sprintf("%v", status["uuid"]) + timeNow, myerr := strconv.ParseFloat(fmt.Sprintf("%v", status["time_now"]), 64) + if myerr != nil { + log.Println("Error parsing time_now to float", myerr.Error()) + continue + } + timeStarted, myerr := strconv.ParseFloat(fmt.Sprintf("%v", status["time_started"]), 64) + if myerr != nil { + log.Println("Error parsing time_started to float", myerr.Error()) + continue + } + uptime := timeNow - timeStarted + updatedIC.InfrastructureComponent.Uptime = uptime + + // validate the update + err = updatedIC.validate() + if err != nil { + log.Println("Error validating updated IC", ic.Name, ic.UUID, err.Error()) + continue + } + + // create the update and update IC in DB + var x InfrastructureComponent + err = x.byID(ic.ID) + if err != nil { + log.Println("Error getting IC by ID", ic.Name, err) + continue + } + u := updatedIC.updatedIC(x) + err = x.update(u) + if err != nil { + log.Println("Error updating IC", ic.Name, ic.UUID, err.Error()) + continue + } + + } else if ic.Category == "manager" && ic.Type == "villas-relay" { + + } else if ic.Category == "gateway" && ic.Type == "villas-relay" { + + } + } + } + }() +} diff --git a/start.go b/start.go index 42e8c4c..3d5e23f 100644 --- a/start.go +++ b/start.go @@ -24,6 +24,7 @@ package main import ( "fmt" "log" + "time" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database" @@ -140,6 +141,11 @@ func main() { log.Fatal(err) } + // Update via external APIs of ICs (if not managed via AMQP) + intervalStr, _ := configuration.GlobalConfig.String("apiupdateinterval") + interval, _ := time.ParseDuration(intervalStr) + infrastructure_component.QueryICAPIs(interval) + log.Println("Running...") // Server at port 4000 to match frontend's redirect path r.Run(":" + port)