Skip to content

Commit

Permalink
feat: Advanced Windows features (#213)
Browse files Browse the repository at this point in the history
* feat: Advanced Windows features

* many changes

* organizing code

* updating CHANGELOG

* CR changes

Co-authored-by: Pedro Faria De Miranda Pinto <pedro.faria@ifood.com.br>
Co-authored-by: Pedro Faria <predofaria@gmail.com>
  • Loading branch information
3 people authored Jul 5, 2022
1 parent cebf2c4 commit 607bfa5
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM golang:1.18-stretch
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y git build-essential nodejs
&& apt-get install -y git build-essential nodejs zip

RUN go install golang.org/x/tools/gopls@latest \
&& go install github.com/go-delve/delve/cmd/dlv@latest \
Expand Down
10 changes: 0 additions & 10 deletions .gitpod.Dockerfile

This file was deleted.

6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.19.0
* Fix some typo errors. #210 #212
* Fix float point handling for rain and cloud. #211
* Introducing the advance windows features as Firewall management, custom core affinity and cpu priority. #213


## 1.18.0
* Add sorting by number of players.
* Add session remaining time in the servers list.
Expand Down
36 changes: 23 additions & 13 deletions internal/app/handler_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,24 @@ type ExtraAccSettings struct {
}

type InstancePayload struct {
ID string `json:"id"`
Path string `json:"path"`
IsRunning bool `json:"is_running"`
PID int `json:"pid"`
Settings instance.AccWebConfigJson `json:"accWeb"`
AccSettings instance.AccConfigFiles `json:"acc"`
AccExtraSettings ExtraAccSettings `json:"accExtraSettings"`
ID string `json:"id"`
Path string `json:"path"`
IsRunning bool `json:"is_running"`
PID int `json:"pid"`
Settings instance.AccWebSettingsJson `json:"accWeb"`
AccSettings instance.AccConfigFiles `json:"acc"`
AccExtraSettings ExtraAccSettings `json:"accExtraSettings"`
}

type InstanceOS struct {
Name string `json:"name"`
NumCPU int `json:"numCpu"`
}

type SaveInstancePayload struct {
AccWeb instance.AccWebConfigJson `json:"accWeb"`
Acc instance.AccConfigFiles `json:"acc"`
AccExtraSettings ExtraAccSettings `json:"accExtraSettings"`
AccWeb instance.AccWebSettingsJson `json:"accWeb"`
Acc instance.AccConfigFiles `json:"acc"`
AccExtraSettings ExtraAccSettings `json:"accExtraSettings"`
}

func NewInstancePayload(srv *instance.Instance) InstancePayload {
Expand All @@ -38,7 +43,7 @@ func NewInstancePayload(srv *instance.Instance) InstancePayload {
Path: srv.Path,
IsRunning: srv.IsRunning(),
PID: srv.GetProcessID(),
Settings: srv.Cfg,
Settings: srv.Cfg.Settings,
AccSettings: srv.AccCfg,
}

Expand Down Expand Up @@ -98,7 +103,7 @@ func (h *Handler) NewInstance(c *gin.Context) {
return
}

srv, err := h.sm.Create(&json.Acc, json.AccWeb.AutoStart)
srv, err := h.sm.Create(&json.Acc, json.AccWeb)
if err != nil {
c.JSON(http.StatusInternalServerError, newAccWError(err.Error()))
return
Expand Down Expand Up @@ -156,8 +161,13 @@ func (h *Handler) SaveInstance(c *gin.Context) {
json.Acc.Settings.AdminPassword = srv.AccCfg.Settings.AdminPassword
}

if err := srv.CanSaveSettings(json.AccWeb, json.Acc); err != nil {
c.JSON(http.StatusBadRequest, newAccWError(err.Error()))
return
}

srv.AccCfg = json.Acc
srv.Cfg.AutoStart = json.AccWeb.AutoStart
srv.Cfg.Settings = json.AccWeb

if err := srv.Save(); err != nil {
c.JSON(http.StatusInternalServerError, newAccWError(err.Error()))
Expand Down
17 changes: 17 additions & 0 deletions internal/app/handler_servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"net/http"
"runtime"

"github.com/assetto-corsa-web/accweb/internal/pkg/instance"

Expand Down Expand Up @@ -79,3 +80,19 @@ func (h *Handler) StopAllServers(c *gin.Context) {

c.JSON(http.StatusOK, nil)
}

// Metadata Returns server OS informations
// @Summary Returns server OS informations
// @Schemes
// @Description Metadata informations
// @Tags servers
// @Produce json
// @Success 200
// @Router /metadata [get]
// @Security JWT
func (h *Handler) Metadata(c *gin.Context) {
c.JSON(http.StatusOK, &InstanceOS{
Name: runtime.GOOS,
NumCPU: runtime.NumCPU(),
})
}
1 change: 1 addition & 0 deletions internal/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func setupRouters(r *gin.Engine, sM *server_manager.Service, config *cfg.Config)

api.GET("/servers", h.ListServers)
api.POST("/servers/stop-all", h.StopAllServers)
api.GET("/metadata", h.Metadata)

api.POST("/instance", h.NewInstance)
api.GET("/instance/:id", h.GetInstance)
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/helper/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func (e *wrapError) Unwrap() error {
}

func WrapErrors(errs ...error) error {
if errs == nil || len(errs) == 0 {
if len(errs) == 0 {
return nil
}

Expand Down
42 changes: 42 additions & 0 deletions internal/pkg/helper/windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package helper

import (
"fmt"
"os/exec"
)

func SetCoreAffinity(pid int, value uint) error {
args := []string{fmt.Sprintf("$Process = Get-Process -Id %d; $Process.ProcessorAffinity=%d", pid, value)}
cmd := exec.Command("PowerShell", args...)
return cmd.Start()
}

func SetCpuPriority(pid int, p uint) error {
args := []string{"process", "where", fmt.Sprintf("ProcessId=%d", pid), "call", "setpriority", fmt.Sprintf("%d", p)}
cmd := exec.Command("wmic", args...)
return cmd.Start()
}

func AddFirewallRules(pid, tcp, udp int) error {
if err := addFWRule(pid, tcp, "TCP"); err != nil {
return err
}

return addFWRule(pid, udp, "UDP")
}

func addFWRule(pid, port int, t string) error {
args := []string{
"advfirewall", "firewall", "add", "rule", fmt.Sprintf("name=\"ACCSERVER_%d\"", pid),
"dir=in", "action=allow", fmt.Sprintf("protocol=%s", t), fmt.Sprintf("localport=%d", port),
}

cmd := exec.Command("netsh.exe", args...)
return cmd.Start()
}

func DelFirewallRules(pid int) error {
args := []string{"advfirewall", "firewall", "del", "rule", fmt.Sprintf("name=\"ACCSERVER_%d\"", pid)}
cmd := exec.Command("netsh.exe", args...)
return cmd.Start()
}
41 changes: 36 additions & 5 deletions internal/pkg/instance/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ const (
bopJsonName = "bop.json"
assistRulesJsonName = "assistRules.json"
configVersion = 1

WinCpuPriorityRealtime = 256
WinCpuPriorityHigh = 128
WinCpuPriorityAboveNormal = 32768
WinCpuPriorityNormal = 32
WinCpuPriorityBelowNormal = 16384
WinCpuPriorityLow = 64
)

var (
CpuPriorities = map[int]bool{
WinCpuPriorityRealtime: true,
WinCpuPriorityHigh: true,
WinCpuPriorityAboveNormal: true,
WinCpuPriorityNormal: true,
WinCpuPriorityBelowNormal: true,
WinCpuPriorityLow: true,
}
)

type AccConfigFiles struct {
Expand All @@ -25,11 +43,24 @@ type AccConfigFiles struct {
}

type AccWebConfigJson struct {
ID string `json:"id"`
Md5Sum string `json:"md5Sum"`
AutoStart bool `json:"autoStart"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID string `json:"id"`
Md5Sum string `json:"md5Sum"`
AutoStart bool `json:"autoStart"` // backward compatibility
Settings AccWebSettingsJson `json:"settings"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

type AccWebSettingsJson struct {
AutoStart bool `json:"autoStart"`
EnableAdvWinCfg bool `json:"enableAdvWindowsCfg"`
AdvWindowsCfg *AccWebAdvWindowsSettingsJson `json:"advWindowsCfg"`
}

type AccWebAdvWindowsSettingsJson struct {
CpuPriority uint `json:"cpuPriority"`
CoreAffinity uint `json:"coreAffinity"`
EnableWinFW bool `json:"enableWindowsFirewall"`
}

func (a *AccWebConfigJson) SetUpdateAt() {
Expand Down
79 changes: 77 additions & 2 deletions internal/pkg/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const (
var (
ErrServerCantBeRunning = errors.New("server instance cant be running to perform this action")
ErrServerDirIsInvalid = errors.New("server directory is invalid")
ErrInvalidCoreAffinity = errors.New("invalid core affinity value")
ErrInvalidCpuPriority = errors.New("invalid cpu priority value")
)

type Instance struct {
Expand Down Expand Up @@ -73,6 +75,10 @@ func (s *Instance) Start() error {
return err
}

if s.HasAdvancedWindowsConfig() {
s.startWithAdvWindows()
}

s.Live.setServerState(ServerStateStarting)

logrus.WithField("server_id", s.GetID()).WithField("pid", s.GetProcessID()).Info("acc server started")
Expand All @@ -82,24 +88,63 @@ func (s *Instance) Start() error {
return nil
}

func (s *Instance) startWithAdvWindows() {
cfg := s.Cfg.Settings.AdvWindowsCfg
l := logrus.WithField("server_id", s.GetID()).WithField("PID", s.GetProcessID())

l.Infof("Defining core affinity to %d", cfg.CoreAffinity)
if err := helper.SetCoreAffinity(s.GetProcessID(), cfg.CoreAffinity); err != nil {
l.Errorf("failed to define affinity with value: %d. ERROR: %s", cfg.CoreAffinity, err.Error())
}

l.Infof("Defining cpu priority to %d", cfg.CpuPriority)
if err := helper.SetCpuPriority(s.GetProcessID(), cfg.CpuPriority); err != nil {
l.Errorf("failed to define cpu priority with value: %d. ERROR: %s", cfg.CpuPriority, err.Error())
}

if cfg.EnableWinFW {
l.Info("Add Firewall Rules")
if err := helper.AddFirewallRules(s.GetProcessID(), s.AccCfg.Configuration.TcpPort, s.AccCfg.Configuration.UdpPort); err != nil {
l.Errorf("Failed to add accserver firewall rule. ERROR: %s", err.Error())
}
}
}

func (s *Instance) Stop() error {
if !s.IsRunning() {
return nil
}

if err := s.cmd.Process.Signal(os.Interrupt); err != nil {
if err := s.cmd.Process.Kill(); err != nil {
return err
logrus.WithField("server_id", s.GetID()).
WithError(err).
Error("Failed to kill the accserver process.")
}
}

if s.HasAdvancedWindowsConfig() {
s.stopWithAdvWindows()
}

s.Live.serverOffline()

logrus.WithField("server_id", s.GetID()).Info("acc server stopped")

return nil
}

func (s *Instance) stopWithAdvWindows() {
if !s.Cfg.Settings.AdvWindowsCfg.EnableWinFW {
return
}

logrus.Info("Removing Firewall Rules")
if err := helper.DelFirewallRules(s.GetProcessID()); err != nil {
logrus.Errorf("Failed to add accserver firewall rule for TCP. ERROR: %s", err.Error())
}
}

func (s *Instance) GetProcessID() int {
if s.IsRunning() {
return s.cmd.Process.Pid
Expand All @@ -108,11 +153,37 @@ func (s *Instance) GetProcessID() int {
return 0
}

func (s *Instance) Save() error {
func (s *Instance) CanSaveSettings(aw AccWebSettingsJson, ac AccConfigFiles) error {
if s.IsRunning() {
return ErrServerCantBeRunning
}

if s.Cfg.Settings.EnableAdvWinCfg {
if s.Cfg.Settings.AdvWindowsCfg == nil {
return errors.New("where are the Advanced Windows Config definitions?")
}

if s.Cfg.Settings.AdvWindowsCfg.CoreAffinity > DefaultCoreAffinity {
return ErrInvalidCoreAffinity
}

if _, ok := CpuPriorities[int(s.Cfg.Settings.AdvWindowsCfg.CpuPriority)]; !ok {
return ErrInvalidCpuPriority
}
}

return nil
}

func (s *Instance) Save() error {
if err := s.CanSaveSettings(s.Cfg.Settings, s.AccCfg); err != nil {
return err
}

if s.Cfg.Settings.AdvWindowsCfg != nil && s.Cfg.Settings.AdvWindowsCfg.CoreAffinity == 0 {
s.Cfg.Settings.AdvWindowsCfg.CoreAffinity = DefaultCoreAffinity
}

fileList := map[string]interface{}{
accwebConfigJsonName: &s.Cfg,
configurationJsonName: &s.AccCfg.Configuration,
Expand Down Expand Up @@ -347,3 +418,7 @@ func (s *Instance) prepareCmdLogHandler() error {

return nil
}

func (s *Instance) HasAdvancedWindowsConfig() bool {
return runtime.GOOS == "windows" && s.Cfg.Settings.EnableAdvWinCfg
}
Loading

0 comments on commit 607bfa5

Please sign in to comment.