Compare commits
8 Commits
fd41e0c03e
...
trunk
Author | SHA1 | Date | |
---|---|---|---|
8bd09a4983 | |||
6c38363694 | |||
9b6a90fbce | |||
b9c1b9e320 | |||
1d16d30cb6 | |||
5d534221a4 | |||
6d76a88c9c | |||
e7e8953ec3 |
28
README.md
28
README.md
@ -71,3 +71,31 @@ Solver
|
||||
flags := flags.Flags{Controller: &controller}
|
||||
solver := solver.Solver{Controller: &controller, Outp: &outp}
|
||||
```
|
||||
### Manager & Agent channel setup
|
||||
Both the manager and the agent will have similar setups:
|
||||
* `*websocket.Conn` is stored in _types.go_.
|
||||
* Any go routine can write to the socket.
|
||||
* All reads go to a `readProcessor()` which in turn parses the message, and in turn does the appropriate logic (read, calling go routine/functions)
|
||||
|
||||
This does however presents me with a problem. Since all communication is reactive, state-less, and all over the same connection; so tracking things is a bit hard. And this is something for maybe the next phase. So for now, reactive content only. This said, I need figure out how on earth I can prevent an agent from taking two jobs at the same time. I guess I will need to build in some agent state tracking for this.
|
||||
|
||||
#### Server setup
|
||||
_(This piece assumes that the network is super fast, and turbo stable.)_
|
||||
|
||||
The current setup of the server is only the thing that its connected to. There is a writer that spams something, a reader that prints everything it receives to the console. And that is it. Pretty much split-brain and it currently lacks the ability to manage workloads on agents.
|
||||
|
||||
For this exercise it needs to be able to handle multiple agents, but start at one. There needs to be an interface that should tell me how much CPU/MEMs an agent has. And finally tell the agent to solve something and accept any answers it returns.
|
||||
|
||||
Another issue is that it needs a manager interfaces to keep track of Agents, agents metrics, status. But also allow users to issue commands. NFC how I am going to do that. But something tells me it will require me to find a way to communicate all this with the user, and I can do this with a simple REST API, or create a terminal interface, or do web things.
|
||||
|
||||
|
||||
1. Incoming connection gets caught by `handleConnections()`
|
||||
2. `handleConnections()` verifies an agent registers
|
||||
3. After registeration it puts this into a map, along with the `conn` and stuffs this into a slice of maps
|
||||
4. The manager pics up the new agent, and offers info to anyone who wants to see it.
|
||||
5. A client can ask the manager to solve a puzzle, manager calculates which agents to use, sends of the work.
|
||||
6. Solution is returned and stored.
|
||||
|
||||
So from there.
|
||||
|
||||
I think the biggest issue now is find a way to have a terminal or web interface with the user.
|
||||
|
@ -11,6 +11,14 @@ tasks:
|
||||
cmds:
|
||||
- go run . --help
|
||||
silent: true
|
||||
server:
|
||||
cmds:
|
||||
- go run . -role scheduler
|
||||
silent: true
|
||||
agent:
|
||||
cmds:
|
||||
- go run . -role agent
|
||||
silent: true
|
||||
precommit:
|
||||
cmds:
|
||||
- pre-commit autoupdate
|
||||
|
94
client/Start.go
Normal file
94
client/Start.go
Normal file
@ -0,0 +1,94 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/inhies/go-bytesize"
|
||||
"github.com/mackerelio/go-osstat/memory"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
func (client *Client) Start() {
|
||||
|
||||
// Setup interrupt handling
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
// Connect to the server
|
||||
err := client.connectToServer()
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: Failed connecting to server - ", err)
|
||||
}
|
||||
defer client.conn.Close()
|
||||
|
||||
// Agent => Manager:
|
||||
// - register: register an agent with the manager
|
||||
// - update: agents sends CPU and Mem metrics (does this every second)
|
||||
// - solution: solution of a task given by the manager
|
||||
// - deregister: deregister an agent from the manager
|
||||
|
||||
// Register
|
||||
// One-time at the start
|
||||
|
||||
// Fetch the hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: Failed to register - cannot get hostname - ", err)
|
||||
}
|
||||
|
||||
// Fetch the number of logical cores
|
||||
cpustats, err := cpu.Counts(true)
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: Failed to register - unable to fetch number of cores - ", err)
|
||||
}
|
||||
|
||||
// Fetch memory stats in bytes
|
||||
mem, err := memory.Get()
|
||||
if err != nil {
|
||||
log.Println("ERROR: Failed to register - fetching memory info - ", err)
|
||||
}
|
||||
// Use the ByteSize package to allow for memory calculations
|
||||
b := bytesize.New(float64(mem.Total))
|
||||
|
||||
// Register this agent with the scheduler
|
||||
client.writeToServer("register;" + hostname + ";" + strconv.Itoa(cpustats) + ";" + b.String())
|
||||
|
||||
// Setup updater logic
|
||||
// Runs continuesly in the background
|
||||
go client.statusUpdater()
|
||||
|
||||
// Start handler for incoming messages
|
||||
done := make(chan struct{})
|
||||
go client.handleIncoming(done)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-interrupt:
|
||||
log.Println("Interrupt received -- Stopping")
|
||||
|
||||
// Dereg the agent before dying off
|
||||
client.writeToServer("deregister;" + hostname)
|
||||
|
||||
// Cleanly close the connection by sending a close message and then
|
||||
// waiting (with timeout) for the server to close the connection.
|
||||
err := client.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
log.Println("ERROR: Close connection - ", err)
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
362880
client/blocks.csv
Normal file
362880
client/blocks.csv
Normal file
File diff suppressed because it is too large
Load Diff
22
client/connectToServer.go
Normal file
22
client/connectToServer.go
Normal file
@ -0,0 +1,22 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func (client *Client) connectToServer() (err error) {
|
||||
u := url.URL{Scheme: "ws", Host: client.ServerAddress + ":" + strconv.Itoa(client.ServerPort), Path: "/ws"}
|
||||
log.Printf("Connecting to %s", u.String())
|
||||
|
||||
// sigh
|
||||
client.conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: dialing - :", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
26
client/handleIncoming.go
Normal file
26
client/handleIncoming.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (client *Client) handleIncoming(done chan struct{}) {
|
||||
defer close(done)
|
||||
for {
|
||||
_, message, err := client.conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("ERROR: read message - :", err)
|
||||
return
|
||||
}
|
||||
// Ignore printing keep alives
|
||||
if string(message) == "keep alive - staying alive" {
|
||||
continue
|
||||
}
|
||||
log.Printf("recv: %s", message)
|
||||
inputs := strings.Split(string(message), ";")
|
||||
if inputs[0] == "task" {
|
||||
client.task(inputs)
|
||||
}
|
||||
}
|
||||
}
|
38
client/statusUpdater.go
Normal file
38
client/statusUpdater.go
Normal file
@ -0,0 +1,38 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/inhies/go-bytesize"
|
||||
"github.com/mackerelio/go-osstat/memory"
|
||||
"github.com/shirou/gopsutil/cpu"
|
||||
)
|
||||
|
||||
// Look into using a ticker instead of a for-loop
|
||||
func (client *Client) statusUpdater() {
|
||||
for {
|
||||
// Fetch CPU usage in percentages
|
||||
cpustats, err := cpu.Percent(time.Second, false)
|
||||
if err != nil {
|
||||
log.Println("ERROR: fetching CPU usage infor - ", err)
|
||||
}
|
||||
|
||||
// Fetch memory stats in bytes
|
||||
mem, err := memory.Get()
|
||||
if err != nil {
|
||||
log.Println("ERROR: fetching memory info - ", err)
|
||||
}
|
||||
|
||||
// Use the ByteSize package to allow for memory calculations
|
||||
b := bytesize.New(float64(mem.Used))
|
||||
|
||||
// Prepare the message.
|
||||
update := "update;" + strconv.Itoa(int(cpustats[0])) + ";" + b.String() + ";" + strconv.Itoa(client.taskId)
|
||||
|
||||
// Write the message to the server
|
||||
client.writeToServer(update)
|
||||
|
||||
}
|
||||
}
|
7
client/task.go
Normal file
7
client/task.go
Normal file
@ -0,0 +1,7 @@
|
||||
package client
|
||||
|
||||
import "log"
|
||||
|
||||
func (client *Client) task(inputs []string) {
|
||||
log.Println(inputs)
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package client
|
||||
|
||||
import "github.com/gorilla/websocket"
|
||||
|
||||
type Client struct {
|
||||
serverAddress string
|
||||
serverPort int
|
||||
ServerAddress string
|
||||
ServerPort int
|
||||
taskId int
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
16
client/writeToServer.go
Normal file
16
client/writeToServer.go
Normal file
@ -0,0 +1,16 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func (client *Client) writeToServer(msg string) {
|
||||
// Send the message towards the server.
|
||||
err := client.conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
||||
if err != nil {
|
||||
log.Println("ERROR: writing - ", err)
|
||||
return
|
||||
}
|
||||
}
|
16
go.mod
16
go.mod
@ -4,4 +4,18 @@ go 1.23.4
|
||||
|
||||
require gitea.ligthert.net/golang/sudoku-funpark v0.0.0-20250129164508-c1f2a28ac155
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf
|
||||
github.com/mackerelio/go-osstat v0.2.5
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
)
|
||||
|
27
go.sum
27
go.sum
@ -1,4 +1,31 @@
|
||||
gitea.ligthert.net/golang/sudoku-funpark v0.0.0-20250129164508-c1f2a28ac155 h1:o3MLpDn26r/9DOCdATINPck2W0pNdXi6WiJcpKQwAYQ=
|
||||
gitea.ligthert.net/golang/sudoku-funpark v0.0.0-20250129164508-c1f2a28ac155/go.mod h1:GdZ2otAB+NMA19Noe7UFeP3gJtHCU4h8N158Oj1jNVY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s=
|
||||
github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4=
|
||||
github.com/mackerelio/go-osstat v0.2.5 h1:+MqTbZUhoIt4m8qzkVoXUJg1EuifwlAJSk4Yl2GXh+o=
|
||||
github.com/mackerelio/go-osstat v0.2.5/go.mod h1:atxwWF+POUZcdtR1wnsUcQxTytoHG4uhl2AKKzrOajY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
6
main.go
6
main.go
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gitea.ligthert.net/golang/sfcs/client"
|
||||
"gitea.ligthert.net/golang/sfcs/flags"
|
||||
"gitea.ligthert.net/golang/sfcs/server"
|
||||
"gitea.ligthert.net/golang/sfcs/vars"
|
||||
@ -25,10 +26,11 @@ func main() {
|
||||
// Switch on the role
|
||||
switch vars.Role {
|
||||
case "scheduler":
|
||||
server := server.Server{ListenAddress: vars.Address, ListenPort: vars.Port}
|
||||
server := server.Server{ListenAddress: vars.Address, ListenPort: vars.Port, Agents: make(map[string]*server.Agent)}
|
||||
vars.Operator = &server
|
||||
case "agent":
|
||||
// operator := client.Client{}
|
||||
client := client.Client{ServerAddress: vars.Address, ServerPort: vars.Port}
|
||||
vars.Operator = &client
|
||||
}
|
||||
|
||||
vars.Operator.Start()
|
||||
|
@ -4,65 +4,21 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func (server *Server) Start() error {
|
||||
// Start the server
|
||||
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
handleConnections(w, r)
|
||||
})
|
||||
// Start the HTTP and WebSocket server
|
||||
func (server *Server) Start() {
|
||||
// Declare ourselves up and running to the console.
|
||||
log.Println("Started sudoku-funpark server")
|
||||
|
||||
// Run the agent manager
|
||||
server.agentManager()
|
||||
|
||||
// Start the http server
|
||||
http.HandleFunc("GET /json", server.jsonDumper)
|
||||
http.HandleFunc("POST /addTask", server.addTask)
|
||||
|
||||
// Start the websocket server
|
||||
http.HandleFunc("/ws", server.handleConnections)
|
||||
log.Fatal(http.ListenAndServe(server.ListenAddress+":"+strconv.Itoa(server.ListenPort), nil))
|
||||
|
||||
time.Sleep(600 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
func handleConnections(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
log.Println("Starting server")
|
||||
go readMessages(conn)
|
||||
writeMessages(conn)
|
||||
|
||||
}
|
||||
|
||||
func readMessages(conn *websocket.Conn) {
|
||||
for {
|
||||
messageType, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
log.Printf("Received(%d): %s\n", messageType, message)
|
||||
|
||||
// This sets the time-out for any incoming messages.
|
||||
// conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
func writeMessages(conn *websocket.Conn) {
|
||||
for {
|
||||
err := conn.WriteMessage(websocket.TextMessage, []byte("Hello World!"))
|
||||
if err != nil {
|
||||
log.Println("Error writing message:", err)
|
||||
break
|
||||
}
|
||||
// This sets the time-out for any outgoing messages.
|
||||
// conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
61
server/addTask.go
Normal file
61
server/addTask.go
Normal file
@ -0,0 +1,61 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (server *Server) addTask(w http.ResponseWriter, r *http.Request) {
|
||||
var valid bool = true
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
if err := r.ParseForm(); err != nil {
|
||||
fmt.Fprintf(w, "ERROR: Failed ParseForm() err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
//fmt.Println(r.PostForm)
|
||||
// Validate r.PostForm["rows"]
|
||||
// Look at flags.validateRow()
|
||||
// 1. Make sure the rows is 9 in length
|
||||
if len(r.PostForm["rows"]) != 9 {
|
||||
fmt.Fprintf(w, "ERROR: There aren't 9 rows")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Validate the row
|
||||
for _, value := range r.PostForm["rows"] {
|
||||
if !server.validateRow(value) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
||||
// Go/No-Go moment
|
||||
if !valid {
|
||||
w.Write([]byte("ERROR found"))
|
||||
return
|
||||
}
|
||||
|
||||
// Add the task
|
||||
var puzzle [9]string
|
||||
puzzle[0] = r.PostForm["rows"][0]
|
||||
puzzle[1] = r.PostForm["rows"][1]
|
||||
puzzle[2] = r.PostForm["rows"][2]
|
||||
puzzle[3] = r.PostForm["rows"][3]
|
||||
puzzle[4] = r.PostForm["rows"][4]
|
||||
puzzle[5] = r.PostForm["rows"][5]
|
||||
puzzle[6] = r.PostForm["rows"][6]
|
||||
puzzle[7] = r.PostForm["rows"][7]
|
||||
puzzle[8] = r.PostForm["rows"][8]
|
||||
|
||||
// Create task and chuck it in the server struct
|
||||
task := Task{Puzzle: puzzle}
|
||||
server.Tasks = append(server.Tasks, &task)
|
||||
|
||||
// Calling it
|
||||
w.Write([]byte("Ok"))
|
||||
|
||||
}
|
23
server/agentManager.go
Normal file
23
server/agentManager.go
Normal file
@ -0,0 +1,23 @@
|
||||
package server
|
||||
|
||||
import "time"
|
||||
|
||||
func (server *Server) agentManager() {
|
||||
// Start the great for-loop
|
||||
for {
|
||||
// Count the number of agents without a runnig task
|
||||
|
||||
// Check if there are any new Tasks up for grabs
|
||||
|
||||
// See if the task can be divided by the number of agents
|
||||
|
||||
// If so, split
|
||||
// if not, split-1
|
||||
// Try again
|
||||
|
||||
// Give agents task, maybe even look for the ones with the most CPUs
|
||||
|
||||
// 😴
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
80
server/agents.html
Normal file
80
server/agents.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>JSON Data Display</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
#data-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.data-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>JSON Data Display</h1>
|
||||
<div id="data-container">
|
||||
<!-- Data will be inserted here -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const url = 'http://localhost:8080/json'; // Replace with your actual URL
|
||||
const dataContainer = document.getElementById('data-container');
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
updateData(data);
|
||||
} catch (error) {
|
||||
console.error('There was a problem with the fetch operation:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateData(data) {
|
||||
const agent = data.Agents.blackbox;
|
||||
dataContainer.innerHTML = `
|
||||
<div class="data-item"><strong>Name:</strong> ${agent.Name}</div>
|
||||
<div class="data-item"><strong>Registration Time:</strong> ${agent.Reg}</div>
|
||||
<div class="data-item"><strong>Cores:</strong> ${agent.Cores}</div>
|
||||
<div class="data-item"><strong>CPU Usage:</strong> ${agent.Cpu.join(', ')}</div>
|
||||
<div class="data-item"><strong>Max Memory:</strong> ${agent.Mem_max}</div>
|
||||
<div class="data-item"><strong>Memory Usage:</strong> ${agent.Mem_usg.join(', ')}</div>
|
||||
<div class="data-item"><strong>Task ID:</strong> ${agent.TaskId}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Fetch data initially
|
||||
fetchData();
|
||||
|
||||
// Fetch data every second
|
||||
setInterval(fetchData, 1000);
|
||||
</script>
|
||||
<hr/>
|
||||
<form action="http://localhost:8080/addTask" method="post">
|
||||
<table border="0">
|
||||
<tr><td>Row1</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row2</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row3</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row4</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row5</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row6</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row7</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row8</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td>Row9</td><td>:</td><td><input type="text" value="000000000" name="rows"></td></tr>
|
||||
<tr><td colspan="3" align="right"><input type="submit"></td></tr>
|
||||
|
||||
</table>
|
||||
</form>
|
||||
<hr/>
|
||||
</body>
|
||||
</html>
|
6
server/closeConnection.go
Normal file
6
server/closeConnection.go
Normal file
@ -0,0 +1,6 @@
|
||||
package server
|
||||
|
||||
func (server *Server) closeConnection(agentName string) {
|
||||
server.Agents[agentName].conn.Close()
|
||||
delete(server.Agents, agentName)
|
||||
}
|
66
server/handleConnections.go
Normal file
66
server/handleConnections.go
Normal file
@ -0,0 +1,66 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Handle incoming websocket connections
|
||||
func (server *Server) handleConnections(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
// defer conn.Close()
|
||||
|
||||
log.Println("Incoming connection")
|
||||
|
||||
// Check for register
|
||||
var registered bool
|
||||
var msgs []string
|
||||
for !registered {
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
|
||||
msgs = server.parseMessage(message)
|
||||
if string(msgs[0]) == "register" {
|
||||
registered = true
|
||||
}
|
||||
}
|
||||
log.Println("Connected to agent")
|
||||
|
||||
// Create var of type Agent
|
||||
cores_int, err := strconv.Atoi(msgs[2])
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: failed strconv.Atoi(cpu cores):", err)
|
||||
}
|
||||
mem_max_string := strings.ReplaceAll(msgs[3], "GB", "")
|
||||
mem_max_float, err := strconv.ParseFloat(mem_max_string, 64)
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: failed strconv.ParseFloat():", err)
|
||||
}
|
||||
agent := Agent{
|
||||
Name: msgs[1],
|
||||
Reg: time.Now(),
|
||||
Cores: cores_int,
|
||||
Mem_max: mem_max_float,
|
||||
TaskId: 0,
|
||||
conn: conn,
|
||||
}
|
||||
|
||||
// Dump it into server.Agents
|
||||
server.Agents[msgs[1]] = &agent
|
||||
|
||||
defer server.closeConnection(msgs[1])
|
||||
|
||||
go server.readMessages(agent)
|
||||
server.writeMessages(conn)
|
||||
|
||||
}
|
20
server/jsonDumper.go
Normal file
20
server/jsonDumper.go
Normal file
@ -0,0 +1,20 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (server *Server) jsonDumper(w http.ResponseWriter, r *http.Request) {
|
||||
json, err := json.Marshal(server)
|
||||
if err != nil {
|
||||
fmt.Println("JSON Marshaling error:", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("This is my Website"))
|
||||
}
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(json)
|
||||
}
|
7
server/parseMessage.go
Normal file
7
server/parseMessage.go
Normal file
@ -0,0 +1,7 @@
|
||||
package server
|
||||
|
||||
import "strings"
|
||||
|
||||
func (server *Server) parseMessage(message []byte) []string {
|
||||
return strings.Split(string(message), ";")
|
||||
}
|
29
server/pruneSlice.go
Normal file
29
server/pruneSlice.go
Normal file
@ -0,0 +1,29 @@
|
||||
package server
|
||||
|
||||
import "slices"
|
||||
|
||||
func (server *Server) pruneIntSlice(mySlice []int) []int {
|
||||
if len(mySlice) > 10 {
|
||||
for key := range mySlice {
|
||||
if key == 10 {
|
||||
mySlice = slices.Delete(mySlice, 9, 10)
|
||||
} else {
|
||||
mySlice[key] = mySlice[key+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return mySlice
|
||||
}
|
||||
|
||||
func (server *Server) pruneFloat64Slice(mySlice []float64) []float64 {
|
||||
if len(mySlice) > 10 {
|
||||
for key := range mySlice {
|
||||
if key == 10 {
|
||||
mySlice = slices.Delete(mySlice, 9, 10)
|
||||
} else {
|
||||
mySlice[key] = mySlice[key+1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return mySlice
|
||||
}
|
52
server/readMessages.go
Normal file
52
server/readMessages.go
Normal file
@ -0,0 +1,52 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Rewrite to become a general input handler
|
||||
func (server *Server) readMessages(agent Agent) {
|
||||
for {
|
||||
_, message, err := agent.conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
// log.Printf("Received(%d): %s\n", messageType, message)
|
||||
|
||||
msgs := server.parseMessage(message)
|
||||
|
||||
if msgs[0] == "update" {
|
||||
cpu_usg, err := strconv.Atoi(msgs[1])
|
||||
if err != nil {
|
||||
fmt.Println("ERROR: converting string to int", err)
|
||||
}
|
||||
server.Agents[agent.Name].Cpu = append(server.Agents[agent.Name].Cpu, cpu_usg)
|
||||
server.Agents[agent.Name].Cpu = server.pruneIntSlice(server.Agents[agent.Name].Cpu)
|
||||
|
||||
mem_usg_string := strings.ReplaceAll(msgs[2], "GB", "")
|
||||
mem_usg_float, err := strconv.ParseFloat(mem_usg_string, 64)
|
||||
if err != nil {
|
||||
log.Fatal("ERROR: failed strconv.ParseFloat():", err)
|
||||
}
|
||||
server.Agents[agent.Name].Mem_usg = append(server.Agents[agent.Name].Mem_usg, mem_usg_float)
|
||||
server.Agents[agent.Name].Mem_usg = server.pruneFloat64Slice(server.Agents[agent.Name].Mem_usg)
|
||||
|
||||
taskId, err := strconv.Atoi(msgs[3])
|
||||
if err != nil {
|
||||
log.Println("ERROR: cannot convert taskId:", err)
|
||||
}
|
||||
server.Agents[agent.Name].TaskId = taskId
|
||||
}
|
||||
|
||||
if msgs[0] == "deregister" {
|
||||
server.closeConnection(agent.Name)
|
||||
}
|
||||
|
||||
// This sets the time-out for any incoming messages.
|
||||
// conn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||
}
|
||||
}
|
@ -1,6 +1,35 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type Agent struct {
|
||||
Name string
|
||||
Reg time.Time
|
||||
Cores int
|
||||
Cpu []int
|
||||
Mem_max float64
|
||||
Mem_usg []float64
|
||||
TaskId int
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Puzzle [9]string
|
||||
Solutions [][]string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
ListenAddress string
|
||||
ListenPort int
|
||||
Agents map[string]*Agent
|
||||
Tasks []*Task
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
13
server/validChar.go
Normal file
13
server/validChar.go
Normal file
@ -0,0 +1,13 @@
|
||||
package server
|
||||
|
||||
func (server *Server) validChar(char rune) (valid bool) {
|
||||
decvals := [10]int{48, 49, 50, 51, 52, 53, 54, 55, 56, 57}
|
||||
|
||||
for _, value := range decvals {
|
||||
if char == rune(value) {
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
40
server/validateRow.go
Normal file
40
server/validateRow.go
Normal file
@ -0,0 +1,40 @@
|
||||
package server
|
||||
|
||||
func (server *Server) validateRow(row string) (valid bool) {
|
||||
// Declarations baby!
|
||||
valid = true
|
||||
var found bool
|
||||
var double bool
|
||||
count := make(map[rune]int)
|
||||
|
||||
// 1. Make sure row is 9 in length
|
||||
if len(row) != 9 {
|
||||
valid = false
|
||||
}
|
||||
|
||||
// 2. Ensure all digits are numbers
|
||||
for _, value := range row {
|
||||
found = server.validChar(value)
|
||||
}
|
||||
|
||||
if !found {
|
||||
valid = false
|
||||
}
|
||||
|
||||
// 3. Ensure all digits (except zero) are only present once
|
||||
for _, digits := range row {
|
||||
count[digits] = count[digits] + 1
|
||||
}
|
||||
|
||||
for key, value := range count {
|
||||
if value > 1 && key != 48 {
|
||||
double = true
|
||||
}
|
||||
}
|
||||
|
||||
if double {
|
||||
valid = false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
19
server/writeMessages.go
Normal file
19
server/writeMessages.go
Normal file
@ -0,0 +1,19 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func (server *Server) writeMessages(conn *websocket.Conn) {
|
||||
for {
|
||||
err := conn.WriteMessage(websocket.TextMessage, []byte("keep alive - staying alive"))
|
||||
if err != nil {
|
||||
log.Println("Error writing message:", err)
|
||||
break
|
||||
}
|
||||
// This sets the time-out for any outgoing messages.
|
||||
// conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import "gitea.ligthert.net/golang/sudoku-funpark/outputter"
|
||||
|
||||
type Operator interface {
|
||||
// Start the operator
|
||||
Start() error
|
||||
Start()
|
||||
}
|
||||
|
||||
type Vars struct {
|
||||
|
Reference in New Issue
Block a user