Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dcb7b79fd7 | ||
|
46edc84421 | ||
|
4a01aaf9bd | ||
|
50f4836e44 | ||
|
4ef6e64a45 | ||
|
4fcb0d9ca2 | ||
|
c7b2050cfe | ||
|
27e3eef7fc | ||
|
b2cf41a088 | ||
|
a72d45d27c | ||
|
0c80f4bdcc | ||
|
47fc86f490 | ||
|
e316ccbf95 | ||
|
4dfda5a807 | ||
|
744a817150 | ||
|
4ebaaebaa7 | ||
|
210373280f | ||
|
ca8a960896 | ||
|
d071609988 | ||
|
c1748edd8d | ||
|
041cda8a35 | ||
|
6c6e52536a | ||
|
630147ecdd | ||
|
36bf26e12a | ||
|
0bca5ebb7b | ||
|
858cfc81e4 | ||
|
74b287017b | ||
|
b74c9e7fa3 | ||
|
a792a8e86f | ||
|
40fd2d5dfb | ||
|
bfedc63eb6 | ||
|
6ab4732b85 | ||
|
cc68365977 | ||
|
72e2870684 | ||
|
a6f340b311 | ||
|
eb7bc9e0fd | ||
|
06e215cde8 | ||
|
8a8e453f18 | ||
|
01846f300a | ||
|
be7015a88f | ||
|
0bf83fc0d0 | ||
|
b278570570 | ||
|
0911c828fc | ||
|
20b93f42d4 | ||
|
44b236e2ef | ||
|
86121b733d | ||
|
a566ccbd6a | ||
|
184f320a2e | ||
|
6abe562642 | ||
|
985617423d | ||
|
48be743abb | ||
|
7797966644 | ||
|
bf2789f0fe | ||
|
cc6394079b | ||
|
ae68804bac | ||
|
598f9b0ec0 | ||
|
749625f399 | ||
|
e3d4f53eb4 | ||
|
6e1800aa5f | ||
|
4138c85639 |
@ -267,6 +267,15 @@ p {
|
||||
.filterformcontainer {
|
||||
text-align: center;
|
||||
}
|
||||
.introtext {
|
||||
margin-bottom: 30px;
|
||||
color: black;
|
||||
font-size: 20px;
|
||||
}
|
||||
.admintextblock {
|
||||
margin-top: 16px;
|
||||
color: black;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.icons.oneback {
|
||||
margin-left: 113px;
|
||||
|
64
Admin/HandlerConfiguration.go
Normal file
64
Admin/HandlerConfiguration.go
Normal file
@ -0,0 +1,64 @@
|
||||
package Admin
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/MimeTypes"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Handler for accessing the file upload function.
|
||||
func HandlerConfiguration(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
// Case: The system goes down now.
|
||||
if Shutdown.IsDown() {
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(request.Method) == `get` {
|
||||
//
|
||||
// Case: Send the website to the client
|
||||
//
|
||||
|
||||
// Read all configuration values:
|
||||
values := ConfigurationDB.ReadAll()
|
||||
|
||||
// Build the data type for the template:
|
||||
data := AdminWebConfiguration{}
|
||||
data.Configuration = values
|
||||
|
||||
// Write the MIME type and execute the template:
|
||||
MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML)
|
||||
if executeError := AdminTemplates.ExecuteTemplate(response, `Configuration`, data); executeError != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the configuration template.`, executeError.Error())
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// Case: Receive the changed configuration
|
||||
//
|
||||
|
||||
// Read all configuration values:
|
||||
values := ConfigurationDB.ReadAll()
|
||||
|
||||
// Loop over all current known values:
|
||||
for _, value := range values {
|
||||
|
||||
// Read the new value from the client side:
|
||||
newValue := request.FormValue(value.Name)
|
||||
|
||||
// Store the new value:
|
||||
value.Value = newValue
|
||||
|
||||
// Update the database:
|
||||
ConfigurationDB.UpdateValue(value.Name, value)
|
||||
}
|
||||
|
||||
// Redirect the client to the admin's overview:
|
||||
defer http.Redirect(response, request, "/configuration", 302)
|
||||
}
|
||||
|
||||
}
|
78
Admin/HandlerFileUpload.go
Normal file
78
Admin/HandlerFileUpload.go
Normal file
@ -0,0 +1,78 @@
|
||||
package Admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/CustomerDB"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/MimeTypes"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Handler for accessing the file upload function.
|
||||
func HandlerFileUpload(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
// Case: The system goes down now.
|
||||
if Shutdown.IsDown() {
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(request.Method) == `get` {
|
||||
//
|
||||
// Case: Send the website to the client
|
||||
//
|
||||
|
||||
// Write the MIME type and execute the template:
|
||||
MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML)
|
||||
if executeError := AdminTemplates.ExecuteTemplate(response, `FileUpload`, nil); executeError != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the file upload template.`, executeError.Error())
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// Case: Receive the file to upload
|
||||
//
|
||||
|
||||
if file, fileHeader, fileError := request.FormFile(`file`); fileError != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameBROWSER, `Was not able to access the file uploaded.`, fileError.Error())
|
||||
} else {
|
||||
//
|
||||
// Case: Access was possible.
|
||||
//
|
||||
|
||||
// Get the GridFS from the database:
|
||||
dbSession, gridFS := CustomerDB.GridFS()
|
||||
defer dbSession.Close()
|
||||
|
||||
// Try to create the desired file at the grid file system:
|
||||
if newFile, errNewFile := gridFS.Create(fileHeader.Filename); errNewFile != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to create the desired file at the grid file system.`, errNewFile.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename))
|
||||
} else {
|
||||
|
||||
// Close the files afterwards:
|
||||
defer file.Close()
|
||||
defer newFile.Close()
|
||||
|
||||
// Try to copy the file's content to the database:
|
||||
if _, errCopy := io.Copy(newFile, file); errCopy != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `Was not able to copy the desired file's content to the grid file system.`, errNewFile.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename))
|
||||
} else {
|
||||
// Try to determine the MIME type:
|
||||
if mimeType, errMime := MimeTypes.DetectType(fileHeader.Filename); errMime != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityLow, LM.ImpactLow, LM.MessageNamePARSE, `Was not able to parse the desired file's MIME type.`, errMime.Error(), fmt.Sprintf("filename='%s'", fileHeader.Filename))
|
||||
} else {
|
||||
// Set also the MIME type in the database:
|
||||
newFile.SetContentType(mimeType.MimeType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect the client to the admin's overview:
|
||||
defer http.Redirect(response, request, "/", 302)
|
||||
}
|
||||
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/MimeTypes"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"github.com/SommerEngineering/Ocean/System/Version"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -17,9 +18,13 @@ func HandlerOverview(response http.ResponseWriter, request *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the data ready:
|
||||
data := AdminWebOverview{}
|
||||
data.Version = Version.GetVersion()
|
||||
|
||||
// Write the MIME type and execute the template:
|
||||
MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML)
|
||||
if executeError := AdminTemplates.ExecuteTemplate(response, `Overview`, nil); executeError != nil {
|
||||
if executeError := AdminTemplates.ExecuteTemplate(response, `Overview`, data); executeError != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameEXECUTE, `Was not able to execute the admin's overview template.`, executeError.Error())
|
||||
}
|
||||
}
|
||||
|
@ -22,4 +22,12 @@ func init() {
|
||||
if _, err := AdminTemplates.Parse(Templates.Overview); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the admin overview.`, err.Error())
|
||||
}
|
||||
|
||||
if _, err := AdminTemplates.Parse(Templates.FileUpload); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the file upload.`, err.Error())
|
||||
}
|
||||
|
||||
if _, err := AdminTemplates.Parse(Templates.Configuration); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to parse the template for the configuration.`, err.Error())
|
||||
}
|
||||
}
|
||||
|
15
Admin/Scheme.go
Normal file
15
Admin/Scheme.go
Normal file
@ -0,0 +1,15 @@
|
||||
package Admin
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
)
|
||||
|
||||
// Data for the admin's configuration management site
|
||||
type AdminWebConfiguration struct {
|
||||
Configuration []ConfigurationDB.ConfigurationDBEntry
|
||||
}
|
||||
|
||||
// Data for the admin's overview i.e. dashboard
|
||||
type AdminWebOverview struct {
|
||||
Version string
|
||||
}
|
47
Admin/Templates/Configuration.go
Normal file
47
Admin/Templates/Configuration.go
Normal file
@ -0,0 +1,47 @@
|
||||
package Templates
|
||||
|
||||
var Configuration = `
|
||||
{{define "Configuration"}}
|
||||
<!DOCTYPE html>
|
||||
<!-- This site was created in Webflow. http://www.webflow.com-->
|
||||
<!-- Last Published: Fri Jun 26 2015 05:50:41 GMT+0000 (UTC) -->
|
||||
<html data-wf-site="547b44aa3e9ac2216ec5d048" data-wf-page="558ce60e0939295c77a362db">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Configuration</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="generator" content="Webflow">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/normalize.css">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/webflow.css">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/admin.css">
|
||||
<script type="text/javascript" src="/admin/js/modernizr.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="w-section headercontainer">
|
||||
<h1>Cluster Configuration</h1>
|
||||
</div>
|
||||
<div class="w-container adminsection">
|
||||
<p class="introtext"><strong>Attention:</strong> This configuration applies to the whole cluster of your Ocean servers. Therefore, <strong>this is not</strong> an individual configuration for any single server! Thus, please consider beforehand if the desired change matches all of your servers.
|
||||
</p>
|
||||
<div class="w-form">
|
||||
<form id="configuration" name="configuration" data-name="configuration" method="post" action="/configuration">
|
||||
{{range .Configuration}}
|
||||
<label for="{{.Name}}">Configuration parameter: {{.Name}}</label>
|
||||
<input class="w-input" id="{{.Name}}" type="text" name="{{.Name}}" data-name="{{.Name}}" required="required" value="{{.Value}}" placeholder="{{.Value}}">
|
||||
{{end}}
|
||||
<input class="w-button button optionbuttons" type="submit" value="Apply all changes" data-wait="Please wait...">
|
||||
</form>
|
||||
<div class="w-form-done">
|
||||
<p>Thank you! Your submission has been received!</p>
|
||||
</div>
|
||||
<div class="w-form-fail">
|
||||
<p>Oops! Something went wrong while submitting the form</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/admin/js/webflow.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
`
|
44
Admin/Templates/FileUpload.go
Normal file
44
Admin/Templates/FileUpload.go
Normal file
@ -0,0 +1,44 @@
|
||||
package Templates
|
||||
|
||||
var FileUpload = `
|
||||
{{define "FileUpload"}}
|
||||
<!DOCTYPE html>
|
||||
<!-- This site was created in Webflow. http://www.webflow.com-->
|
||||
<!-- Last Published: Fri Jun 26 2015 05:50:41 GMT+0000 (UTC) -->
|
||||
<html data-wf-site="547b44aa3e9ac2216ec5d048" data-wf-page="558ce167e20f1f4d31c64577">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Upload a file</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="generator" content="Webflow">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/normalize.css">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/webflow.css">
|
||||
<link rel="stylesheet" type="text/css" href="/admin/css/admin.css">
|
||||
<script type="text/javascript" src="/admin/js/modernizr.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="w-section headercontainer">
|
||||
<h1>Upload a file</h1>
|
||||
</div>
|
||||
<div class="w-container adminsection">
|
||||
<p class="introtext">This function enables you to upload a file to the distributed file system of the MongoDB database. If the desired file is already present, a new revision of this file is created. Therefore, an already existing file gets never overwritten! Please consider, that the configured maximum size of the header of the admin web server (see configuration) forces the maximum file size. Thus, please check the current maximum. The default maximum is approx. 10 MB!</p>
|
||||
<div class="w-form">
|
||||
<form id="upload" name="upload" data-name="upload" method="post" action="/upload" enctype="multipart/form-data">
|
||||
<label for="file">Please select a file to upload:</label>
|
||||
<input class="w-input" id="file" type="file" name="file" data-name="file">
|
||||
<input class="w-button button optionbuttons" type="submit" value="Upload this file" data-wait="Please wait...">
|
||||
</form>
|
||||
<div class="w-form-done">
|
||||
<p>Thank you! Your submission has been received!</p>
|
||||
</div>
|
||||
<div class="w-form-fail">
|
||||
<p>Oops! Something went wrong while submitting the form</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/admin/js/webflow.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
`
|
@ -24,9 +24,12 @@ var Overview = `
|
||||
<div class="w-row">
|
||||
<div class="w-col w-col-4"><a class="button adminbutton" href="/log">Logging Viewer</a>
|
||||
</div>
|
||||
<div class="w-col w-col-4"></div>
|
||||
<div class="w-col w-col-4"></div>
|
||||
<div class="w-col w-col-4"><a class="button adminbutton" href="/upload">Upload a file</a>
|
||||
</div>
|
||||
<div class="w-col w-col-4"><a class="button adminbutton" href="/configuration">Configuration</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="admintextblock">The current Ocean's version is: {{.Version}}</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="/admin/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/admin/js/webflow.js"></script>
|
||||
|
@ -14,13 +14,21 @@ func checkConfiguration() {
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`DefaultLanguageCode`, `en-GB`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerBinding`, `127.0.0.1:60000`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerEnabled`, `True`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerPassword`, ``)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerUseTLS`, `False`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerTLSCertificateName`, `certificateAdmin.pem`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerTLSPrivateKey`, `privateKeyAdmin.pem`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerReadTimeoutSeconds`, `10`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerWriteTimeoutSeconds`, `10`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `1048576`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`AdminWebServerMaxHeaderLenBytes`, `10485760`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerPort`, `50000`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerHostname`, `www.my-site.domain`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerReadTimeoutSeconds`, `10`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerWriteTimeoutSeconds`, `10`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerMaxHeaderLenBytes`, `1048576`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerUseTLS`, `False`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerTLSCertificateName`, `certificatePublic.pem`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`PublicWebServerTLSPrivateKey`, `privateKeyPublic.pem`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`InternalCommPassword`, `please replace this with e.g. a random GUID, etc.`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBHost`, `localhost:27017`)
|
||||
CheckSingleConfigurationPresentsAndAddIfMissing(`CustomerDBDatabase`, `Ocean`)
|
||||
|
@ -14,7 +14,7 @@ func connectDatabase(config Meta.Configuration) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Printf("[Error] Was not able to connect to the configuration database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", err)
|
||||
os.Exit(0)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -22,7 +22,7 @@ func connectDatabase(config Meta.Configuration) {
|
||||
if newSession, errDial := mgo.Dial(config.ConfigDBHostname); errDial != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+config.ConfigDBHostname, errDial.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the configuration database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errDial.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
session = newSession
|
||||
}
|
||||
@ -34,7 +34,7 @@ func connectDatabase(config Meta.Configuration) {
|
||||
if errLogin := db.Login(config.ConfigDBConfigurationCollectionUsername, config.ConfigDBConfigurationCollectionPassword); errLogin != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+config.ConfigDBConfigurationCollectionUsername, errLogin.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the configuration database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errLogin.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// In case of write operations, wait for the majority of servers to be done:
|
||||
|
16
ConfigurationDB/ReadAll.go
Normal file
16
ConfigurationDB/ReadAll.go
Normal file
@ -0,0 +1,16 @@
|
||||
package ConfigurationDB
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// This function reads all configuration values e.g. for the admin's configuration web interface.
|
||||
func ReadAll() (values []ConfigurationDBEntry) {
|
||||
if errFind := collection.Find(bson.D{}).All(&values); errFind != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to read all configuration values out of the database.`, `Error while find.`, errFind.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
47
ConfigurationDB/UpdateValue.go
Normal file
47
ConfigurationDB/UpdateValue.go
Normal file
@ -0,0 +1,47 @@
|
||||
package ConfigurationDB
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// This function updates a configuration value e.g. from the admin's configuration web interface.
|
||||
func UpdateValue(name string, configuration ConfigurationDBEntry) {
|
||||
|
||||
// Check the configuration's name:
|
||||
if name == `` {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The given name was nil!`)
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure, that the configuration is already present:
|
||||
if count, errFind := collection.Find(bson.D{{"Name", name}}).Count(); errFind != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to update a configuration value.`, `Error while find the old value.`, errFind.Error())
|
||||
return
|
||||
} else {
|
||||
// Is the configuration already present?
|
||||
if count == 0 {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The desired configuration is not present.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure, that the configuration value also uses the same name:
|
||||
if name != configuration.Name {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, `Was not able to update a configuration value.`, `The given name was different with the name of the desired configuration value.`)
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Case: Any precondition is fulfilled
|
||||
//
|
||||
|
||||
// Selection of the correct configuration (the name is a unique value):
|
||||
selector := bson.D{{"Name", name}}
|
||||
if errUpdate := collection.Update(selector, configuration); errUpdate != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `Was not able to update a configuration value.`, `Error while updating the database.`, errUpdate.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -13,7 +13,7 @@ func connectDatabase(host, username, password, database string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Printf("[Error] Was not able to connect to the customer database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", err)
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -21,7 +21,7 @@ func connectDatabase(host, username, password, database string) {
|
||||
if newSession, errDial := mgo.Dial(host); errDial != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+host, errDial.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the customer database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errDial.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
} else {
|
||||
mainSession = newSession
|
||||
}
|
||||
@ -31,14 +31,14 @@ func connectDatabase(host, username, password, database string) {
|
||||
if db == nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the customer database.`)
|
||||
fmt.Printf("[Error] Was not able to connect to the customer database. Please read https://github.com/SommerEngineering/Ocean.\n")
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Login:
|
||||
if errLogin := db.Login(username, password); errLogin != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+databaseUsername, errLogin.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the customer database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errLogin.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// In case of write operations, wait for the majority of servers to be done:
|
||||
@ -53,7 +53,7 @@ func connectDatabase(host, username, password, database string) {
|
||||
if gridFS == nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `Was not able to get the GridFS from the database.`)
|
||||
fmt.Printf("[Error] Was not able to connect to the customer database. Please read https://github.com/SommerEngineering/Ocean.\n")
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Ensure the indexes for the GridFS:
|
||||
|
@ -2,9 +2,11 @@ package Handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Function to add a new public handler.
|
||||
@ -32,5 +34,5 @@ func AddAdminHandler(pattern string, handler func(http.ResponseWriter, *http.Req
|
||||
}()
|
||||
|
||||
// Add the handler:
|
||||
muxAdmin.HandleFunc(pattern, handler)
|
||||
muxAdmin.HandleFunc(pattern, BasicAuth(handler, `admin`, ConfigurationDB.Read(`AdminWebServerPassword`), `Please enter your username and password for this site`))
|
||||
}
|
||||
|
30
Handlers/BasicAuth.go
Normal file
30
Handlers/BasicAuth.go
Normal file
@ -0,0 +1,30 @@
|
||||
package Handlers
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// BasicAuth wraps a handler requiring HTTP basic auth for it using the given
|
||||
// username and password and the specified realm, which shouldn't contain quotes.
|
||||
//
|
||||
// Most web browser display a dialog with something like:
|
||||
//
|
||||
// The website says: "<realm>"
|
||||
//
|
||||
// Which is really stupid so you may want to set the realm to a message rather than
|
||||
// an actual realm.
|
||||
//
|
||||
// Taken from on http://stackoverflow.com/questions/21936332/idiomatic-way-of-requiring-http-basic-auth-in-go/39591234#39591234
|
||||
func BasicAuth(handler http.HandlerFunc, username, password, realm string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user, pass, ok := r.BasicAuth()
|
||||
if !ok || subtle.ConstantTimeCompare([]byte(user), []byte(username)) != 1 || subtle.ConstantTimeCompare([]byte(pass), []byte(password)) != 1 {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="`+realm+`"`)
|
||||
w.WriteHeader(401)
|
||||
w.Write([]byte(http.StatusText(401)))
|
||||
return
|
||||
}
|
||||
handler(w, r)
|
||||
}
|
||||
}
|
@ -7,43 +7,61 @@ import (
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Internal function for the timer logic thread.
|
||||
func cacheTimerLogic(waiting bool) {
|
||||
// Internal function for the timer-logic thread.
|
||||
func cacheTimerLogic() {
|
||||
|
||||
// Case: This server goes down now.
|
||||
if Shutdown.IsDown() {
|
||||
return
|
||||
}
|
||||
|
||||
// Define the query and get the iterator:
|
||||
lastCount := cacheListenerDatabase.Len()
|
||||
selection := bson.D{{`IsActive`, true}}
|
||||
entriesIterator := collectionListener.Find(selection).Iter()
|
||||
// Get the current counts:
|
||||
lastCountListener := cacheListenerDatabase.Len()
|
||||
lastCountHosts := cacheHostDatabase.Len()
|
||||
|
||||
entry := Scheme.Listener{}
|
||||
// Define the queries:
|
||||
selectionListeners := bson.D{{`IsActive`, true}}
|
||||
selectionHosts := bson.D{}
|
||||
|
||||
// Get the iterators:
|
||||
entriesIteratorListeners := collectionListener.Find(selectionListeners).Iter()
|
||||
entriesIteratorHosts := collectionHosts.Find(selectionHosts).Iter()
|
||||
|
||||
//
|
||||
// Execute the listeners first:
|
||||
//
|
||||
|
||||
entryListener := Scheme.Listener{}
|
||||
cacheListenerDatabaseLock.Lock()
|
||||
|
||||
// Re-init the cache:
|
||||
cacheListenerDatabase.Init()
|
||||
|
||||
// Loop over all entries
|
||||
for entriesIterator.Next(&entry) {
|
||||
cacheListenerDatabase.PushBack(entry)
|
||||
for entriesIteratorListeners.Next(&entryListener) {
|
||||
cacheListenerDatabase.PushBack(entryListener)
|
||||
}
|
||||
|
||||
cacheListenerDatabaseLock.Unlock()
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCount), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len()))
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The listener cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCountListener), fmt.Sprintf(`new count=%d`, cacheListenerDatabase.Len()))
|
||||
|
||||
// In case, that this function runs at a thread, we want to wait:
|
||||
if waiting {
|
||||
nextDuration := time.Duration(5) * time.Minute
|
||||
if cacheListenerDatabase.Len() == 0 {
|
||||
nextDuration = time.Duration(10) * time.Second
|
||||
//
|
||||
// Execute now the hosts:
|
||||
//
|
||||
|
||||
entryHost := Scheme.Host{}
|
||||
cacheHostDatabaseLock.Lock()
|
||||
|
||||
// Re-init the cache:
|
||||
cacheHostDatabase.Init()
|
||||
|
||||
// Loop over all entries
|
||||
for entriesIteratorHosts.Next(&entryHost) {
|
||||
cacheHostDatabase.PushBack(entryHost)
|
||||
}
|
||||
|
||||
time.Sleep(nextDuration)
|
||||
}
|
||||
cacheHostDatabaseLock.Unlock()
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameEXECUTE, `The host cache was refreshed with the values from the database.`, fmt.Sprintf(`last count=%d`, lastCountHosts), fmt.Sprintf(`new count=%d`, cacheHostDatabase.Len()))
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Function to convert the HTTP data back to a message.
|
||||
@ -49,37 +53,131 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman
|
||||
|
||||
// Use the order of the destination type's fields:
|
||||
for i := 0; i < element.NumField(); i++ {
|
||||
field := element.Field(i)
|
||||
switch field.Kind().String() {
|
||||
|
||||
// Read the current field:
|
||||
field := element.Field(i)
|
||||
|
||||
// Choose the right type for this field:
|
||||
switch field.Kind().String() {
|
||||
case `int64`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`int:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
v, _ := strconv.ParseInt(mapValue, 10, 64)
|
||||
field.SetInt(v)
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// The destination:
|
||||
var destination int64
|
||||
|
||||
// A reader for the bytes:
|
||||
buffer := bytes.NewReader(bytesArray)
|
||||
|
||||
// Try to decode the bytes to an instance of the type:
|
||||
errBinary := binary.Read(buffer, binary.LittleEndian, &destination)
|
||||
if errBinary != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error())
|
||||
} else {
|
||||
// Finnaly, store the value in the message:
|
||||
field.SetInt(destination)
|
||||
}
|
||||
}
|
||||
|
||||
case `string`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`str:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
field.SetString(mapValue)
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// Decode the bytes as string:
|
||||
text := string(bytesArray)
|
||||
|
||||
// Decode the URL encoded string:
|
||||
textFinal, errURL := url.QueryUnescape(text)
|
||||
if errURL != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode a URL encoded string.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errURL.Error())
|
||||
} else {
|
||||
field.SetString(textFinal)
|
||||
}
|
||||
}
|
||||
|
||||
case `float64`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`f64:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
v, _ := strconv.ParseFloat(mapValue, 64)
|
||||
field.SetFloat(v)
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// The destination:
|
||||
var destination float64
|
||||
|
||||
// A reader for the bytes:
|
||||
buffer := bytes.NewReader(bytesArray)
|
||||
|
||||
// Try to decode the bytes to an instance of the type:
|
||||
errBinary := binary.Read(buffer, binary.LittleEndian, &destination)
|
||||
if errBinary != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error())
|
||||
} else {
|
||||
// Finnaly, store the value in the message:
|
||||
field.SetFloat(destination)
|
||||
}
|
||||
}
|
||||
|
||||
case `bool`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`bool:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
v, _ := strconv.ParseBool(mapValue)
|
||||
field.SetBool(v)
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// Store the value:
|
||||
if bytesArray[0] == 0x1 {
|
||||
field.SetBool(true)
|
||||
} else {
|
||||
field.SetBool(false)
|
||||
}
|
||||
}
|
||||
|
||||
case `uint8`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`ui8:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
v, _ := strconv.ParseUint(mapValue, 16, 8)
|
||||
field.SetUint(v)
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// Store the value:
|
||||
field.SetUint(uint64(bytesArray[0]))
|
||||
}
|
||||
|
||||
// Case: Arrays...
|
||||
case `slice`:
|
||||
@ -87,64 +185,147 @@ func Data2Message(target interface{}, data map[string][]string) (channel, comman
|
||||
sliceKind := reflect.ValueOf(sliceInterface).Type().String()
|
||||
|
||||
switch sliceKind {
|
||||
case `[]uint8`: // bytes
|
||||
case `[]uint8`: // a byte array
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`ui8[]:%s`, elementType.Field(i).Name)
|
||||
mapValues := data[mapName]
|
||||
fieldLen := len(mapValues)
|
||||
fieldData := make([]uint8, fieldLen, fieldLen)
|
||||
for n, mapValue := range mapValues {
|
||||
v, _ := strconv.ParseUint(mapValue, 16, 8)
|
||||
fieldData[n] = byte(v)
|
||||
}
|
||||
|
||||
fieldDataValue := reflect.ValueOf(fieldData)
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// Store the values in the message:
|
||||
fieldDataValue := reflect.ValueOf(bytesArray)
|
||||
field.Set(fieldDataValue)
|
||||
}
|
||||
|
||||
case `[]int64`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`int[]:%s`, elementType.Field(i).Name)
|
||||
mapValues := data[mapName]
|
||||
fieldLen := len(mapValues)
|
||||
fieldData := make([]int64, fieldLen, fieldLen)
|
||||
for n, mapValue := range mapValues {
|
||||
v, _ := strconv.ParseInt(mapValue, 10, 64)
|
||||
fieldData[n] = v
|
||||
}
|
||||
|
||||
fieldDataValue := reflect.ValueOf(fieldData)
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// The destination:
|
||||
var destination []int64
|
||||
|
||||
// A reader for the bytes:
|
||||
buffer := bytes.NewReader(bytesArray)
|
||||
|
||||
// Try to decode the bytes to an instance of the type:
|
||||
errBinary := binary.Read(buffer, binary.LittleEndian, &destination)
|
||||
if errBinary != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error())
|
||||
} else {
|
||||
// Finnaly, store the value in the message:
|
||||
fieldDataValue := reflect.ValueOf(destination)
|
||||
field.Set(fieldDataValue)
|
||||
}
|
||||
}
|
||||
|
||||
case `[]bool`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`bool[]:%s`, elementType.Field(i).Name)
|
||||
mapValues := data[mapName]
|
||||
fieldLen := len(mapValues)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
fieldLen := len(bytesArray)
|
||||
fieldData := make([]bool, fieldLen, fieldLen)
|
||||
for n, mapValue := range mapValues {
|
||||
v, _ := strconv.ParseBool(mapValue)
|
||||
fieldData[n] = v
|
||||
|
||||
// Convert each byte in a bool:
|
||||
for n, value := range bytesArray {
|
||||
if value == 0x1 {
|
||||
fieldData[n] = true
|
||||
} else {
|
||||
fieldData[n] = false
|
||||
}
|
||||
}
|
||||
|
||||
// Store the values in the message:
|
||||
fieldDataValue := reflect.ValueOf(fieldData)
|
||||
field.Set(fieldDataValue)
|
||||
}
|
||||
|
||||
case `[]string`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`str[]:%s`, elementType.Field(i).Name)
|
||||
mapValues := data[mapName]
|
||||
fieldDataValue := reflect.ValueOf(mapValues)
|
||||
field.Set(fieldDataValue)
|
||||
|
||||
case `[]float64`:
|
||||
mapName := fmt.Sprintf(`f64[]:%s`, elementType.Field(i).Name)
|
||||
mapValues := data[mapName]
|
||||
fieldLen := len(mapValues)
|
||||
fieldData := make([]float64, fieldLen, fieldLen)
|
||||
for n, mapValue := range mapValues {
|
||||
v, _ := strconv.ParseFloat(mapValue, 64)
|
||||
fieldData[n] = v
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// Get the URL encoded string of all values:
|
||||
allStringsRAW := string(bytesArray)
|
||||
|
||||
// Split now the different strings:
|
||||
allStrings := strings.Split(allStringsRAW, "\n")
|
||||
|
||||
// A place where we store the final strings:
|
||||
data := make([]string, len(allStrings), len(allStrings))
|
||||
|
||||
// Loop over all URL encoded strings and decode it:
|
||||
for n, element := range allStrings {
|
||||
elementFinal, errURL := url.QueryUnescape(element)
|
||||
if errURL != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode a base64 string for a string array.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errURL.Error())
|
||||
} else {
|
||||
data[n] = elementFinal
|
||||
}
|
||||
}
|
||||
|
||||
fieldDataValue := reflect.ValueOf(fieldData)
|
||||
// Store the values in the message:
|
||||
fieldDataValue := reflect.ValueOf(data)
|
||||
field.Set(fieldDataValue)
|
||||
}
|
||||
|
||||
case `[]float64`:
|
||||
// The name of the field:
|
||||
mapName := fmt.Sprintf(`f64[]:%s`, elementType.Field(i).Name)
|
||||
|
||||
// The value of the field as string:
|
||||
mapValue := data[mapName][0]
|
||||
|
||||
// The value of the field as bytes:
|
||||
bytesArray, errBase64 := base64.StdEncoding.DecodeString(mapValue)
|
||||
if errBase64 != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the base64 data to an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBase64.Error())
|
||||
} else {
|
||||
// The destination:
|
||||
var destination []float64
|
||||
|
||||
// A reader for the bytes:
|
||||
buffer := bytes.NewReader(bytesArray)
|
||||
|
||||
// Try to decode the bytes to an instance of the type:
|
||||
errBinary := binary.Read(buffer, binary.LittleEndian, &destination)
|
||||
if errBinary != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `Was not able to decode the binary data to the type of the ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errBinary.Error())
|
||||
} else {
|
||||
// Finnaly, store the value in the message:
|
||||
fieldDataValue := reflect.ValueOf(destination)
|
||||
field.Set(fieldDataValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
33
ICCC/Doc.go
33
ICCC/Doc.go
@ -7,9 +7,9 @@ To be able to marshal / parse the data back to objects, some additional informat
|
||||
|
||||
Example 01:
|
||||
name=str:Surname
|
||||
value=Sommer
|
||||
value=U29tbWVy
|
||||
|
||||
The HTTP form name is 'str:Surname' and the value is 'Sommer'. The 'str' is the indicator for the data type, in this case it is a string.
|
||||
The HTTP form name is 'str:Surname' and the value is 'Sommer' as base64 encoded string. The 'str' is the indicator for the data type, in this case it is a string.
|
||||
|
||||
Known data types are:
|
||||
* str := string
|
||||
@ -23,28 +23,25 @@ Known data types are:
|
||||
* str[] := string array
|
||||
* f64[] := 64 bit float array
|
||||
|
||||
Formatting of the corresponding values (each value is a string => HTTP). Plase note:
|
||||
For the arrays, the name will repeated for each value.
|
||||
* str := the plain UTF8 string
|
||||
* int := the integer e.g. '13894612'
|
||||
* f64 := the float with nine digits e.g. 9.48 gets '9.480000000'
|
||||
* bool := 'true' or 'false' (lower case)
|
||||
* ui8 := the byte as hexadecimal string e.g. 255 gets 'ff'
|
||||
* ui8[] := the bytes as hexdecimal strings e.g. 0 255 0 gets ui8[]:name:00 ui8[]:name:ff ui8[]:name:00
|
||||
* int[] := 64 bit integer array e.g. 1 2 gets int[]:name:1 int[]:name:2
|
||||
* bool[] := a boolean array e.g. true true gets bool[]:name:true bool[]:name:true
|
||||
* str[] := string array e.g. 'a' 'abc' gets str[]:name:a str[]:name:abc
|
||||
* f64[] := 64 bit float array e.g. 1.1 1.2 gets f64[]:name:1.100000000 f64[]:name:1.2000000000
|
||||
Formatting of the corresponding values (each value is at the end a base64 string).
|
||||
* str := the plain UTF8 string as URL encoded. These bytes are getting base64 encoded.
|
||||
* int := the little endian representation of the int. These bytes are getting base64 encoded.
|
||||
* f64 := the little endian representation of the float. These bytes are getting base64 encoded.
|
||||
* bool := the byte 0x1 or 0x0 for true and false. These byte will be base64 encoded.
|
||||
* ui8 := These byte will be base64 encoded.
|
||||
* ui8[] := These bytes are getting base64 encoded.
|
||||
* int[] := the little endian representation of the integers. These bytes are getting base64 encoded.
|
||||
* bool[] := the bools are getting converted to bytes (0x1 or 0x0 for true and false). These bytes are getting base64 encoded.
|
||||
* str[] := each string will be URL encoded. Afterwards, join all strings by \n. These bytes are getting base64 encoded.
|
||||
* f64[] := the little endian representation of the floats. These bytes are getting base64 encoded.
|
||||
|
||||
The format of a message is:
|
||||
command=COMMAND
|
||||
channel=CHANNEL
|
||||
[any count of data tuples]
|
||||
InternalCommPassword=[configured communication password e.g. an UUID etc.]
|
||||
|
||||
If you want to build a distributed system across the Internet, please use e.g. SSH tunnels to keep things secret.
|
||||
checksum=[the SHA512 checksum of the message]
|
||||
|
||||
Constrains to the environment:
|
||||
The web server cannot reorder the fields of the request or response. The order of fields at the data object (message) must correspond with the order of fields inside the HTTP message. Therefore, a reorder is not possible at the moment.
|
||||
The three field names 'command', 'channel' and 'checksum' are reserved, thus, you cannot use these names.
|
||||
*/
|
||||
package ICCC
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// The HTTP handler for the local ICCC commands. Will used in case, that another server
|
||||
// want to utelise an command from this server.
|
||||
// The HTTP handler for the local ICCC listeners. Will used in case, that another server
|
||||
// want to utelise an listener from this server.
|
||||
func ICCCHandler(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
// Cannot parse the form?
|
||||
@ -23,9 +23,9 @@ func ICCCHandler(response http.ResponseWriter, request *http.Request) {
|
||||
// Read the data out of the request:
|
||||
messageData := map[string][]string(request.PostForm)
|
||||
|
||||
// The data must contain at least three fields (command, channel & communication password)
|
||||
// The data must contain at least three fields (command, channel & checksum)
|
||||
if len(messageData) < 3 {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `The ICCC message contains not enough data: At least the channel, command and password is required!`)
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameNETWORK, `The ICCC message contains not enough data: At least the channel, command and checksum is required!`)
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
@ -33,11 +33,17 @@ func ICCCHandler(response http.ResponseWriter, request *http.Request) {
|
||||
// Read the meta data:
|
||||
channel := messageData[`channel`][0]
|
||||
command := messageData[`command`][0]
|
||||
password := messageData[`InternalCommPassword`][0]
|
||||
receivedChecksum := messageData[`checksum`][0]
|
||||
|
||||
// Check the password:
|
||||
if password != Tools.InternalCommPassword() {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `Received a ICCC message with wrong password!`, request.RemoteAddr)
|
||||
// Remove the checksum as preparation for the re-hash:
|
||||
delete(messageData, `checksum`)
|
||||
|
||||
// Re-hash the received message:
|
||||
receivedMessageHash := signMessage(messageData).Get(`checksum`)
|
||||
|
||||
// Check the checksums:
|
||||
if receivedChecksum != receivedMessageHash {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityCritical, LM.ImpactNone, LM.MessageNamePASSWORD, `Received a ICCC message with wrong checksum!`, request.RemoteAddr, fmt.Sprintf("channel=%s", channel), fmt.Sprintf("command=%s", command))
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
@ -50,7 +56,7 @@ func ICCCHandler(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
if listener == nil {
|
||||
// Case: No such listener
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to find the correct listener for these ICCC message.`, `channel=`+channel, `command`+command, `hostname=`+Tools.ThisHostname())
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameCONFIGURATION, `Was not able to find the correct listener for these ICCC message.`, `channel=`+channel, `command=`+command, `hostname=`+Tools.ThisHostname())
|
||||
http.NotFound(response, request)
|
||||
} else {
|
||||
// Case: Everything is fine => deliver the message and read the answer:
|
||||
|
56
ICCC/ICCCDeleteHost.go
Normal file
56
ICCC/ICCCDeleteHost.go
Normal file
@ -0,0 +1,56 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that deletes a host.
|
||||
func ICCCDeleteHostReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC delete host message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCDeleteHost{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// Convert the object:
|
||||
messageData := obj.(SystemMessages.ICCCDeleteHost)
|
||||
|
||||
// The database selection:
|
||||
selectionDelete := bson.D{{`Hostname`, messageData.Hostname}, {`IPAddressPort`, messageData.IPAddressPort}}
|
||||
|
||||
// Delete the entry:
|
||||
if errDelete := collectionHosts.Remove(selectionDelete); errDelete != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC delete host message.", errDelete.Error(), fmt.Sprintf("hostname='%s'", messageData.Hostname), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerNACK)
|
||||
} else {
|
||||
//
|
||||
// Case: No error
|
||||
//
|
||||
|
||||
// Update the cache as soon as possible:
|
||||
InitCacheNow()
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, "An ICCC host was deleted.", fmt.Sprintf("hostname='%s'", messageData.Hostname), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerACK)
|
||||
}
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
56
ICCC/ICCCDeleteListenerReceiver.go
Normal file
56
ICCC/ICCCDeleteListenerReceiver.go
Normal file
@ -0,0 +1,56 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that deletes an listener.
|
||||
func ICCCDeleteListenerReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC delete listener message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCDeleteListener{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// Convert the object:
|
||||
messageData := obj.(SystemMessages.ICCCDeleteListener)
|
||||
|
||||
// The database selection:
|
||||
selectionDelete := bson.D{{`Channel`, messageData.Channel}, {`Command`, messageData.Command}, {`IPAddressPort`, messageData.IPAddressPort}}
|
||||
|
||||
// Delete the entry:
|
||||
if errDelete := collectionListener.Remove(selectionDelete); errDelete != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC delete listener message.", errDelete.Error(), fmt.Sprintf("channel='%s'", messageData.Channel), fmt.Sprintf("command='%s'", messageData.Command), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerNACK)
|
||||
} else {
|
||||
//
|
||||
// Case: No error
|
||||
//
|
||||
|
||||
// Update the cache as soon as possible:
|
||||
InitCacheNow()
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, "An ICCC listener was deleted.", fmt.Sprintf("channel='%s'", messageData.Channel), fmt.Sprintf("command='%s'", messageData.Command), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerACK)
|
||||
}
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
62
ICCC/ICCCGetHostsReceiver.go
Normal file
62
ICCC/ICCCGetHostsReceiver.go
Normal file
@ -0,0 +1,62 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC/Scheme"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that yields the hosts.
|
||||
func ICCCGetHostsReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC get hosts message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCGetHosts{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// We have to read from the cache:
|
||||
cacheHostDatabaseLock.RLock()
|
||||
|
||||
// How many hosts we currently known?
|
||||
countHosts := cacheHostDatabase.Len()
|
||||
|
||||
// Prepare the answer object:
|
||||
answerMessage := SystemMessages.ICCCGetHostsAnswer{}
|
||||
answerMessage.Hostnames = make([]string, countHosts, countHosts)
|
||||
answerMessage.IPAddressesPorts = make([]string, countHosts, countHosts)
|
||||
answerMessage.Kinds = make([]byte, countHosts, countHosts)
|
||||
|
||||
// Loop over all hosts which are currently available at the cache:
|
||||
n := 0
|
||||
for entry := cacheHostDatabase.Front(); entry != nil; entry = entry.Next() {
|
||||
host := entry.Value.(Scheme.Host)
|
||||
answerMessage.Hostnames[n] = host.Hostname
|
||||
answerMessage.IPAddressesPorts[n] = host.IPAddressPort
|
||||
answerMessage.Kinds[n] = host.Kind
|
||||
n++
|
||||
}
|
||||
|
||||
// Unlock the cache:
|
||||
cacheHostDatabaseLock.RUnlock()
|
||||
|
||||
// Send the answer:
|
||||
return Message2Data(``, ``, answerMessage)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
64
ICCC/ICCCGetListenersReceiver.go
Normal file
64
ICCC/ICCCGetListenersReceiver.go
Normal file
@ -0,0 +1,64 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC/Scheme"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that yields the listeners.
|
||||
func ICCCGetListenersReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC get listeners message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCGetListeners{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// We have to read from the cache:
|
||||
cacheListenerDatabaseLock.RLock()
|
||||
|
||||
// How many listeners we currently known?
|
||||
countListeners := cacheListenerDatabase.Len()
|
||||
|
||||
// Prepare the answer object:
|
||||
answerMessage := SystemMessages.ICCCGetListenersAnswer{}
|
||||
answerMessage.Channels = make([]string, countListeners, countListeners)
|
||||
answerMessage.Commands = make([]string, countListeners, countListeners)
|
||||
answerMessage.IPAddressesPorts = make([]string, countListeners, countListeners)
|
||||
answerMessage.Kinds = make([]byte, countListeners, countListeners)
|
||||
|
||||
// Loop over all hosts which are currently available at the cache:
|
||||
n := 0
|
||||
for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() {
|
||||
listener := entry.Value.(Scheme.Listener)
|
||||
answerMessage.Channels[n] = listener.Channel
|
||||
answerMessage.Commands[n] = listener.Command
|
||||
answerMessage.IPAddressesPorts[n] = listener.IPAddressPort
|
||||
answerMessage.Kinds[n] = listener.Kind
|
||||
n++
|
||||
}
|
||||
|
||||
// Unlock the cache:
|
||||
cacheListenerDatabaseLock.RUnlock()
|
||||
|
||||
// Send the answer:
|
||||
return Message2Data(``, ``, answerMessage)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
44
ICCC/ICCCGetVersionReceiver.go
Normal file
44
ICCC/ICCCGetVersionReceiver.go
Normal file
@ -0,0 +1,44 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/System/Version"
|
||||
"github.com/SommerEngineering/Ocean/Tools"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC version message.
|
||||
func ICCCGetVersionReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC get version message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCGetVersion{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// Prepare the answer:
|
||||
answer := SystemMessages.ICCCGetVersionAnswer{}
|
||||
answer.Kind = KindOCEAN
|
||||
answer.Name = Tools.ThisHostname()
|
||||
answer.Version = Version.GetVersion()
|
||||
|
||||
// An answer is necessary:
|
||||
return Message2Data("", "", answer)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to convert the ping message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
65
ICCC/ICCCListenerUpdateReceiver.go
Normal file
65
ICCC/ICCCListenerUpdateReceiver.go
Normal file
@ -0,0 +1,65 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/Scheme"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that updates an listener.
|
||||
func ICCCListenerUpdateReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC update listener message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCListenerUpdate{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
|
||||
// Convert the object:
|
||||
messageData := obj.(SystemMessages.ICCCListenerUpdate)
|
||||
|
||||
// The database selection:
|
||||
selectionUpdate := bson.D{{`Channel`, messageData.Channel}, {`Command`, messageData.Command}, {`IPAddressPort`, messageData.IPAddressPort}}
|
||||
|
||||
// The object with holds the new state:
|
||||
updatedObject := Scheme.Listener{}
|
||||
updatedObject.Channel = messageData.Channel
|
||||
updatedObject.Command = messageData.Command
|
||||
updatedObject.IPAddressPort = messageData.IPAddressPort
|
||||
updatedObject.IsActive = messageData.IsActiveNew
|
||||
updatedObject.Kind = messageData.Kind
|
||||
|
||||
// Update the entry:
|
||||
if errUpdate := collectionListener.Update(selectionUpdate, updatedObject); errUpdate != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC update listener message.", errUpdate.Error(), fmt.Sprintf("channel='%s'", messageData.Channel), fmt.Sprintf("command='%s'", messageData.Command), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerNACK)
|
||||
} else {
|
||||
//
|
||||
// Case: No error
|
||||
//
|
||||
|
||||
// Update the cache as soon as possible:
|
||||
InitCacheNow()
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameSTATE, "An ICCC listener was updated.", fmt.Sprintf("channel='%s'", messageData.Channel), fmt.Sprintf("command='%s'", messageData.Command), fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort), fmt.Sprintf("isActive=%v", messageData.IsActiveNew))
|
||||
return Message2Data(``, ``, SystemMessages.AnswerACK)
|
||||
}
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
35
ICCC/ICCCPingReceiver.go
Normal file
35
ICCC/ICCCPingReceiver.go
Normal file
@ -0,0 +1,35 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC ping message.
|
||||
func ICCCPingReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC ping message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := Data2Message(SystemMessages.ICCCPing{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
// An answer is necessary:
|
||||
return Message2Data("", "", SystemMessages.AnswerACK)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to convert the ping message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that registers a host.
|
||||
func ICCCRegisterHost(data map[string][]string) (result map[string][]string) {
|
||||
func ICCCRegisterHostReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, fmt.Sprintf("Was not able to execute the ICCC register host message. %s", err))
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC register host message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
@ -28,10 +28,13 @@ func ICCCRegisterHost(data map[string][]string) (result map[string][]string) {
|
||||
messageData := obj.(SystemMessages.ICCCRegisterHost)
|
||||
|
||||
// Provide a log entry:
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Should register another host.`, messageData.Hostname, messageData.IPAddressPort)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Register another host.`, fmt.Sprintf("hostname=%s", messageData.Hostname), fmt.Sprintf("ipAddressPort=%s", messageData.IPAddressPort))
|
||||
|
||||
// Execute the command:
|
||||
registerHost2Database(messageData.Hostname, messageData.IPAddressPort)
|
||||
registerHost2Database(messageData.Hostname, messageData.IPAddressPort, messageData.Kind)
|
||||
|
||||
// Update the caches:
|
||||
InitCacheNow()
|
||||
|
||||
// An answer is necessary:
|
||||
return Message2Data(``, ``, SystemMessages.AnswerACK)
|
@ -7,13 +7,13 @@ import (
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that registers a command.
|
||||
func ICCCRegisterCommand(data map[string][]string) (result map[string][]string) {
|
||||
// The receiver function for the ICCC message, that registers a listener.
|
||||
func ICCCRegisterListenerReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, fmt.Sprintf("Was not able to execute the ICCC register command message. %s", err))
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC register listener message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
@ -28,10 +28,13 @@ func ICCCRegisterCommand(data map[string][]string) (result map[string][]string)
|
||||
messageData := obj.(SystemMessages.ICCCRegisterListener)
|
||||
|
||||
// Provide a log entry:
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Should register another command.`, `channel=`+messageData.Channel, `command=`+messageData.Command, `IPAddressPort=`+messageData.IPAddressPort, fmt.Sprintf(`isActive=%v`, messageData.IsActive))
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Should register another listener.`, `channel=`+messageData.Channel, `command=`+messageData.Command, `IPAddressPort=`+messageData.IPAddressPort, fmt.Sprintf(`isActive=%v`, messageData.IsActive))
|
||||
|
||||
// Execute the command:
|
||||
registerCommand2Database(messageData.Channel, messageData.Command, messageData.IPAddressPort, messageData.IsActive)
|
||||
registerListener2Database(messageData.Channel, messageData.Command, messageData.IPAddressPort, messageData.IsActive, messageData.Kind)
|
||||
|
||||
// Update the caches:
|
||||
InitCacheNow()
|
||||
|
||||
// An answer is necessary:
|
||||
return Message2Data(``, ``, SystemMessages.AnswerACK)
|
12
ICCC/Init.go
12
ICCC/Init.go
@ -2,9 +2,11 @@ package ICCC
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/Tools"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Init this package.
|
||||
@ -12,8 +14,9 @@ func init() {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Start init of ICCC.`)
|
||||
defer Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameINIT, `Done init ICCC.`)
|
||||
|
||||
// Create the list as cache for all global listener (not only listener from this server):
|
||||
// Create the list as cache for all global listener and hosts (not only listener from this server):
|
||||
cacheListenerDatabase = list.New()
|
||||
cacheHostDatabase = list.New()
|
||||
|
||||
// Create a mapping as cache for all local listener end-points (functions):
|
||||
listeners = make(map[string]func(data map[string][]string) map[string][]string)
|
||||
@ -21,6 +24,13 @@ func init() {
|
||||
// Using the local IP address:
|
||||
correctAddressWithPort = Tools.LocalIPAddressAndPort()
|
||||
|
||||
// Determine the correct protocol:
|
||||
if publicTLSEnabled := ConfigurationDB.Read(`PublicWebServerUseTLS`); strings.ToLower(publicTLSEnabled) == `true` {
|
||||
activeProtocol = "https://"
|
||||
} else {
|
||||
activeProtocol = "http://"
|
||||
}
|
||||
|
||||
// Init the database:
|
||||
initDB()
|
||||
|
||||
|
@ -1,13 +1,6 @@
|
||||
package ICCC
|
||||
|
||||
// Starts the timer cache once and exit it after (no thread, no endless loop).
|
||||
// Starts the timer cache logic once and return after (no thread, no endless loop)
|
||||
func InitCacheNow() {
|
||||
startCacheTimerLock.Lock()
|
||||
defer startCacheTimerLock.Unlock()
|
||||
|
||||
if cacheTimerRunning {
|
||||
return
|
||||
}
|
||||
|
||||
cacheTimerLogic(false)
|
||||
cacheTimerLogic()
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Setup and starts the cache timer.
|
||||
func initCacheTimer() {
|
||||
startCacheTimerLock.Lock()
|
||||
@ -11,11 +15,15 @@ func initCacheTimer() {
|
||||
cacheTimerRunning = true
|
||||
}
|
||||
|
||||
// Start another thread with the timer logic:
|
||||
// Start another thread with the timer-logic:
|
||||
go func() {
|
||||
// Endless loop:
|
||||
for {
|
||||
cacheTimerLogic(true)
|
||||
// Execute the logic:
|
||||
cacheTimerLogic()
|
||||
|
||||
// Wait five minutes:
|
||||
time.Sleep(time.Duration(5) * time.Minute)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ func initDB() {
|
||||
//
|
||||
// Take care about the indexes for ICCCListener:
|
||||
//
|
||||
collectionListener.EnsureIndexKey(`Kind`)
|
||||
collectionListener.EnsureIndexKey(`Command`)
|
||||
collectionListener.EnsureIndexKey(`Command`, `IsActive`)
|
||||
|
||||
@ -52,15 +53,12 @@ func initDB() {
|
||||
//
|
||||
// Index for hosts:
|
||||
//
|
||||
collectionHosts.EnsureIndexKey(`Hostname`, `IPAddressPort`)
|
||||
collectionHosts.EnsureIndexKey(`Kind`)
|
||||
collectionHosts.EnsureIndexKey(`Hostname`)
|
||||
collectionHosts.EnsureIndexKey(`IPAddressPort`)
|
||||
|
||||
indexName2 := mgo.Index{}
|
||||
indexName2.Key = []string{`Hostname`}
|
||||
indexName2.Key = []string{`Hostname`, `IPAddressPort`}
|
||||
indexName2.Unique = true
|
||||
collectionHosts.EnsureIndex(indexName2)
|
||||
|
||||
indexName3 := mgo.Index{}
|
||||
indexName3.Key = []string{`IPAddressPort`}
|
||||
indexName3.Unique = true
|
||||
collectionHosts.EnsureIndex(indexName3)
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Function to convert an ICCC message to HTTP data.
|
||||
@ -39,74 +44,133 @@ func Message2Data(channel, command string, message interface{}) (data map[string
|
||||
field := element.Field(i)
|
||||
keyName := elementType.Field(i).Name
|
||||
|
||||
// A buffer for the binary representation:
|
||||
buffer := new(bytes.Buffer)
|
||||
|
||||
// For possible errors:
|
||||
var errConverter error = nil
|
||||
|
||||
// The key for this element:
|
||||
key := ``
|
||||
|
||||
// Look for the right data type:
|
||||
switch field.Kind().String() {
|
||||
|
||||
case `int64`:
|
||||
key := fmt.Sprintf(`int:%s`, keyName)
|
||||
data[key] = []string{strconv.FormatInt(field.Int(), 10)}
|
||||
key = fmt.Sprintf(`int:%s`, keyName)
|
||||
|
||||
// Converts the value in a byte array:
|
||||
errConverter = binary.Write(buffer, binary.LittleEndian, field.Int())
|
||||
|
||||
case `string`:
|
||||
key := fmt.Sprintf(`str:%s`, keyName)
|
||||
data[key] = []string{field.String()}
|
||||
key = fmt.Sprintf(`str:%s`, keyName)
|
||||
|
||||
// URL encode the string and copy its bytes to the buffer:
|
||||
io.Copy(buffer, strings.NewReader(url.QueryEscape(field.String())))
|
||||
|
||||
case `float64`:
|
||||
key := fmt.Sprintf(`f64:%s`, keyName)
|
||||
data[key] = []string{strconv.FormatFloat(field.Float(), 'f', 9, 64)}
|
||||
key = fmt.Sprintf(`f64:%s`, keyName)
|
||||
|
||||
// Converts the value in a byte array:
|
||||
errConverter = binary.Write(buffer, binary.LittleEndian, field.Float())
|
||||
|
||||
case `bool`:
|
||||
key := fmt.Sprintf(`bool:%s`, keyName)
|
||||
data[key] = []string{strconv.FormatBool(field.Bool())}
|
||||
key = fmt.Sprintf(`bool:%s`, keyName)
|
||||
|
||||
case `uint8`: // byte
|
||||
key := fmt.Sprintf(`ui8:%s`, keyName)
|
||||
data[key] = []string{strconv.FormatUint(field.Uint(), 16)}
|
||||
// Directly convert the bool in a byte:
|
||||
if field.Bool() {
|
||||
// Case: True
|
||||
buffer.WriteByte(0x1) // Write 1
|
||||
} else {
|
||||
// Case: False
|
||||
buffer.WriteByte(0x0) // Write 0
|
||||
}
|
||||
|
||||
case `uint8`: // a byte
|
||||
key = fmt.Sprintf(`ui8:%s`, keyName)
|
||||
|
||||
// uint8 is a byte, thus, write it directly in the buffer:
|
||||
buffer.WriteByte(byte(field.Uint()))
|
||||
|
||||
// Case: Arrays...
|
||||
case `slice`:
|
||||
sliceLen := field.Len()
|
||||
if sliceLen > 0 {
|
||||
|
||||
// Which kind of data is this?
|
||||
sliceKind := field.Index(0).Kind()
|
||||
key := ``
|
||||
dataValues := make([]string, sliceLen, sliceLen)
|
||||
|
||||
// Select the right data type:
|
||||
switch sliceKind.String() {
|
||||
case `uint8`: // bytes
|
||||
case `uint8`: // a byte array
|
||||
key = fmt.Sprintf(`ui8[]:%s`, keyName)
|
||||
values := field.Interface().([]uint8)
|
||||
for index, value := range values {
|
||||
dataValues[index] = strconv.FormatUint(uint64(value), 16)
|
||||
|
||||
// Directly write the bytes in the buffer:
|
||||
for _, val := range values {
|
||||
buffer.WriteByte(byte(val))
|
||||
}
|
||||
|
||||
case `int64`:
|
||||
key = fmt.Sprintf(`int[]:%s`, keyName)
|
||||
values := field.Interface().([]int64)
|
||||
for index, value := range values {
|
||||
dataValues[index] = strconv.FormatInt(value, 10)
|
||||
}
|
||||
|
||||
// Converts the array in a byte array:
|
||||
errConverter = binary.Write(buffer, binary.LittleEndian, values)
|
||||
|
||||
case `bool`:
|
||||
key = fmt.Sprintf(`bool[]:%s`, keyName)
|
||||
values := field.Interface().([]bool)
|
||||
for index, value := range values {
|
||||
dataValues[index] = strconv.FormatBool(value)
|
||||
|
||||
// Cannot convert bool to bytes by using binary.Write(). Thus,
|
||||
// convert it by ower own:
|
||||
|
||||
// Loop over all values:
|
||||
for _, val := range values {
|
||||
if val {
|
||||
// If the value is true:
|
||||
buffer.WriteByte(0x1) // Write 1
|
||||
} else {
|
||||
// If the value is false:
|
||||
buffer.WriteByte(0x0) // Write 0
|
||||
}
|
||||
}
|
||||
|
||||
case `string`:
|
||||
key = fmt.Sprintf(`str[]:%s`, keyName)
|
||||
values := field.Interface().([]string)
|
||||
for index, value := range values {
|
||||
dataValues[index] = value
|
||||
|
||||
// Mask every string by using a URL encoding.
|
||||
// This masks e.g. every new-line i.e \n, etc.
|
||||
// This allows us to combine later the strings by
|
||||
// using \n:
|
||||
masked := make([]string, len(values))
|
||||
|
||||
// Loop over each string and convert it:
|
||||
for n, val := range values {
|
||||
masked[n] = url.QueryEscape(val)
|
||||
}
|
||||
|
||||
// Join all masked strings by using \n and copy the byte array
|
||||
// representation in the buffer:
|
||||
io.Copy(buffer, strings.NewReader(strings.Join(masked, "\n")))
|
||||
|
||||
case `float64`:
|
||||
key = fmt.Sprintf(`f64[]:%s`, keyName)
|
||||
values := field.Interface().([]float64)
|
||||
for index, value := range values {
|
||||
dataValues[index] = strconv.FormatFloat(value, 'f', 9, 64)
|
||||
|
||||
// Converts the array in a byte array:
|
||||
errConverter = binary.Write(buffer, binary.LittleEndian, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data[key] = dataValues
|
||||
}
|
||||
if errConverter != nil {
|
||||
// An error occurs:
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, `It was not possible to convert an array for an ICCC message.`, fmt.Sprintf("channel='%s'", channel), fmt.Sprintf("command='%s'", command), errConverter.Error())
|
||||
} else {
|
||||
// Convert the byte array to a base64 string for the transportation on wire:
|
||||
data[key] = []string{base64.StdEncoding.EncodeToString(buffer.Bytes())}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,13 @@ import (
|
||||
)
|
||||
|
||||
// Function to register a server to the ICCC.
|
||||
func registerHost2Database(hostname, ipAddressPort string) {
|
||||
func registerHost2Database(hostname, ipAddressPort string, kind byte) {
|
||||
|
||||
// Create the host entry:
|
||||
host := Scheme.Host{}
|
||||
host.Hostname = hostname
|
||||
host.IPAddressPort = ipAddressPort
|
||||
host.Kind = kind
|
||||
|
||||
// The query to find already existing entries:
|
||||
selection := bson.D{{`Hostname`, host.Hostname}, {`IPAddressPort`, host.IPAddressPort}}
|
||||
|
@ -2,43 +2,46 @@ package ICCC
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/Scheme"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
// The internal function to register a command to ICCC.
|
||||
func registerCommand2Database(channel, command, ipAddressPort string, isActive bool) {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register this ICCC command in to the database.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, fmt.Sprintf("isActive=%v", isActive))
|
||||
// The internal function to register an listener to ICCC.
|
||||
func registerListener2Database(channel, command, ipAddressPort string, isActive bool, kind byte) {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Register this ICCC command in to the database.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, fmt.Sprintf("isActive=%v", isActive), `Hostname=`+ConfigurationDB.Read(`PublicWebServerHostname`))
|
||||
|
||||
entry := Scheme.Listener{}
|
||||
entry.Channel = channel
|
||||
entry.Command = command
|
||||
entry.IsActive = isActive
|
||||
entry.IPAddressPort = ipAddressPort
|
||||
entry.Kind = kind
|
||||
entry.Hostname = ConfigurationDB.Read(`PublicWebServerHostname`)
|
||||
|
||||
//
|
||||
// Case: Exists?
|
||||
//
|
||||
selection := bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, ipAddressPort}}
|
||||
selection := bson.D{{`Channel`, channel}, {`Command`, command}, {`IPAddressPort`, ipAddressPort}, {`Hostname`, entry.Hostname}}
|
||||
count1, _ := collectionListener.Find(selection).Count()
|
||||
if count1 == 1 {
|
||||
//
|
||||
// Case: Exist but maybe not active
|
||||
//
|
||||
collectionListener.Update(selection, entry)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Updating the existing ICCC command.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `Updating the existing ICCC command.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, `Hostname=`+ConfigurationDB.Read(`PublicWebServerHostname`))
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Case: Not exist
|
||||
//
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `This ICCC command is not known.`, `Create now a new entry!`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort)
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelWARN, LM.SeverityCritical, LM.ImpactNone, LM.MessageNameCONFIGURATION, `This ICCC command is not known.`, `Create now a new entry!`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, `Hostname=`+ConfigurationDB.Read(`PublicWebServerHostname`))
|
||||
if err := collectionListener.Insert(entry); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `It was not possible to add this ICCC command!`, err.Error(), `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort)
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactCritical, LM.MessageNameDATABASE, `It was not possible to add this ICCC command!`, err.Error(), `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, `Hostname=`+ConfigurationDB.Read(`PublicWebServerHostname`))
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This ICCC command is now known and active.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, `This ICCC command is now known and active.`, `channel=`+channel, `command=`+command, `IPAddressPort=`+ipAddressPort, `Hostname=`+ConfigurationDB.Read(`PublicWebServerHostname`))
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package ICCC
|
||||
|
||||
// The internal function to register a local command to ICCC.
|
||||
func registerLocalCommand2Database(channel, command string) {
|
||||
/*
|
||||
Cannot use here the ICCC command to register this command.
|
||||
Because, this host is maybe the first one. In that case,
|
||||
there would be no server which can execute the ICCC command.
|
||||
Therefore, every Ocean server registers the own commans directly.
|
||||
*/
|
||||
registerCommand2Database(channel, command, correctAddressWithPort, true)
|
||||
}
|
12
ICCC/RegisterLocalListener2Database.go
Normal file
12
ICCC/RegisterLocalListener2Database.go
Normal file
@ -0,0 +1,12 @@
|
||||
package ICCC
|
||||
|
||||
// The internal function to register a local listener to ICCC.
|
||||
func registerLocalListener2Database(channel, command string) {
|
||||
/*
|
||||
Cannot use here the ICCC command to register this listener.
|
||||
Because, this host is maybe the first one. In that case,
|
||||
there would be no server which can execute the ICCC command.
|
||||
Therefore, every Ocean server registers the own listeners directly.
|
||||
*/
|
||||
registerListener2Database(channel, command, correctAddressWithPort, true, KindOCEAN)
|
||||
}
|
@ -12,5 +12,5 @@ func registerThisHost2Database() {
|
||||
there would be no server which can execute the ICCC command.
|
||||
Therefore, every Ocean server registers the own host directly.
|
||||
*/
|
||||
registerHost2Database(Tools.ThisHostname(), correctAddressWithPort)
|
||||
registerHost2Database(Tools.ThisHostname(), correctAddressWithPort, KindOCEAN)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func Registrar(channel, command string, callback func(data map[string][]string)
|
||||
defer listenersLock.Unlock()
|
||||
|
||||
// Write the command to the database:
|
||||
registerLocalCommand2Database(channel, command)
|
||||
registerLocalListener2Database(channel, command)
|
||||
|
||||
// Register the command at the local cache:
|
||||
listeners[fmt.Sprintf(`%s::%s`, channel, command)] = callback
|
||||
|
@ -4,4 +4,5 @@ package Scheme
|
||||
type Host struct {
|
||||
Hostname string `bson:"Hostname"`
|
||||
IPAddressPort string `bson:"IPAddressPort"`
|
||||
Kind byte `bson:"Kind"`
|
||||
}
|
||||
|
@ -6,4 +6,6 @@ type Listener struct {
|
||||
Command string `bson:"Command"`
|
||||
IsActive bool `bson:"IsActive"`
|
||||
IPAddressPort string `bson:"IPAddressPort"`
|
||||
Kind byte `bson:"Kind"`
|
||||
Hostname string `bson:"Hostname"`
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC/Scheme"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/Tools"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -12,14 +11,12 @@ import (
|
||||
|
||||
// Send a message to a listener.
|
||||
func sendMessage(listener Scheme.Listener, data map[string][]string) (result map[string][]string) {
|
||||
// Convert the data and encode it:
|
||||
valuesHTTP := url.Values(data)
|
||||
|
||||
// Add the communication password:
|
||||
valuesHTTP.Add(`InternalCommPassword`, Tools.InternalCommPassword())
|
||||
// Lets sign the data:
|
||||
valuesHTTP := signMessage(data)
|
||||
|
||||
// Try to deliver the message:
|
||||
if response, err := http.PostForm(`http://`+listener.IPAddressPort+`/ICCC`, valuesHTTP); err != nil {
|
||||
if response, err := http.PostForm(activeProtocol+listener.Hostname+`/ICCC`, valuesHTTP); err != nil {
|
||||
// Case: Was not possible to deliver.
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityCritical, LM.ImpactUnknown, LM.MessageNameNETWORK, `Was not able to send the ICCC message.`, err.Error())
|
||||
} else {
|
||||
|
@ -35,5 +35,5 @@ func (a ShutdownFunction) Shutdown() {
|
||||
// Disconnect the database:
|
||||
db.Logout()
|
||||
dbSession.Close()
|
||||
Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Done shutting down now all ICCC listener for this host.`)
|
||||
Log.LogShort(senderName, LM.CategoryAPP, LM.LevelWARN, LM.MessageNameSHUTDOWN, `Done shutting down all ICCC listener for this host.`)
|
||||
}
|
||||
|
48
ICCC/SignMessage.go
Normal file
48
ICCC/SignMessage.go
Normal file
@ -0,0 +1,48 @@
|
||||
package ICCC
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/Tools"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Sign a message to secure it.
|
||||
func signMessage(data map[string][]string) (result url.Values) {
|
||||
// Create the hash generator:
|
||||
hash := sha512.New()
|
||||
|
||||
// To the start, we hash the password:
|
||||
fmt.Fprintf(hash, "%s", Tools.InternalCommPassword())
|
||||
|
||||
// Because the order of a map is random, we have to sort
|
||||
// the keys beforehand. Next, we can use the ordered keys
|
||||
// to access the data:
|
||||
keys := []string{}
|
||||
|
||||
// Get all keys:
|
||||
for key := range data {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
// Sort the keys:
|
||||
sort.Strings(keys)
|
||||
|
||||
// Now, loop over all the data:
|
||||
for _, key := range keys {
|
||||
// Get the value:
|
||||
value := data[key][0]
|
||||
|
||||
// Hash each key and value:
|
||||
fmt.Fprintf(hash, "key=%s :: value=%s\n", key, value)
|
||||
}
|
||||
|
||||
// Create the result:
|
||||
result = url.Values(data)
|
||||
|
||||
// Append the sign:
|
||||
result.Add(`checksum`, strings.ToUpper(fmt.Sprintf("%x", hash.Sum(nil))))
|
||||
return
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package SystemMessages
|
||||
|
||||
// The type for any answer, which can be extended by using CommandData.
|
||||
type DefaultAnswer struct {
|
||||
type ICCCDefaultAnswer struct {
|
||||
CommandSuccessful bool
|
||||
CommandAnswer int64
|
||||
}
|
7
ICCC/SystemMessages/ICCCDeleteHost.go
Normal file
7
ICCC/SystemMessages/ICCCDeleteHost.go
Normal file
@ -0,0 +1,7 @@
|
||||
package SystemMessages
|
||||
|
||||
// The message to delete a host from ICCC.
|
||||
type ICCCDeleteHost struct {
|
||||
Hostname string
|
||||
IPAddressPort string
|
||||
}
|
8
ICCC/SystemMessages/ICCCDeleteListener.go
Normal file
8
ICCC/SystemMessages/ICCCDeleteListener.go
Normal file
@ -0,0 +1,8 @@
|
||||
package SystemMessages
|
||||
|
||||
// The message to delete an listener from ICCC.
|
||||
type ICCCDeleteListener struct {
|
||||
Channel string
|
||||
Command string
|
||||
IPAddressPort string
|
||||
}
|
12
ICCC/SystemMessages/ICCCGetHosts.go
Normal file
12
ICCC/SystemMessages/ICCCGetHosts.go
Normal file
@ -0,0 +1,12 @@
|
||||
package SystemMessages
|
||||
|
||||
// A message to request the known ICCC hosts.
|
||||
type ICCCGetHosts struct {
|
||||
}
|
||||
|
||||
// The answer to the hosts request.
|
||||
type ICCCGetHostsAnswer struct {
|
||||
Hostnames []string
|
||||
IPAddressesPorts []string
|
||||
Kinds []byte
|
||||
}
|
13
ICCC/SystemMessages/ICCCGetListeners.go
Normal file
13
ICCC/SystemMessages/ICCCGetListeners.go
Normal file
@ -0,0 +1,13 @@
|
||||
package SystemMessages
|
||||
|
||||
// A message to request the known ICCC listeners.
|
||||
type ICCCGetListeners struct {
|
||||
}
|
||||
|
||||
// The answer to the listeners request.
|
||||
type ICCCGetListenersAnswer struct {
|
||||
Channels []string
|
||||
Commands []string
|
||||
IPAddressesPorts []string
|
||||
Kinds []byte
|
||||
}
|
12
ICCC/SystemMessages/ICCCGetVersion.go
Normal file
12
ICCC/SystemMessages/ICCCGetVersion.go
Normal file
@ -0,0 +1,12 @@
|
||||
package SystemMessages
|
||||
|
||||
// Requests the version from a Ocean server
|
||||
type ICCCGetVersion struct {
|
||||
}
|
||||
|
||||
// Answer to the version request
|
||||
type ICCCGetVersionAnswer struct {
|
||||
Kind byte // Ocean || Component
|
||||
Name string // Ocean: Hostname; Components: Name
|
||||
Version string // The current version
|
||||
}
|
10
ICCC/SystemMessages/ICCCListenerUpdate.go
Normal file
10
ICCC/SystemMessages/ICCCListenerUpdate.go
Normal file
@ -0,0 +1,10 @@
|
||||
package SystemMessages
|
||||
|
||||
// The message to update an listener from ICCC.
|
||||
type ICCCListenerUpdate struct {
|
||||
Channel string
|
||||
Command string
|
||||
IPAddressPort string
|
||||
IsActiveNew bool
|
||||
Kind byte
|
||||
}
|
15
ICCC/SystemMessages/ICCCNewLogEvent.go
Normal file
15
ICCC/SystemMessages/ICCCNewLogEvent.go
Normal file
@ -0,0 +1,15 @@
|
||||
package SystemMessages
|
||||
|
||||
// The message to send logging events over ICCC.
|
||||
type ICCCNewLogEvent struct {
|
||||
Project string
|
||||
UnixTimestampUTC int64
|
||||
Sender string
|
||||
Category string
|
||||
Level string
|
||||
Severity string
|
||||
Impact string
|
||||
MessageName string
|
||||
MessageDescription string
|
||||
Parameters []string
|
||||
}
|
@ -4,7 +4,7 @@ package SystemMessages
|
||||
type ICCCNumGenNext struct {
|
||||
}
|
||||
|
||||
// The response to the Numgen request.
|
||||
type ICCCAnswerNumGen struct {
|
||||
// The response to the NumGen request.
|
||||
type ICCCNumGenNextAnswer struct {
|
||||
Number int64
|
||||
}
|
||||
|
5
ICCC/SystemMessages/ICCCPing.go
Normal file
5
ICCC/SystemMessages/ICCCPing.go
Normal file
@ -0,0 +1,5 @@
|
||||
package SystemMessages
|
||||
|
||||
// The ping message to check status of other hosts.
|
||||
type ICCCPing struct {
|
||||
}
|
@ -4,4 +4,5 @@ package SystemMessages
|
||||
type ICCCRegisterHost struct {
|
||||
Hostname string // The hostname for the end-point
|
||||
IPAddressPort string // The IP address and port for the end-point
|
||||
Kind byte // Ocean || Component
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package SystemMessages
|
||||
|
||||
// The message to register a command/listener to ICCC.
|
||||
// The message to register an listener to ICCC.
|
||||
type ICCCRegisterListener struct {
|
||||
Channel string // The channel for the provided command
|
||||
Command string // The provided command
|
||||
IsActive bool // Is the command active?
|
||||
IPAddressPort string // The IP address and port for the end-point
|
||||
Kind byte // Ocean || Component
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
package SystemMessages
|
||||
|
||||
// Message type for the startup message:
|
||||
type ICCCStartUpMessage struct {
|
||||
PublicIPAddressAndPort string // The public web server's IP address and port
|
||||
AdminIPAddressAndPort string // The private admin server's IP address and port
|
||||
}
|
15
ICCC/SystemMessages/ICCCStartupMessages.go
Normal file
15
ICCC/SystemMessages/ICCCStartupMessages.go
Normal file
@ -0,0 +1,15 @@
|
||||
package SystemMessages
|
||||
|
||||
// Message type for the startup message of Ocean's servers:
|
||||
type ICCCOceanStartUpMessage struct {
|
||||
PublicIPAddressPort string // The public web server's IP address and port
|
||||
AdminIPAddressPort string // The private admin server's IP address and port
|
||||
OceanVersion string // The current version of this server
|
||||
}
|
||||
|
||||
// Message type for a startup message for external components:
|
||||
type ICCCComponentStartUpMessage struct {
|
||||
IPAddressPort string // The component's ICCC IP address and port
|
||||
Name string // What is the name of this component?
|
||||
Version string // Which version is used?
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package SystemMessages
|
||||
|
||||
var (
|
||||
AnswerACK DefaultAnswer = DefaultAnswer{true, 0} // The command was successful
|
||||
AnswerNACK DefaultAnswer = DefaultAnswer{false, 0} // The command was not successful
|
||||
AnswerUNKNOWN DefaultAnswer = DefaultAnswer{false, -1} // The answer is unknown e.g. an error while reading the answer (HTTP errors, etc.)
|
||||
AnswerACK ICCCDefaultAnswer = ICCCDefaultAnswer{true, 0} // The command was successful
|
||||
AnswerNACK ICCCDefaultAnswer = ICCCDefaultAnswer{false, 0} // The command was not successful
|
||||
AnswerUNKNOWN ICCCDefaultAnswer = ICCCDefaultAnswer{false, -1} // The answer is unknown e.g. an error while reading the answer (HTTP errors, etc.)
|
||||
)
|
||||
|
@ -14,6 +14,11 @@ const (
|
||||
ChannelSHUTDOWN string = `System::Shutdown` // A channel for system shutdown messages.
|
||||
ChannelSTARTUP string = `System::Startup` // A channel for system startup messages.
|
||||
ChannelICCC string = `System::ICCC` // A common ICCC channel.
|
||||
ChannelPING string = `System::Ping` // A channel for pings.
|
||||
ChannelLOGGING string = `System::Logging` // A channel for send log events to the logging system
|
||||
KindOCEAN byte = 0x0
|
||||
KindComponent byte = 0x1
|
||||
KindALL byte = 0xFF
|
||||
)
|
||||
|
||||
var (
|
||||
@ -27,7 +32,10 @@ var (
|
||||
listenersLock sync.RWMutex = sync.RWMutex{} // The mutex for the listener cache
|
||||
cacheListenerDatabase *list.List = nil // The globally cache for all listeners from all servers
|
||||
cacheListenerDatabaseLock sync.RWMutex = sync.RWMutex{} // The mutex for the globally cache
|
||||
cacheHostDatabase *list.List = nil // The cache for all hosts entries
|
||||
cacheHostDatabaseLock sync.RWMutex = sync.RWMutex{} // The read-write mutex for the host cache
|
||||
startCacheTimerLock sync.Mutex = sync.Mutex{} // Mutex for the start timer
|
||||
cacheTimerRunning bool = false // Is the timer running?
|
||||
correctAddressWithPort string = `` // The IP address and port of the this local server
|
||||
activeProtocol string = `http://`
|
||||
)
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// Function to broadcast a message to all listeners.
|
||||
func WriteMessage2All(channel, command string, message interface{}, answerPrototype interface{}) (results []interface{}) {
|
||||
func WriteMessage2All(channel, command string, kind byte, message interface{}, answerPrototype interface{}) (results []interface{}) {
|
||||
cacheListenerDatabaseLock.RLock()
|
||||
defer cacheListenerDatabaseLock.RUnlock()
|
||||
|
||||
@ -21,10 +21,16 @@ func WriteMessage2All(channel, command string, message interface{}, answerProtot
|
||||
for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() {
|
||||
listener := entry.Value.(Scheme.Listener)
|
||||
|
||||
// If the channel and the command matches, deliver the message:
|
||||
// If the channel, command and kind matches, deliver the message:
|
||||
if kind == KindALL {
|
||||
if listener.Channel == channel && listener.Command == command {
|
||||
matchingListener = append(matchingListener, listener)
|
||||
}
|
||||
} else {
|
||||
if listener.Channel == channel && listener.Command == command && listener.Kind == kind {
|
||||
matchingListener = append(matchingListener, listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Was not able to find any matching listener?
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// Function to write a message to any listener.
|
||||
func WriteMessage2Any(channel, command string, message interface{}, answerPrototype interface{}) (result interface{}) {
|
||||
func WriteMessage2Any(channel, command string, kind byte, message interface{}, answerPrototype interface{}) (result interface{}) {
|
||||
cacheListenerDatabaseLock.RLock()
|
||||
defer cacheListenerDatabaseLock.RUnlock()
|
||||
|
||||
@ -22,11 +22,18 @@ func WriteMessage2Any(channel, command string, message interface{}, answerProtot
|
||||
for entry := cacheListenerDatabase.Front(); entry != nil; entry = entry.Next() {
|
||||
listener := entry.Value.(Scheme.Listener)
|
||||
|
||||
// If the channel and the command matches, store the listener:
|
||||
// If the channel, command and kind matches, store the listener:
|
||||
if kind == KindALL {
|
||||
if listener.Channel == channel && listener.Command == command {
|
||||
entries = entries[:len(entries)+1]
|
||||
entries[counter] = listener
|
||||
}
|
||||
} else {
|
||||
if listener.Channel == channel && listener.Command == command && listener.Kind == kind {
|
||||
entries = entries[:len(entries)+1]
|
||||
entries[counter] = listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count := len(entries)
|
||||
|
@ -18,7 +18,7 @@ func initDatabase() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Printf("[Error] Was not able to connect to the logging database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", err)
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -57,7 +57,7 @@ func initDatabase() {
|
||||
if newSession, errDial := mgo.Dial(databaseHost); errDial != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to connect to the MongoDB host `+databaseHost, errDial.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the logging database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errDial.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
} else {
|
||||
logDBSession = newSession
|
||||
}
|
||||
@ -69,7 +69,7 @@ func initDatabase() {
|
||||
if errLogin := logDB.Login(databaseUsername, databasePassword); errLogin != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNameDATABASE, `It was not possible to login the user `+databaseUsername, errLogin.Error())
|
||||
fmt.Printf("[Error] Was not able to connect to the logging database: %s. Please read https://github.com/SommerEngineering/Ocean.\n", errLogin.Error())
|
||||
os.Exit(0)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Get the collection:
|
||||
|
7
Log/GetProjectName.go
Normal file
7
Log/GetProjectName.go
Normal file
@ -0,0 +1,7 @@
|
||||
package Log
|
||||
|
||||
// This function provides the project name for e.g. logging events
|
||||
// coming trough the ICCC.
|
||||
func GetProjectName() string {
|
||||
return projectName
|
||||
}
|
@ -121,10 +121,16 @@ func clearEntry(entry Meta.Entry) (result Meta.Entry) {
|
||||
// Remove white spaces from the parameters.
|
||||
func clearParameters(oldParameters []string) (result []string) {
|
||||
for n := 0; n < len(oldParameters); n++ {
|
||||
// Remove white spaces:
|
||||
oldParameters[n] = removeWhitespaces(oldParameters[n])
|
||||
|
||||
// Is content left after removing?
|
||||
if oldParameters[n] != `` {
|
||||
// Yes, some content is still there. Thus, commit this entry:
|
||||
result = append(result, oldParameters[n])
|
||||
}
|
||||
}
|
||||
|
||||
result = oldParameters
|
||||
return
|
||||
}
|
||||
|
||||
|
56
Log/ICCCLog/ICCCNewLogEventReceiver.go
Normal file
56
Log/ICCCLog/ICCCNewLogEventReceiver.go
Normal file
@ -0,0 +1,56 @@
|
||||
package ICCCLog
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/ICCC"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC new log event message.
|
||||
func ICCCNewLogEventReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC new log event message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := ICCC.Data2Message(SystemMessages.ICCCNewLogEvent{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
// Cast the object to the right type:
|
||||
messageData := obj.(SystemMessages.ICCCNewLogEvent)
|
||||
|
||||
// Provide a log entry:
|
||||
logEntry := LM.Entry{}
|
||||
logEntry.Project = Log.GetProjectName()
|
||||
logEntry.Sender = LM.Sender(messageData.Sender)
|
||||
logEntry.Category = LM.ParseCategory(messageData.Category)
|
||||
logEntry.Level = LM.ParseLevel(messageData.Level)
|
||||
logEntry.Severity = LM.ParseSeverity(messageData.Severity)
|
||||
logEntry.Impact = LM.ParseImpact(messageData.Impact)
|
||||
logEntry.MessageName = LM.MessageName(messageData.MessageName)
|
||||
logEntry.MessageDescription = messageData.MessageDescription
|
||||
logEntry.Parameters = messageData.Parameters
|
||||
logEntry.Time = time.Unix(messageData.UnixTimestampUTC, 0).UTC()
|
||||
|
||||
// Deliver the log event:
|
||||
Log.TakeEntry(logEntry)
|
||||
|
||||
// An answer is necessary:
|
||||
return ICCC.Message2Data("", "", SystemMessages.AnswerACK)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
9
Log/ICCCLog/Variables.go
Normal file
9
Log/ICCCLog/Variables.go
Normal file
@ -0,0 +1,9 @@
|
||||
package ICCCLog
|
||||
|
||||
import (
|
||||
"github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
var (
|
||||
senderName Meta.Sender = `System::Log::ICCCLog`
|
||||
)
|
@ -39,13 +39,21 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) {
|
||||
data.Sender = DeviceDatabase.ReadSenderNames()
|
||||
data.MessageNames = DeviceDatabase.ReadMessageNames()
|
||||
|
||||
// Get the current page as number:
|
||||
currentPage := request.FormValue(`CurrentPage`)
|
||||
if currentPage != `` {
|
||||
if number, err := strconv.Atoi(currentPage); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityNone, LM.ImpactNone, LM.MessageNameEXECUTE, `Was not able to parse the page number.`, err.Error())
|
||||
} else {
|
||||
currentPageNumber = number
|
||||
}
|
||||
}
|
||||
|
||||
// To less parameters?
|
||||
if countParameters < 9 {
|
||||
// Initial view => first page (latest logs)
|
||||
data.Events, lastPageNumber = readLatest()
|
||||
data.SetLiveView = true
|
||||
data.CurrentPage = `1`
|
||||
data.LastPage = fmt.Sprintf("%d", lastPageNumber)
|
||||
if currentPageNumber+1 > lastPageNumber {
|
||||
data.NextPage = fmt.Sprintf("%d", lastPageNumber)
|
||||
} else {
|
||||
@ -53,6 +61,13 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
data.PreviousPage = `1`
|
||||
data.CurrentLevel = `*`
|
||||
data.CurrentTimeRange = `*`
|
||||
data.CurrentCategory = `*`
|
||||
data.CurrentImpact = `*`
|
||||
data.CurrentSeverity = `*`
|
||||
data.CurrentMessageName = `*`
|
||||
data.CurrentSender = `*`
|
||||
} else {
|
||||
// Case: Custom view
|
||||
currentLevel := request.FormValue(`Level`)
|
||||
@ -62,18 +77,8 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) {
|
||||
currentSeverity := request.FormValue(`Severity`)
|
||||
currentMessageName := request.FormValue(`MSGName`)
|
||||
currentSender := request.FormValue(`Sender`)
|
||||
currentPage := request.FormValue(`CurrentPage`)
|
||||
currentLiveView := request.FormValue(`LiveView`)
|
||||
|
||||
// Get the current page as number:
|
||||
if currentPage != `` {
|
||||
if number, err := strconv.Atoi(currentPage); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityNone, LM.ImpactNone, LM.MessageNameEXECUTE, `Was not able to parse the page number.`, err.Error())
|
||||
} else {
|
||||
currentPageNumber = number
|
||||
}
|
||||
}
|
||||
|
||||
// Store the events for the template:
|
||||
data.Events, lastPageNumber = readCustom(currentTimeRange, currentLevel, currentCategory, currentImpact, currentSeverity, currentMessageName, currentSender, currentPageNumber)
|
||||
|
||||
@ -126,6 +131,7 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) {
|
||||
} else {
|
||||
data.CurrentSender = `*`
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the current, last, previous and next page:
|
||||
if currentPage != `` {
|
||||
@ -153,7 +159,6 @@ func HandlerWebLog(response http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
data.PreviousPage = `1`
|
||||
}
|
||||
}
|
||||
|
||||
// Write the MIME type and execute the template:
|
||||
MimeTypes.Write2HTTP(response, MimeTypes.TypeWebHTML)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package NumGen
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ICCC"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
@ -9,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that registers a command.
|
||||
func ICCCNextNumber(data map[string][]string) (result map[string][]string) {
|
||||
func ICCCNextNumberReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, fmt.Sprintf("Was not able to execute the ICCC next number command message. %s", err))
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC next number command message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
@ -32,7 +31,7 @@ func ICCCNextNumber(data map[string][]string) (result map[string][]string) {
|
||||
nextNumber := GetUniqueID()
|
||||
|
||||
// An answer is necessary:
|
||||
answer := SystemMessages.ICCCAnswerNumGen{}
|
||||
answer := SystemMessages.ICCCNumGenNextAnswer{}
|
||||
answer.Number = nextNumber
|
||||
return ICCC.Message2Data(``, ``, answer)
|
||||
} else {
|
74
README.md
74
README.md
@ -2,19 +2,20 @@ Ocean
|
||||
=====
|
||||
|
||||
## Overview
|
||||
Ocean is a smart and powerful application framework and server which uses the KISS principle ("Keep it simple, stupid"). It enables you to develop (distributed) (web or Internet of Things) applications. Therefore, Ocean contains several components to support you:
|
||||
Ocean is a smart and powerful application framework and server which uses the KISS principle ("Keep it simple, stupid"). It enables you to develop (distributed) e.g. web, Internet of Things, distributed data / text mining applications. Therefore, Ocean contains several components to support you:
|
||||
* A distributed logging component with different loggers (log to console, to a database cluster, admin's web view)
|
||||
* A public facing webserver
|
||||
* A private facing webserver (e.g. for administration issues)
|
||||
* A distributed messaging component called ICCC to communicate with all of your servers (or even with other parts at different programming languages at different servers)
|
||||
* A distributed template engine for web applications
|
||||
* A distributed and half-automated configuration management
|
||||
* A distributed and half-automated configuration management with HTML5 admin's interface
|
||||
* A distributed unique number generator which produces e.g. customer IDs
|
||||
* A distributed logging service with HTML5 admin's logging viewer
|
||||
* A simple I18N support
|
||||
* A simple database abstraction which MongoDB as database back-end
|
||||
|
||||
## Operation modes
|
||||
* You can use Ocean just as **distributed messaging broker or e.g. as distributed logging service** by downloading and starting the executables. In this case, you have to implement your business logic somewhere else and connect that logic by the ICCC component to Ocean. This case means, Ocean is a program for you.
|
||||
* You can use Ocean as **distributed messaging broker or e.g. as distributed logging service** by downloading and starting the executables. In this case, you have to implement your business logic somewhere else and connect that logic by the ICCC component to Ocean. This case means, Ocean is a program for you. This case is also for you e.g. if you want to get the strengths of different languages and avoid their weaknesses.
|
||||
|
||||
* The other operation mode is the **integrated mode**. In this case, you write at least some parts of your business logic with the programming language "Go" (http://golang.org) and you import the Ocean framework. It is still possible to have other parts of your business logic somewhere else with different programming languages, connected by Oceans ICCC component. This integraded mode means, Ocean is a framework for you.
|
||||
|
||||
@ -26,14 +27,27 @@ You have to setup a MongoDB (http://www.mongodb.org) database or database cluste
|
||||
|
||||
Ocean uses a very small memory footprint which is usually between 5 and 20 MB, thus, the memory requirements are very low. Further, Ocean also tries to avoid any disk I/O after start-up and utilise for e.g. HTML templates and small static files an in-memory cache.
|
||||
|
||||
One assumption to the environment is the usage of static IP addresses. If you start Ocean with a different IP address as last time, you will receive the "Was not able to register this host" error. If you want to use e.g. a DHCP environment with changing addresses, you have to delete the corresponding out-dated entries from the `ICCCHosts` and `ICCCListener` collections.
|
||||
One assumption to the environment is the usage of static IP addresses. If you start Ocean with a different IP address as last time, you will receive the "*Was not able to register this host*" error. If you want to use e.g. a DHCP environment with changing addresses, you have to delete the corresponding out-dated entries from the `ICCCHosts` and `ICCCListener` collections.
|
||||
|
||||
## Setup
|
||||
### MongoDB steps
|
||||
After you have installed MongoDB and it runs as service, you have to add a database and provide a user for Ocean. Here is how to do this without any GUI tool. Assumption: The database is installed and no user was added and the authentication is not activated til now.
|
||||
```
|
||||
mongo # opens the command-line interface of the MongoDB database
|
||||
use admin # Change to admin database
|
||||
db.createUser({user: "root", pwd: "PASSWORD", roles: [ "root" ]}) # Adds an root user for all databases
|
||||
use Ocean # Change to the non-existing Ocean database
|
||||
db.createUser({ user: "Ocean", pwd: "PASSWORD", roles: [{ role: "dbOwner", db: "Ocean" }]}) # Create an admin user for the Ocean database
|
||||
exit
|
||||
```
|
||||
You must activate the authentication for the MongoDB installtion now in order to allow Ocean to start.
|
||||
|
||||
### Common steps
|
||||
To enable the Ocean's startup, two small configuration files at the working directory are required. The first file is **project.name**: This file contains one line with the project name. If the project name is longer than 10 characters, the name will be cutted after 10 characters.
|
||||
|
||||
The second configuration file is **configuration.json**. It contains the configuration database's information, such as user's name, password, etc. Here is an example:
|
||||
|
||||
```json
|
||||
```JSON
|
||||
{
|
||||
"ConfigDBHostname" : "127.0.0.1:27017",
|
||||
"ConfigDBDatabase" : "MyOcean",
|
||||
@ -42,27 +56,51 @@ The second configuration file is **configuration.json**. It contains the configu
|
||||
"ConfigDBConfigurationCollectionPassword" : "PWD"
|
||||
}
|
||||
```
|
||||
The ConfigDBConfigurationCollection collection gets automatically created. After both files are present and the MongoDB (cluster) is running, Ocean is able to start. The database can and should be empty. Ocean does the configuration automatically. Due to this minimal local configuration and the distributed configuration database, the deployment of additional Ocean servers can be achieved by using e.g. data center scripts, etc.
|
||||
The `ConfigDBConfigurationCollection` collection gets automatically created. After both files are present and the MongoDB database or database cluster is running, Ocean is able to start. The database can and should be empty. Ocean does the configuration automatically. Due to this minimal local configuration and the distributed configuration database, the deployment of additional Ocean servers can be achieved by using e.g. data center scripts, etc.
|
||||
|
||||
In case you setting up an additional Ocean server, you are done. After a few minutes, the ICCC components are connected to each others. The further setup steps in case of a new environment:
|
||||
* Start Ocean the first time. Ocean will connect to the configuration database. After the setup of all necessary database indexes and after the creation of the configuration collection (table), Ocean stops working. This is fine: Ocean cannot connect to the customer and logging database.
|
||||
* Therefore, the second step is to use your MongoDB (GUI) tool(s) and edit the configuration collection's values. The most important values are:
|
||||
* Start Ocean the first time. Ocean will connect to the configuration database. After the setup of all necessary database indexes and after the creation of the configuration collection (i.e. table), Ocean stops working. This is fine: Ocean cannot connect to the customer and logging database.
|
||||
* Therefore, the second step is to edit the configuration collection's values.
|
||||
* To update these values, you can use the built-in MongoDB command-line shell, instead of using a GUI tool. Please see http://docs.mongodb.org/manual/reference/method/db.collection.update/ about how to update single values. To identify the documents which you want to update it is fine to use the names.
|
||||
* Example, about how to update the `CustomerDBHost` field:
|
||||
```
|
||||
mongo # opens the command-line interface of the MongoDB database
|
||||
use Ocean # Change to the Ocean database for the authentication
|
||||
db.auth("root", "PASSWORD") # Your user name and password
|
||||
db.Configuration.update({Name: "CustomerDBHost"},{$set: {Value: "127.0.0.1:27017"}})
|
||||
exit
|
||||
```
|
||||
* The most important values and the corresponding MongoDB update-codes are:
|
||||
* `CustomerDBHost`: Please provide the hostname and port for the customer database. You can use the same as for the configuration database. `db.Configuration.update({Name: "CustomerDBHost"},{$set: {Value: "127.0.0.1:27017"}})`
|
||||
* `CustomerDBDatabase`: Please provide the database name for the customer database. You can use the same as for the configuration database. `db.Configuration.update({Name: "CustomerDBDatabase"},{$set: {Value: "Ocean"}})`
|
||||
* `CustomerDBUsername`: Please provide the customer database's username. `db.Configuration.update({Name: "CustomerDBUsername"},{$set: {Value: "Ocean"}})`
|
||||
* `CustomerDBPassword`: Please provide the customer database's password. `db.Configuration.update({Name: "CustomerDBPassword"},{$set: {Value: "PASSWORD"}})`
|
||||
* `LogDBHost`: Please provide the hostname and port for the logging database. You can use the same as for the configuration database. `db.Configuration.update({Name: "LogDBHost"},{$set: {Value: "127.0.0.1:27017"}})`
|
||||
* `LogDBDatabase`: Please provide the database name for the logging database. You can use the same as for the configuration database. `db.Configuration.update({Name: "LogDBDatabase"},{$set: {Value: "Ocean"}})`
|
||||
* `LogDBUsername`: Please provide the logging database's username. `db.Configuration.update({Name: "LogDBUsername"},{$set: {Value: "Ocean"}})`
|
||||
* `LogDBPassword`: Please provide the logging database's password. `db.Configuration.update({Name: "LogDBPassword"},{$set: {Value: "PASSWORD"}})`
|
||||
* Start Ocean the second time. Ocean should start-up now and provides you the admin's web interface at http://127.0.0.1:60000/. With this interface, you can now conveniently adjust additional configuration values. May you consider these values:
|
||||
* `AdminWebServerBinding`: Where should the admin/private web server connected? You should use this only at local and trusted networks.
|
||||
* `AdminWebServerEnabled`: Is the admin/private web server enabled?
|
||||
* `PublicWebServerPort`: Which port should the public web server use? If you use another web server at the front (e.g. a hardware load balancer, nginx, etc.) you can use the default. If you want to use Ocean directly to the public, use port 80.
|
||||
* `InternalCommPassword`: Replase this value with e.g. a UUID or a good password. It will be used e.g. for the ICCC communication. Therefore, it will be used for the communication between the Ocean's servers.
|
||||
* `CustomerDBHost`: Please provide the hostname and port for the customer database. You can use the same as for the configuration database.
|
||||
* `CustomerDBDatabase`: Please provide the database name for the customer database. You can use the same as for the configuration database.
|
||||
* `CustomerDBUsername`: Please provide the customer database's username.
|
||||
* `CustomerDBPassword`: Please provide the customer database's password.
|
||||
* `LogDBHost`: Please provide the hostname and port for the logging database. You can use the same as for the configuration database.
|
||||
* `LogDBDatabase`: Please provide the database name for the logging database. You can use the same as for the configuration database.
|
||||
* `LogDBUsername`: Please provide the logging database's username.
|
||||
* `LogDBPassword`: Please provide the logging database's password.
|
||||
* `InternalCommPassword`: Replase this value with e.g. a UUID or a good password. It will be used e.g. for the ICCC communication. Therefore, it will be used for the communication between the Ocean's servers. Keep this value secure.
|
||||
* `OceanUtilizeCPUs`: How many CPUs should Ocean utilise? At the moment, Ocean uses one value for all servers. Later on, this will be replaced by a per-machine configuration table/collection to enable the usage of heterogeneous servers.
|
||||
* `LogUseConsoleLogging`: Do you wan to use the console logging? Use it for your first steps and disable it for the production usage. Use the distributed database logging instead!
|
||||
* `LogUseDatabaseLogging`: Is the database logging enabled? Yes, you should enable it.
|
||||
* As third step, start Ocean again. The system should now run fine. Please have a look at the ICCC startup messages.
|
||||
* In case of using Ocean to provide some websites: Use the admin's web interface (http://127.0.0.1:60000/) to upload the necessary files, e.g. `templates.zip` (your HTML templates, see http://golang.org/pkg/html/template/), `staticFiles.zip` (e.g. to provide static images, CSS, JavaScript, XML, JSON data) and a `web.zip` (to separately manage web frameworks like e.g. jQuery, Bootstrap, etc.) See also https://github.com/SommerEngineering/Example003 for an example.
|
||||
* Finnaly, start Ocean again. The system should now run fine. Please have a look at the ICCC startup logging messages.
|
||||
|
||||
### Use Ocean as distributed messaging broker
|
||||
For this case, you can now start your other ICCC components. This are e.g. some Java, Python or C# programs which are using the Ocean ICCC drivers. These drivers currently under development and they will be available soon. **The next priority:** OceanPy, the ICCC driver for Python.
|
||||
|
||||
**Available ICCC drivers:**
|
||||
- [OceanNET - ICCC with .NET and C#](https://github.com/SommerEngineering/OceanNET)
|
||||
|
||||
### Use Ocean as framework e.g. for websites
|
||||
For this case, an example project with documentation is available here: https://github.com/SommerEngineering/Example003
|
||||
|
||||
## Further Documentation
|
||||
If you are interested about more internals, please feel free to read the comments within the source code. Additional, the whole system architecture gets successive documented by using UML. You find the current version here: https://github.com/SommerEngineering/Ocean/blob/master/Ocean.pdf
|
||||
|
||||
## License
|
||||
Ocean's source code is available under a BSD 2-clause license. The used third-party components have different licenses:
|
||||
|
@ -18,5 +18,5 @@ func HandlerRobots(response http.ResponseWriter, request *http.Request) {
|
||||
}
|
||||
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameNETWORK, `The robots.txt was requested.`, request.RemoteAddr)
|
||||
fmt.Fprintf(response, `%s`, robotsContent)
|
||||
fmt.Fprintf(response, "%s", robotsContent)
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package StaticFiles
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Try to read a static file.
|
||||
@ -17,6 +19,40 @@ func FindAndReadFile(filename string) (result []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// Ensure that the TLS keys are secure and save:
|
||||
//
|
||||
if strings.ToLower(filename) == strings.ToLower(ConfigurationDB.Read(`AdminWebServerTLSCertificateName`)) {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityNone, LM.ImpactNone, LM.MessageNameREQUEST, `Someone tried to read the TLS certificate of the admin server. The attempt was inhibited.`)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(filename) == strings.ToLower(ConfigurationDB.Read(`AdminWebServerTLSPrivateKey`)) {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityNone, LM.ImpactNone, LM.MessageNameREQUEST, `Someone tried to read the TLS certificate's private key of the admin server. The attempt was inhibited.`)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(filename) == strings.ToLower(ConfigurationDB.Read(`PublicWebServerTLSCertificateName`)) {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityNone, LM.ImpactNone, LM.MessageNameREQUEST, `Someone tried to read the TLS certificate of the public server. The attempt was inhibited.`)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToLower(filename) == strings.ToLower(ConfigurationDB.Read(`PublicWebServerTLSPrivateKey`)) {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelSECURITY, LM.SeverityNone, LM.ImpactNone, LM.MessageNameREQUEST, `Someone tried to read the TLS certificate's private key of the public server. The attempt was inhibited.`)
|
||||
return
|
||||
}
|
||||
|
||||
result = FindAndReadFileINTERNAL(filename)
|
||||
return
|
||||
}
|
||||
|
||||
func FindAndReadFileINTERNAL(filename string) (result []byte) {
|
||||
|
||||
// Case: The system goes down.
|
||||
if Shutdown.IsDown() {
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare the path:
|
||||
path := filename
|
||||
|
||||
|
@ -8,34 +8,33 @@ import (
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that a server is up and running.
|
||||
func icccSystemStart(data map[string][]string) (result map[string][]string) {
|
||||
// The receiver function for the ICCC message, that a external component is up and running.
|
||||
func icccComponentStartUpMessageReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, fmt.Sprintf("Was not able to execute the ICCC server startup message. %s", err))
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC component startup message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := ICCC.Data2Message(SystemMessages.ICCCStartUpMessage{}, data)
|
||||
_, _, obj := ICCC.Data2Message(SystemMessages.ICCCComponentStartUpMessage{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
// Cast the object to the right type:
|
||||
messageData := obj.(SystemMessages.ICCCStartUpMessage)
|
||||
messageData := obj.(SystemMessages.ICCCComponentStartUpMessage)
|
||||
|
||||
// Provide a log entry:
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: The server is now up and ready.`, messageData.PublicIPAddressAndPort, messageData.AdminIPAddressAndPort)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: The external component is now up and ready.`, fmt.Sprintf("ipAddressPort='%s'", messageData.IPAddressPort), fmt.Sprintf("name='%s'", messageData.Name), fmt.Sprintf("version='%s'", messageData.Version))
|
||||
|
||||
// An answer is necessary:
|
||||
return ICCC.Message2Data("", "", SystemMessages.AnswerACK)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
fmt.Println(`[Error] ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
43
System/ICCCOceanStartUpMessageReceiver.go
Normal file
43
System/ICCCOceanStartUpMessageReceiver.go
Normal file
@ -0,0 +1,43 @@
|
||||
package System
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ICCC"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
)
|
||||
|
||||
// The receiver function for the ICCC message, that an Ocean server is up and running.
|
||||
func icccOceanStartUpMessageReceiver(data map[string][]string) (result map[string][]string) {
|
||||
|
||||
// Recover from errors:
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Log.LogFull(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.SeverityUnknown, LM.ImpactUnknown, LM.MessageNamePARSE, "Was not able to execute the ICCC Ocean server startup message.")
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// Converts the HTTP form data into an object:
|
||||
_, _, obj := ICCC.Data2Message(SystemMessages.ICCCOceanStartUpMessage{}, data)
|
||||
|
||||
// Was it possible to convert the data?
|
||||
if obj != nil {
|
||||
// Cast the object to the right type:
|
||||
messageData := obj.(SystemMessages.ICCCOceanStartUpMessage)
|
||||
|
||||
// Provide a log entry:
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: The Ocean server is now up and ready.`, fmt.Sprintf("public server='%s'", messageData.PublicIPAddressPort), fmt.Sprintf("admin server='%s'", messageData.AdminIPAddressPort), fmt.Sprintf("Ocean's version='%s'", messageData.OceanVersion))
|
||||
|
||||
// An answer is necessary:
|
||||
return ICCC.Message2Data("", "", SystemMessages.AnswerACK)
|
||||
} else {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `ICCC message: Was not able to create the message.`)
|
||||
}
|
||||
|
||||
// In any other error case:
|
||||
result = make(map[string][]string, 0)
|
||||
return
|
||||
}
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/Log/Web"
|
||||
"github.com/SommerEngineering/Ocean/Robots"
|
||||
"github.com/SommerEngineering/Ocean/StaticFiles"
|
||||
"github.com/SommerEngineering/Ocean/System/Version"
|
||||
"github.com/SommerEngineering/Ocean/WebContent"
|
||||
)
|
||||
|
||||
@ -37,7 +38,7 @@ func InitHandlers() {
|
||||
Handlers.AddPublicHandler(`/ICCC`, ICCC.ICCCHandler)
|
||||
|
||||
//
|
||||
// Private Handlers:
|
||||
// Private/Admin Handlers:
|
||||
//
|
||||
|
||||
// Handler for the web frameworks like e.g. jQuery, Bootstrap, etc.
|
||||
@ -58,7 +59,16 @@ func InitHandlers() {
|
||||
// Handler for the web logging:
|
||||
Handlers.AddAdminHandler(`/log`, Web.HandlerWebLog)
|
||||
|
||||
// Handler for the web logging's CSS and JS:
|
||||
// Handler for the access to Ocean's version:
|
||||
Handlers.AddAdminHandler(`/version`, Version.HandlerVersion)
|
||||
|
||||
// Handler for the file upload:
|
||||
Handlers.AddAdminHandler(`/upload`, Admin.HandlerFileUpload)
|
||||
|
||||
// Handler for the configuration view:
|
||||
Handlers.AddAdminHandler(`/configuration`, Admin.HandlerConfiguration)
|
||||
|
||||
// Handler for the admin area:
|
||||
Handlers.AddAdminHandler(`/admin/css/normalize.css`, Admin.HandlerCSSNormalize)
|
||||
Handlers.AddAdminHandler(`/admin/css/webflow.css`, Admin.HandlerCSSWebflow)
|
||||
Handlers.AddAdminHandler(`/admin/css/admin.css`, Admin.HandlerCSSAdmin)
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/CustomerDB"
|
||||
"github.com/SommerEngineering/Ocean/ICCC"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
"github.com/SommerEngineering/Ocean/Log/ICCCLog"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/NumGen"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
@ -97,10 +98,19 @@ func initSystem() {
|
||||
// The logging subsystem is not registered here, because it will be automated called at the end
|
||||
|
||||
// Register all system ICCC commands:
|
||||
ICCC.Registrar(ICCC.ChannelSYSTEM, `System::Start`, icccSystemStart)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::RegisterHost`, ICCC.ICCCRegisterHost)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::RegisterCommand`, ICCC.ICCCRegisterCommand)
|
||||
ICCC.Registrar(ICCC.ChannelNUMGEN, `NumGen::Next`, NumGen.ICCCNextNumber)
|
||||
ICCC.Registrar(ICCC.ChannelSTARTUP, `System::OceanStart`, icccOceanStartUpMessageReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelSTARTUP, `System::ComponentStart`, icccComponentStartUpMessageReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::RegisterHost`, ICCC.ICCCRegisterHostReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::RegisterListener`, ICCC.ICCCRegisterListenerReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::DeleteListener`, ICCC.ICCCDeleteListenerReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::DeleteHost`, ICCC.ICCCDeleteHostReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::ListenerUpdate`, ICCC.ICCCListenerUpdateReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelPING, `Ping::Ping`, ICCC.ICCCPingReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelSYSTEM, `System::Version`, ICCC.ICCCGetVersionReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::GetHosts`, ICCC.ICCCGetHostsReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelICCC, `ICCC::GetListeners`, ICCC.ICCCGetListenersReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelNUMGEN, `NumGen::Next`, NumGen.ICCCNextNumberReceiver)
|
||||
ICCC.Registrar(ICCC.ChannelLOGGING, `Logging::NewLogEvent`, ICCCLog.ICCCNewLogEventReceiver)
|
||||
|
||||
// Start the ICCC Listener Cache:
|
||||
ICCC.InitCacheNow() // Blocking, until the job is done
|
||||
|
6
System/Version/GetVersion.go
Normal file
6
System/Version/GetVersion.go
Normal file
@ -0,0 +1,6 @@
|
||||
package Version
|
||||
|
||||
// Gets Ocean's version
|
||||
func GetVersion() string {
|
||||
return oceansVersion
|
||||
}
|
19
System/Version/HandlerVersion.go
Normal file
19
System/Version/HandlerVersion.go
Normal file
@ -0,0 +1,19 @@
|
||||
package Version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/Shutdown"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Handler for the access to Ocean's version
|
||||
func HandlerVersion(response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
// Case: The system goes down now?
|
||||
if Shutdown.IsDown() {
|
||||
http.NotFound(response, request)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(response, "%s", oceansVersion)
|
||||
}
|
5
System/Version/Variables.go
Normal file
5
System/Version/Variables.go
Normal file
@ -0,0 +1,5 @@
|
||||
package Version
|
||||
|
||||
var (
|
||||
oceansVersion string = `2.1.3` // Ocean's current version
|
||||
)
|
7
Templates/Add.go
Normal file
7
Templates/Add.go
Normal file
@ -0,0 +1,7 @@
|
||||
package Templates
|
||||
|
||||
//AddTemplate adds a template to the template cache so it can be used by ProcessHTML
|
||||
func AddTemplate(src string) error {
|
||||
_, err := templates.Parse(src)
|
||||
return err
|
||||
}
|
@ -20,6 +20,5 @@ func RandomFloat64() (rnd float64) {
|
||||
// Gets a random UUID (v4).
|
||||
func RandomGUID() (guidString string) {
|
||||
guidString = uuid.NewV4().String()
|
||||
guidString = guidString[1 : len(guidString)-1]
|
||||
return
|
||||
}
|
||||
|
@ -6,8 +6,12 @@ import (
|
||||
"github.com/SommerEngineering/Ocean/Handlers"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/StaticFiles"
|
||||
"github.com/SommerEngineering/Ocean/Tools"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -64,6 +68,32 @@ func init() {
|
||||
serverPublic.MaxHeaderBytes = maxHeaderBytes
|
||||
}
|
||||
|
||||
// Is TLS configured?
|
||||
if publicTLSEnabled := ConfigurationDB.Read(`PublicWebServerUseTLS`); strings.ToLower(publicTLSEnabled) == `true` {
|
||||
|
||||
// TLS is enabled. Copy the certificate and private key to the source directory.
|
||||
publicTLSCertificate := StaticFiles.FindAndReadFileINTERNAL(ConfigurationDB.Read(`PublicWebServerTLSCertificateName`))
|
||||
publicTLSPrivateKey := StaticFiles.FindAndReadFileINTERNAL(ConfigurationDB.Read(`PublicWebServerTLSPrivateKey`))
|
||||
|
||||
// Access to the working directory?
|
||||
currentDir, dirError := os.Getwd()
|
||||
if dirError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to read the working directory. Thus, cannot store the TLS certificates!`, dirError.Error())
|
||||
} else {
|
||||
// Build the filenames:
|
||||
pathCertificate := filepath.Join(currentDir, ConfigurationDB.Read(`PublicWebServerTLSCertificateName`))
|
||||
pathPrivateKey := filepath.Join(currentDir, ConfigurationDB.Read(`PublicWebServerTLSPrivateKey`))
|
||||
|
||||
// Write the files:
|
||||
if writeError := ioutil.WriteFile(pathCertificate, publicTLSCertificate, 0660); writeError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to write the TLS certificate to the working directory.`, writeError.Error())
|
||||
}
|
||||
if writeError := ioutil.WriteFile(pathPrivateKey, publicTLSPrivateKey, 0660); writeError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to write the TLS private key to the working directory.`, writeError.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the private web server (i.e. administration server) enabled?
|
||||
if strings.ToLower(ConfigurationDB.Read(`AdminWebServerEnabled`)) == `true` {
|
||||
|
||||
@ -105,6 +135,32 @@ func init() {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameCONFIGURATION, fmt.Sprintf("The admin web server's max. header size was set to %d bytes.", maxHeaderBytes))
|
||||
serverAdmin.MaxHeaderBytes = maxHeaderBytes
|
||||
}
|
||||
|
||||
// Is TLS configured?
|
||||
if adminTLSEnabled := ConfigurationDB.Read(`AdminWebServerUseTLS`); strings.ToLower(adminTLSEnabled) == `true` {
|
||||
|
||||
// TLS is enabled. Copy the certificate and private key to the source directory.
|
||||
adminTLSCertificate := StaticFiles.FindAndReadFileINTERNAL(ConfigurationDB.Read(`AdminWebServerTLSCertificateName`))
|
||||
adminTLSPrivateKey := StaticFiles.FindAndReadFileINTERNAL(ConfigurationDB.Read(`AdminWebServerTLSPrivateKey`))
|
||||
|
||||
// Access to the working directory?
|
||||
currentDir, dirError := os.Getwd()
|
||||
if dirError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to read the working directory. Thus, cannot store the TLS certificates!`, dirError.Error())
|
||||
} else {
|
||||
// Build the filenames:
|
||||
pathCertificate := filepath.Join(currentDir, ConfigurationDB.Read(`AdminWebServerTLSCertificateName`))
|
||||
pathPrivateKey := filepath.Join(currentDir, ConfigurationDB.Read(`AdminWebServerTLSPrivateKey`))
|
||||
|
||||
// Write the files:
|
||||
if writeError := ioutil.WriteFile(pathCertificate, adminTLSCertificate, 0660); writeError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to write the TLS certificate to the working directory.`, writeError.Error())
|
||||
}
|
||||
if writeError := ioutil.WriteFile(pathPrivateKey, adminTLSPrivateKey, 0660); writeError != nil {
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelERROR, LM.MessageNameCONFIGURATION, `Was not able to write the TLS private key to the working directory.`, writeError.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Private web server is disabled:
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `The admin web server is disabled.`)
|
||||
|
@ -2,36 +2,53 @@ package WebServer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/SommerEngineering/Ocean/ConfigurationDB"
|
||||
"github.com/SommerEngineering/Ocean/ICCC"
|
||||
"github.com/SommerEngineering/Ocean/ICCC/SystemMessages"
|
||||
"github.com/SommerEngineering/Ocean/Log"
|
||||
LM "github.com/SommerEngineering/Ocean/Log/Meta"
|
||||
"github.com/SommerEngineering/Ocean/System/Version"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Start() {
|
||||
|
||||
// Tell the whole cluster, that we are up and ready:
|
||||
data := SystemMessages.ICCCStartUpMessage{}
|
||||
data := SystemMessages.ICCCOceanStartUpMessage{}
|
||||
data.OceanVersion = Version.GetVersion()
|
||||
|
||||
// Start the public web server:
|
||||
if serverPublic != nil {
|
||||
data.PublicIPAddressAndPort = serverPublicAddressPort
|
||||
data.PublicIPAddressPort = serverPublicAddressPort
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Public web server is now listening.`, `Configuration for hostname and port.`, serverPublicAddressPort)
|
||||
|
||||
// Is TLS configured?
|
||||
if publicTLSEnabled := ConfigurationDB.Read(`PublicWebServerUseTLS`); strings.ToLower(publicTLSEnabled) == `true` {
|
||||
go serverPublic.ListenAndServeTLS(ConfigurationDB.Read(`PublicWebServerTLSCertificateName`), ConfigurationDB.Read(`PublicWebServerTLSPrivateKey`))
|
||||
} else {
|
||||
go serverPublic.ListenAndServe()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Start the private web server:
|
||||
if serverAdmin != nil {
|
||||
data.AdminIPAddressAndPort = serverAdminAddressPort
|
||||
data.AdminIPAddressPort = serverAdminAddressPort
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, `Admin web server is now listening.`, `Configuration for hostname and port.`, serverAdminAddressPort)
|
||||
|
||||
// Is TLS configured?
|
||||
if adminTLSEnabled := ConfigurationDB.Read(`AdminWebServerUseTLS`); strings.ToLower(adminTLSEnabled) == `true` {
|
||||
go serverAdmin.ListenAndServeTLS(ConfigurationDB.Read(`AdminWebServerTLSCertificateName`), ConfigurationDB.Read(`AdminWebServerTLSPrivateKey`))
|
||||
} else {
|
||||
go serverAdmin.ListenAndServe()
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the whole cluster, that this server is now up and ready:
|
||||
answers := ICCC.WriteMessage2All(ICCC.ChannelSYSTEM, `System::Start`, data, SystemMessages.DefaultAnswer{})
|
||||
answers := ICCC.WriteMessage2All(ICCC.ChannelSTARTUP, `System::OceanStart`, ICCC.KindALL, data, SystemMessages.ICCCDefaultAnswer{})
|
||||
for n, obj := range answers {
|
||||
if obj != nil {
|
||||
answer := obj.(SystemMessages.DefaultAnswer)
|
||||
answer := obj.(SystemMessages.ICCCDefaultAnswer)
|
||||
Log.LogShort(senderName, LM.CategorySYSTEM, LM.LevelINFO, LM.MessageNameSTARTUP, fmt.Sprintf("An answer to the ICCC start up message: Successful=%v, Status=%d, Answer=%d/%d", answer.CommandSuccessful, answer.CommandAnswer, n+1, len(answers)))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user