diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..6782cf4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-fmt + - id: go-imports + - id: no-go-testing + - id: golangci-lint + - id: go-unit-tests diff --git a/README.md b/README.md index d523033..eb16ec8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # sfcs +Sudoku-Funpark Cluster Software: same as [Sudoku-funpark](https://gitea.ligthert.net/golang/sudoku-funpark/), but networked. -Sudoku-Funpark Cluster Software: same as Sudoku-funpark, but networked. \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..0d6ec0a --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,36 @@ +# https://taskfile.dev + +version: '3' + +vars: + APP: sudoku-funpark + BUILD_DIR: builds + +tasks: + default: + cmds: + - go run . --help + silent: true + precommit: + cmds: + - pre-commit autoupdate + - pre-commit run --all + silent: true + lint: + cmds: + - golangci-lint run + silent: true + build: + cmds: + - mkdir -p {{.BUILD_DIR}} + - rm {{.BUILD_DIR}}/* || true + - go tool dist list | grep -v android | grep -v ios | grep -v wasip1 | awk -F '/' '{printf "echo Compiling %s/%s; env CGO_ENABLED=1 GOOS=%s GOARCH=%s go build -o {{.BUILD_DIR}}/{{.APP}}.%s-%s\n",$1,$2,$1,$2,$1,$2 }' | sh + - for i in `ls {{.BUILD_DIR}}/*windows*`; do mv -v $i $i.exe; done + gource: + cmds: + - gource --auto-skip-seconds 1 --key -r 60 + silent: true + godoc: + cmds: + - godoc -http=:6060 + silent: true diff --git a/client/types.go b/client/types.go new file mode 100644 index 0000000..2571379 --- /dev/null +++ b/client/types.go @@ -0,0 +1,6 @@ +package client + +type Client struct { + serverAddress string + serverPort int +} diff --git a/flags/ParseFlags.go b/flags/ParseFlags.go new file mode 100644 index 0000000..9894a40 --- /dev/null +++ b/flags/ParseFlags.go @@ -0,0 +1,36 @@ +package flags + +import "flag" + +func (flags *Flags) ParseFlags() (err error) { + + // Define parameters + flag.StringVar(&flags.Vars.Role, "role", "scheduler", "Role of the instance ['scheduler','agent']") + flag.StringVar(&flags.Vars.Address, "address", "127.0.0.1", "Address of the instance to listen to/connect to") + flag.IntVar(&flags.Vars.Port, "port", 8080, "Port of the instance to listen on/connect to") + flag.IntVar(&flags.Vars.NumCPUs, "numcpus", 1, "[agent] Number of CPUs to use") + + // Parse the flags + flag.Parse() + + // Parse the role parameters + err = flags.parseRole() + if err != nil { + return err + } + + /// Parse the number of CPUs parameters + err = flags.parseNumCPUs() + if err != nil { + return err + } + + // Parse the address and port parameters + err = flags.parseAddressPort() + if err != nil { + return err + } + + // Return nil if no errors + return +} diff --git a/flags/parseAddressPort.go b/flags/parseAddressPort.go new file mode 100644 index 0000000..83d0249 --- /dev/null +++ b/flags/parseAddressPort.go @@ -0,0 +1,32 @@ +package flags + +import ( + "fmt" + "net" +) + +func (flags *Flags) parseAddressPort() (err error) { + + // Ensure that address field is not empty + if flags.Vars.Address == "" { + return fmt.Errorf("Address cannot be empty") + } + + // Ensure that address field is valid IP address + if net.ParseIP(flags.Vars.Address) == nil { + return fmt.Errorf("GloVars must be a valid IP address") + } + + // Ensure that port field is not empty + if flags.Vars.Port == 0 { + return fmt.Errorf("Port cannot be empty") + } + + // Ensure that port field is within the valid range + if flags.Vars.Port < 1 || flags.Vars.Port > 65535 { + return fmt.Errorf("Port must be between 1 and 65535") + } + + return nil + +} diff --git a/flags/parseNumCPUs.go b/flags/parseNumCPUs.go new file mode 100644 index 0000000..bd41506 --- /dev/null +++ b/flags/parseNumCPUs.go @@ -0,0 +1,12 @@ +package flags + +import "fmt" + +func (flags *Flags) parseNumCPUs() (err error) { + + if flags.Vars.NumCPUs <= 0 { + return fmt.Errorf("Number of CPUs must be greater than 0") + } + + return nil +} diff --git a/flags/parseRole.go b/flags/parseRole.go new file mode 100644 index 0000000..543f53c --- /dev/null +++ b/flags/parseRole.go @@ -0,0 +1,18 @@ +package flags + +import "fmt" + +func (flags *Flags) parseRole() (err error) { + + // Ensure that role field is not empty + if flags.Vars.Role == "" { + return fmt.Errorf("Role cannot be empty") + } + + // Ensure that role field is valid + if flags.Vars.Role != "scheduler" && flags.Vars.Role != "agent" { + return fmt.Errorf("Role must be either 'scheduler' or 'agent'") + } + + return nil +} diff --git a/flags/types.go b/flags/types.go new file mode 100644 index 0000000..23bf995 --- /dev/null +++ b/flags/types.go @@ -0,0 +1,7 @@ +package flags + +import "gitea.ligthert.net/golang/sfcs/vars" + +type Flags struct { + Vars *vars.Vars +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f6c616d --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module gitea.ligthert.net/golang/sfcs + +go 1.23.4 + +require gitea.ligthert.net/golang/sudoku-funpark v0.0.0-20250129164508-c1f2a28ac155 + +require github.com/gorilla/websocket v1.5.3 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..844751a --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +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/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c9e5cb9 --- /dev/null +++ b/main.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + "os" + + "gitea.ligthert.net/golang/sfcs/flags" + "gitea.ligthert.net/golang/sfcs/server" + "gitea.ligthert.net/golang/sfcs/vars" +) + +func main() { + + // Create a new instance of the Controller struct + vars := vars.Vars{} + + // Create a new instance of the Flags struct + flags := flags.Flags{Vars: &vars} + err := flags.ParseFlags() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Switch on the role + switch vars.Role { + case "scheduler": + server := server.Server{ListenAddress: vars.Address, ListenPort: vars.Port} + vars.Operator = &server + case "agent": + // operator := client.Client{} + } + + vars.Operator.Start() + +} diff --git a/server/Start.go b/server/Start.go new file mode 100644 index 0000000..1f45dc4 --- /dev/null +++ b/server/Start.go @@ -0,0 +1,68 @@ +package server + +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) + }) + 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) + } +} diff --git a/server/types.go b/server/types.go new file mode 100644 index 0000000..3656900 --- /dev/null +++ b/server/types.go @@ -0,0 +1,6 @@ +package server + +type Server struct { + ListenAddress string + ListenPort int +} diff --git a/vars/types.go b/vars/types.go new file mode 100644 index 0000000..da9e562 --- /dev/null +++ b/vars/types.go @@ -0,0 +1,23 @@ +package vars + +import "gitea.ligthert.net/golang/sudoku-funpark/outputter" + +type Operator interface { + // Start the operator + Start() error +} + +type Vars struct { + // Instance of the Outputter struct + Outputter *outputter.Outputter + // Role is either "scheduler" or "agent" + Role string + // Operator interface + Operator Operator + // [agent] NumCPUs is the number of CPUs to use + NumCPUs int + // Address is the address of the instance to listen to/connect to + Address string + // Port is the port of the instance to listen on/connect to + Port int +}