Compare commits

...

10 Commits

29 changed files with 363701 additions and 65 deletions

1
.gitignore vendored
View File

@ -24,4 +24,3 @@ go.work.sum
# env file
.env

View File

@ -1,3 +1,101 @@
# sfcs
Sudoku-Funpark Cluster Software: same as [Sudoku-funpark](https://gitea.ligthert.net/golang/sudoku-funpark/), but networked.
## Notes
### Network Protocol
While network and transport is done via WebSocket, traffic is sent using Python.
( 🤔 not sure if this is the smartest idea, as Golang isn't really that strong with JSON. 🫠 )
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
Manager => Agent:
- task: A task for the agent to chew on
### Commands
#### Register
1. Hostname
2. CPU cores
3. Memory
#### Update
1. cpu usage
2. memory usage
3. assigned task [none,taskID]
#### Solution
1. taskId
2. solution ([][9]string)
#### deregister
1. Hostname
#### task
1. taskId
2. split
3. part
4. puzzle ([][9]string)
### Protocol
For ease of programming I would like start of easy and use `;`-separated string to push values between manager and its agents. In this setup the format is predefined, and parsing will be based on the first field after the string is split on `;`.
* register;string;int;memory int
* update;float;float;int
* solution;string;string;string;string;string;string;string;string;string;string
* deregister;string
* task;int;int;int;string;string;string;string;string;string;string;string;string
I still have the impression that Go and JSON is hard to do as the rigidness of Go does not go well with the free spirited nature of JSON. I might revise this in the future.
### Imports
I intend use `golang/sudoku-funpark` for the code to determine workload and calculate the puzzles that need to be solved. And while this is all modular, it unfortunately isn't flexible enough to actually use it in the tool (or I am not trying hard enough).
### Other notes
#### Load ordering
Outputter
Controller
Export
Flags
Solver
```Go
controller := controller.Controller{}
outp := outputter.Outputter{}
export := export.Export{Controller: &controller}
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.

View File

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

File diff suppressed because it is too large Load Diff

22
client/connectToServer.go Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,7 @@
package client
import "log"
func (client *Client) task(inputs []string) {
log.Println(inputs)
}

View File

@ -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
View 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
View File

@ -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
View File

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

View File

@ -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()

View File

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

View File

@ -0,0 +1,6 @@
package server
func (server *Server) closeConnection(agentName string) {
server.Agents[agentName].conn.Close()
delete(server.Agents, agentName)
}

View 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
View 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
View 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
View 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
View 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))
}
}

View File

@ -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
View 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
View 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
View 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))
}
}

View File

@ -4,7 +4,7 @@ import "gitea.ligthert.net/golang/sudoku-funpark/outputter"
type Operator interface {
// Start the operator
Start() error
Start()
}
type Vars struct {