Split backend service into checker and generator
Both are linked through a unix socket.
This commit is contained in:
parent
f755d7c998
commit
ed091e761c
@ -1,7 +1,8 @@
|
||||
admin/admin
|
||||
backend/backend
|
||||
checker/checker
|
||||
dashboard/dashboard
|
||||
evdist/evdist
|
||||
generator/generator
|
||||
receiver/receiver
|
||||
repochecker/repochecker
|
||||
frontend/fic/build
|
||||
|
@ -1,4 +1,4 @@
|
||||
image: nemunaire/fic-backend:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
@ -6,16 +6,16 @@ tags:
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
- image: nemunaire/fic-backend:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: nemunaire/fic-backend:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
- image: nemunaire/fic-backend:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
- image: nemunaire/fic-checker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
22
.drone-manifest-fic-generator.yml
Normal file
22
.drone-manifest-fic-generator.yml
Normal file
@ -0,0 +1,22 @@
|
||||
image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
- image: nemunaire/fic-generator:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
variant: v7
|
87
.drone.yml
87
.drone.yml
@ -17,8 +17,9 @@ steps:
|
||||
commands:
|
||||
- apk --no-cache add git
|
||||
- go get -v -d srs.epita.fr/fic-server/admin
|
||||
- go get -v -d srs.epita.fr/fic-server/backend
|
||||
- go get -v -d srs.epita.fr/fic-server/checker
|
||||
- go get -v -d srs.epita.fr/fic-server/evdist
|
||||
- go get -v -d srs.epita.fr/fic-server/generator
|
||||
- go get -v -d srs.epita.fr/fic-server/receiver
|
||||
- go get -v -d srs.epita.fr/fic-server/dashboard
|
||||
- go get -v -d srs.epita.fr/fic-server/repochecker
|
||||
@ -48,8 +49,9 @@ steps:
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/admin/sync
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/admin/pki
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/admin
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/backend
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/checker
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/evdist
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/generator
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/receiver
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/dashboard
|
||||
- go vet -v -buildvcs=false srs.epita.fr/fic-server/repochecker
|
||||
@ -70,10 +72,10 @@ steps:
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build backend
|
||||
- name: build checker
|
||||
image: golang:alpine
|
||||
commands:
|
||||
- go build -v -buildvcs=false -o deploy/backend-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/backend
|
||||
- go build -v -buildvcs=false -o deploy/checker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/checker
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@ -84,6 +86,13 @@ steps:
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build generator
|
||||
image: golang:alpine
|
||||
commands:
|
||||
- go build -v -buildvcs=false -o deploy/generator-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/generator
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build receiver
|
||||
image: golang:alpine
|
||||
commands:
|
||||
@ -176,17 +185,17 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: docker backend
|
||||
- name: docker checker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: nemunaire/fic-backend
|
||||
repo: nemunaire/fic-checker
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile-backend
|
||||
dockerfile: Dockerfile-checker
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
@ -206,6 +215,20 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: docker generator
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: nemunaire/fic-generator
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile-generator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- name: docker receiver
|
||||
image: plugins/docker
|
||||
settings:
|
||||
@ -351,8 +374,9 @@ steps:
|
||||
commands:
|
||||
- apk --no-cache add git
|
||||
- go get -v -d srs.epita.fr/fic-server/admin
|
||||
- go get -v -d srs.epita.fr/fic-server/backend
|
||||
- go get -v -d srs.epita.fr/fic-server/checker
|
||||
- go get -v -d srs.epita.fr/fic-server/evdist
|
||||
- go get -v -d srs.epita.fr/fic-server/generator
|
||||
- go get -v -d srs.epita.fr/fic-server/receiver
|
||||
- go get -v -d srs.epita.fr/fic-server/dashboard
|
||||
|
||||
@ -364,10 +388,10 @@ steps:
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build backend
|
||||
- name: build checker
|
||||
image: golang:alpine
|
||||
commands:
|
||||
- go build -v -buildvcs=false -o deploy/backend-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/backend
|
||||
- go build -v -buildvcs=false -o deploy/checker-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/checker
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
@ -378,6 +402,13 @@ steps:
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build generator
|
||||
image: golang:alpine
|
||||
commands:
|
||||
- go build -v -buildvcs=false -o deploy/generator-${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH} srs.epita.fr/fic-server/generator
|
||||
environment:
|
||||
CGO_ENABLED: 0
|
||||
|
||||
- name: build receiver
|
||||
image: golang:alpine
|
||||
commands:
|
||||
@ -472,17 +503,17 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: docker backend
|
||||
- name: docker checker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: nemunaire/fic-backend
|
||||
repo: nemunaire/fic-checker
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile-backend
|
||||
dockerfile: Dockerfile-checker
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
@ -502,6 +533,21 @@ steps:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: docker generator
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: nemunaire/fic-generator
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
dockerfile: Dockerfile-generator
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
- name: docker receiver
|
||||
image: plugins/docker
|
||||
settings:
|
||||
@ -598,12 +644,12 @@ steps:
|
||||
password:
|
||||
from_secret: docker_password
|
||||
|
||||
- name: publish backend
|
||||
- name: publish checker
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: .drone-manifest-fic-backend.yml
|
||||
spec: .drone-manifest-fic-checker.yml
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
@ -620,6 +666,17 @@ steps:
|
||||
password:
|
||||
from_secret: docker_password
|
||||
|
||||
- name: publish generator
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: .drone-manifest-fic-generator.yml
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
|
||||
- name: publish receiver
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
|
@ -1,22 +0,0 @@
|
||||
FROM golang:1-alpine as gobuild
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY settings settings/
|
||||
COPY libfic ./libfic/
|
||||
COPY backend ./backend/
|
||||
|
||||
RUN go get -d -v ./backend && \
|
||||
go build -v -buildvcs=false -o backend/backend ./backend
|
||||
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
WORKDIR /srv
|
||||
|
||||
ENTRYPOINT ["/srv/backend"]
|
||||
|
||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/backend/backend /srv/backend
|
22
Dockerfile-checker
Normal file
22
Dockerfile-checker
Normal file
@ -0,0 +1,22 @@
|
||||
FROM golang:1-alpine as gobuild
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY settings settings/
|
||||
COPY libfic ./libfic/
|
||||
COPY checker ./checker/
|
||||
|
||||
RUN go get -d -v ./checker && \
|
||||
go build -v -buildvcs=false -o checker/checker ./checker
|
||||
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
WORKDIR /srv
|
||||
|
||||
ENTRYPOINT ["/srv/checker"]
|
||||
|
||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/checker/checker /srv/checker
|
22
Dockerfile-generator
Normal file
22
Dockerfile-generator
Normal file
@ -0,0 +1,22 @@
|
||||
FROM golang:1-alpine as gobuild
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /go/src/srs.epita.fr/fic-server/
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY settings settings/
|
||||
COPY libfic ./libfic/
|
||||
COPY generator ./generator/
|
||||
|
||||
RUN go get -d -v ./generator && \
|
||||
go build -v -buildvcs=false -o generator/generator ./generator
|
||||
|
||||
|
||||
FROM alpine:3.18
|
||||
|
||||
WORKDIR /srv
|
||||
|
||||
ENTRYPOINT ["/srv/generator"]
|
||||
|
||||
COPY --from=gobuild /go/src/srs.epita.fr/fic-server/generator/generator /srv/generator
|
@ -13,7 +13,7 @@ FROM nginx:stable-alpine
|
||||
|
||||
ENV FIC_BASEURL=/ \
|
||||
HOST_RECEIVER=receiver:8080 HOST_ADMIN=admin:8081 HOST_DASHBOARD=dashboard:8082 HOST_QA=qa:8083 \
|
||||
PATH_FILES=/srv/FILES PATH_STARTINGBLOCK=/srv/STARTINGBLOCK PATH_STATIC=/srv/htdocs-frontend PATH_SETTINGS=/srv/SETTINGSDIST PATH_SYNC=/srv/SYNC PATH_TEAMS=/srv/TEAMS
|
||||
PATH_FILES=/srv/FILES PATH_STARTINGBLOCK=/srv/STARTINGBLOCK PATH_STATIC=/srv/htdocs-frontend PATH_SETTINGS=/srv/SETTINGSDIST PATH_TEAMS=/srv/TEAMS
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
|
@ -11,13 +11,14 @@ micro-services :
|
||||
|
||||
- `admin` is the web interface and API used to control the challenge
|
||||
and doing synchronization.
|
||||
- `backend` is an inotify reacting service that handles submissions
|
||||
checking and team's files generation.
|
||||
- `checker` is an inotify reacting service that handles submissions
|
||||
checking.
|
||||
- `dashboard` is a public interface to explain and follow the
|
||||
conquest, aims to animate the challenge for visitors.
|
||||
- `evdist` is an inotify reacting service that handles settings
|
||||
changes during the challenge (eg. a 30 minutes event where hints are
|
||||
free, ...).
|
||||
- `generator` takes care of global and team's files generation.
|
||||
- `qa` is an interface dedicated to challenge development, it stores
|
||||
reports to be treated by challenges creators.
|
||||
- `receiver` is only responsible for receiving submissions. It is the
|
||||
|
@ -1,13 +1,16 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
@ -15,6 +18,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var GeneratorSocket string
|
||||
|
||||
func declareClaimsRoutes(router *gin.RouterGroup) {
|
||||
// Tasks
|
||||
router.GET("/claims", getClaims)
|
||||
@ -287,6 +292,7 @@ func clearClaims(c *gin.Context) {
|
||||
}
|
||||
|
||||
func generateTeamIssuesFile(team fic.Team) error {
|
||||
if GeneratorSocket == "" {
|
||||
if my, err := team.MyIssueFile(); err != nil {
|
||||
return fmt.Errorf("Unable to generate issue FILE (tid=%d): %w", team.Id, err)
|
||||
} else if j, err := json.Marshal(my); err != nil {
|
||||
@ -294,6 +300,37 @@ func generateTeamIssuesFile(team fic.Team) error {
|
||||
} else if err = ioutil.WriteFile(path.Join(TeamsDir, fmt.Sprintf("%d", team.Id), "issues.json"), j, 0644); err != nil {
|
||||
return fmt.Errorf("Unable to write issues' file: %w", err)
|
||||
}
|
||||
} else {
|
||||
buf, err := json.Marshal(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Something is wrong with JSON encoder: %w", err)
|
||||
}
|
||||
|
||||
sockType := "unix"
|
||||
if strings.Contains(GeneratorSocket, ":") {
|
||||
sockType = "tcp"
|
||||
}
|
||||
|
||||
socket, err := net.Dial(sockType, GeneratorSocket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer socket.Close()
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return socket, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := httpClient.Post("http://localhost/enqueue", "application/json", bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to enqueue new generation event: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -108,6 +108,7 @@ func main() {
|
||||
flag.StringVar(&api.DashboardDir, "dashbord", "./DASHBOARD", "Base directory where save public JSON files")
|
||||
flag.StringVar(&settings.SettingsDir, "settings", settings.SettingsDir, "Base directory where load and save settings")
|
||||
flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Base directory where found challenges files, local part")
|
||||
flag.StringVar(&api.GeneratorSocket, "generator", "./GENERATOR/generator.socket", "Path to the generator socket (used to trigger issues.json generations, use an empty string to generate locally)")
|
||||
flag.StringVar(&localImporterDirectory, "localimport", localImporterDirectory,
|
||||
"Base directory where found challenges files to import, local part")
|
||||
flag.BoolVar(&localImporterSymlink, "localimportsymlink", localImporterSymlink,
|
||||
|
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -1 +0,0 @@
|
||||
backend
|
1
checker/.gitignore
vendored
Normal file
1
checker/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
checker
|
@ -45,7 +45,7 @@ func treatWantChoices(pathname string, team *fic.Team) {
|
||||
} else if err = team.DisplayChoices(flag); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
} else {
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
if err = os.Remove(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
52
checker/generation.go
Normal file
52
checker/generation.go
Normal file
@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
var generatorSocket string
|
||||
|
||||
func appendGenQueue(gs fic.GenStruct) error {
|
||||
buf, err := json.Marshal(gs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Something is wrong with JSON encoder: %w", err)
|
||||
}
|
||||
|
||||
sockType := "unix"
|
||||
if strings.Contains(generatorSocket, ":") {
|
||||
sockType = "tcp"
|
||||
}
|
||||
|
||||
socket, err := net.Dial(sockType, generatorSocket)
|
||||
if err != nil {
|
||||
log.Printf("Unable to contact generator at: %s, retring in 1 second", generatorSocket)
|
||||
time.Sleep(time.Second)
|
||||
return appendGenQueue(gs)
|
||||
}
|
||||
defer socket.Close()
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: func(network, addr string) (net.Conn, error) {
|
||||
return socket, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := httpClient.Post("http://localhost/enqueue", "application/json", bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to enqueue new generation event: %w", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
return nil
|
||||
}
|
@ -56,8 +56,8 @@ func treatOpeningHint(pathname string, team *fic.Team) {
|
||||
log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
|
||||
}
|
||||
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(genStruct{Type: GenEvents})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
|
||||
if err = os.Remove(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
@ -60,7 +60,7 @@ func treatIssue(pathname string, team *fic.Team) {
|
||||
if err = os.Remove(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
||||
genTeamIssuesFile(team)
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeamIssues, TeamId: team.Id})
|
||||
}
|
||||
} else {
|
||||
var exercice *fic.Exercice = nil
|
||||
@ -85,6 +85,6 @@ func treatIssue(pathname string, team *fic.Team) {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
||||
}
|
||||
genTeamIssuesFile(team)
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeamIssues, TeamId: team.Id})
|
||||
}
|
||||
}
|
@ -75,7 +75,6 @@ func reloadSettings(config *settings.Settings) {
|
||||
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
|
||||
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
|
||||
ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0
|
||||
if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor {
|
||||
fic.PartialValidation = config.PartialValidation
|
||||
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
|
||||
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
|
||||
@ -87,41 +86,18 @@ func reloadSettings(config *settings.Settings) {
|
||||
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
|
||||
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
|
||||
fic.DiscountedFactor = config.DiscountedFactor
|
||||
|
||||
if !skipInitialGeneration {
|
||||
log.Println("Generating files...")
|
||||
go func() {
|
||||
genAll()
|
||||
log.Println("Full generation done")
|
||||
}()
|
||||
} else {
|
||||
skipInitialGeneration = false
|
||||
log.Println("Regeneration skipped by option.")
|
||||
}
|
||||
lastRegeneration = config.Generation
|
||||
} else {
|
||||
log.Println("No change found. Skipping regeneration.")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if v, exists := os.LookupEnv("FIC_BASEURL"); exists {
|
||||
fic.FilesDir = v + "files"
|
||||
} else {
|
||||
fic.FilesDir = "/files"
|
||||
}
|
||||
|
||||
var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server")
|
||||
flag.StringVar(&generatorSocket, "generator", "./GENERATOR/generator.socket", "Path to the generator socket")
|
||||
flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings")
|
||||
flag.StringVar(&SubmissionDir, "submission", "./submissions", "Base directory where save submissions")
|
||||
flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
|
||||
flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Request path prefix to reach files")
|
||||
var debugINotify = flag.Bool("debuginotify", false, "Show skipped inotofy events")
|
||||
flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration")
|
||||
flag.IntVar(¶llelJobs, "jobs", parallelJobs, "Number of generation workers")
|
||||
flag.Parse()
|
||||
|
||||
log.SetPrefix("[backend] ")
|
||||
log.SetPrefix("[checker] ")
|
||||
|
||||
settings.SettingsDir = path.Clean(settings.SettingsDir)
|
||||
SubmissionDir = path.Clean(SubmissionDir)
|
||||
@ -129,8 +105,6 @@ func main() {
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
launchWorkers()
|
||||
|
||||
log.Println("Creating submission directory...")
|
||||
if _, err := os.Stat(path.Join(SubmissionDir, ".tmp")); os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(path.Join(SubmissionDir, ".tmp"), 0777); err != nil {
|
||||
@ -158,24 +132,23 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Register SIGUSR1, SIGUSR2
|
||||
// Register SIGUSR1 and SIGTERM
|
||||
interrupt1 := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt1, syscall.SIGUSR1)
|
||||
interrupt2 := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt2, syscall.SIGUSR2)
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, syscall.SIGTERM)
|
||||
|
||||
watchedNotify := fsnotify.Create
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-interrupt:
|
||||
break loop
|
||||
case <-interrupt1:
|
||||
log.Println("SIGUSR1 received, retreating all files in queue...")
|
||||
walkAndTreat(SubmissionDir)
|
||||
log.Println("SIGUSR1 treated.")
|
||||
case <-interrupt2:
|
||||
inQueueMutex.Lock()
|
||||
log.Printf("SIGUSR2 received, dumping statistics:\n parallelJobs: %d\n genTeamQueue size: %d\n genQueue: %d\n Teams in queue: %v\n Challenge started: %v\n Last regeneration: %v\n", parallelJobs, len(genTeamQueue), len(genQueue), inGenQueue, ChStarted, lastRegeneration)
|
||||
inQueueMutex.Unlock()
|
||||
case ev := <-watcher.Events:
|
||||
if d, err := os.Lstat(ev.Name); err == nil && ev.Op&fsnotify.Create == fsnotify.Create && d.Mode().IsDir() && d.Mode()&os.ModeSymlink == 0 && d.Name() != ".tmp" {
|
||||
// Register new subdirectory
|
@ -46,8 +46,8 @@ func registrationProcess(id string, team *fic.Team, members []fic.Member, team_i
|
||||
log.Println(id, "[ERR]", err)
|
||||
}
|
||||
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(genStruct{Type: GenTeams})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeams})
|
||||
}
|
||||
|
||||
func treatRegistration(pathname string, team_id string) {
|
||||
@ -94,7 +94,7 @@ func treatRegistration(pathname string, team_id string) {
|
||||
log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
|
||||
}
|
||||
|
||||
appendGenQueue(genStruct{Type: GenEvents})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
|
||||
}
|
||||
}
|
||||
}
|
@ -38,11 +38,11 @@ func treatRename(pathname string, team *fic.Team) {
|
||||
if _, err := team.Update(); err != nil {
|
||||
log.Printf("%s [WRN] Unable to change team name: %s\n", id, err)
|
||||
}
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
if _, err := fic.NewEvent(fmt.Sprintf("Souhaitons bonne chance à l'équipe <strong>%s</strong> qui vient de nous rejoindre !", html.EscapeString(team.Name)), "info"); err != nil {
|
||||
log.Printf("%s [WRN] Unable to create event: %s\n", id, err)
|
||||
}
|
||||
appendGenQueue(genStruct{Type: GenEvents})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
|
||||
if err := os.Remove(pathname); err != nil {
|
||||
log.Printf("%s [ERR] %s\n", id, err)
|
||||
}
|
@ -129,7 +129,7 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
|
||||
solved, err := exercice.CheckResponse(cksum[:], responses.Keys, responses.MCQs, team)
|
||||
if err != nil {
|
||||
log.Println(id, "[ERR] Unable to CheckResponse:", err)
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
return
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
|
||||
}
|
||||
|
||||
if tm != nil {
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
} else if solved {
|
||||
log.Printf("%s Team %d SOLVED exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
|
||||
if err := exercice.Solved(team); err != nil {
|
||||
@ -152,9 +152,9 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
|
||||
} else if _, err := fic.NewEvent(fmt.Sprintf("L'équipe %s a résolu le <strong>%d<sup>e</sup></strong> défi %s !", html.EscapeString(team.Name), lvl, theme.Name), "success"); err != nil {
|
||||
log.Println(id, "[WRN] Unable to create event:", err)
|
||||
}
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(genStruct{id, GenThemes})
|
||||
appendGenQueue(genStruct{id, GenTeams})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenThemes})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeams})
|
||||
} else {
|
||||
log.Printf("%s Team %d submit an invalid solution for exercice %d (%s : %s)\n", id, team.Id, exercice.Id, theme.Name, exercice.Title)
|
||||
|
||||
@ -166,8 +166,8 @@ func treatSubmission(pathname string, team *fic.Team, exercice_id string) {
|
||||
log.Println(id, "[WRN] Unable to create event:", err)
|
||||
}
|
||||
}
|
||||
genTeamQueue <- team
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenTeam, TeamId: team.Id})
|
||||
}
|
||||
|
||||
appendGenQueue(genStruct{id, GenEvents})
|
||||
appendGenQueue(fic.GenStruct{Id: id, Type: fic.GenEvents})
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
10.10.10.1 phobos
|
||||
|
||||
172.17.0.2 admin
|
||||
172.17.0.3 backend
|
||||
172.17.0.3 checker
|
||||
172.17.0.4 db
|
||||
172.17.0.5 sshd
|
||||
172.17.0.5 generator
|
||||
|
||||
10.10.10.2 deimos
|
||||
|
||||
|
@ -28,11 +28,12 @@ services:
|
||||
- /mnt/fic:/mnt/fic:ro
|
||||
- dashboard:/srv/DASHBOARD
|
||||
- files:/srv/FILES
|
||||
- generator:/srv/GENERATOR:ro
|
||||
- pki:/srv/PKI
|
||||
- settings:/srv/SETTINGS
|
||||
- settingsdist:/srv/SETTINGSDIST
|
||||
- submissions:/srv/submissions
|
||||
- teams:/srv/TEAMS
|
||||
- teams:/srv/TEAMS:ro
|
||||
command: -baseurl /admin/ -localimport /mnt/fic -localimportsymlink
|
||||
depends_on:
|
||||
- mysql
|
||||
@ -51,20 +52,39 @@ services:
|
||||
- settings:/srv/SETTINGS
|
||||
- settingsdist:/srv/SETTINGSDIST
|
||||
|
||||
backend:
|
||||
checker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-backend
|
||||
image: nemunaire/fic-backend:latest
|
||||
dockerfile: Dockerfile-checker
|
||||
image: nemunaire/fic-checker:latest
|
||||
links:
|
||||
- mysql
|
||||
networks:
|
||||
- fic-net
|
||||
volumes:
|
||||
- files:/srv/FILES:ro
|
||||
- teams:/srv/TEAMS
|
||||
- generator:/srv/GENERATOR:ro
|
||||
- teams:/srv/TEAMS:ro
|
||||
- settingsdist:/srv/SETTINGSDIST:ro
|
||||
- submissions:/srv/submissions
|
||||
depends_on:
|
||||
- mysql
|
||||
- generator
|
||||
environment:
|
||||
- MYSQL_HOST=mysql
|
||||
|
||||
generator:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile-generator
|
||||
image: nemunaire/fic-generator:latest
|
||||
links:
|
||||
- mysql
|
||||
networks:
|
||||
- fic-net
|
||||
volumes:
|
||||
- generator:/srv/GENERATOR
|
||||
- teams:/srv/TEAMS
|
||||
- settingsdist:/srv/SETTINGSDIST:ro
|
||||
depends_on:
|
||||
- mysql
|
||||
environment:
|
||||
@ -106,8 +126,6 @@ services:
|
||||
- settingsdist:/srv/SETTINGSDIST:ro
|
||||
- submissions:/srv/submissions
|
||||
- startingblock:/srv/startingblock
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
dashboard:
|
||||
build:
|
||||
@ -123,8 +141,6 @@ services:
|
||||
- dashboard:/srv/DASHBOARD
|
||||
- teams:/srv/TEAMS:ro
|
||||
- settingsdist:/srv/SETTINGSDIST:ro
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
front:
|
||||
build:
|
||||
@ -146,7 +162,7 @@ services:
|
||||
- qa
|
||||
- receiver
|
||||
- dashboard
|
||||
- backend
|
||||
- checker
|
||||
- admin
|
||||
|
||||
volumes:
|
||||
@ -154,6 +170,7 @@ volumes:
|
||||
dashboard:
|
||||
files:
|
||||
htdocs:
|
||||
generator:
|
||||
pki:
|
||||
settings:
|
||||
settingsdist:
|
||||
|
@ -61,17 +61,28 @@ onboot:
|
||||
peer: veth-admin
|
||||
bindNS:
|
||||
net: /run/netns/fic-admin
|
||||
- name: backend-ip-setup
|
||||
- name: checker-ip-setup
|
||||
image: linuxkit/ip:c88e3272e3b12edec454e4720da8bb70a7655bc7
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.3/24 dev vethin-backend; ip link set vethin-backend up;" ]
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.3/24 dev vethin-checker; ip link set vethin-checker up;" ]
|
||||
net: new
|
||||
runtime:
|
||||
interfaces:
|
||||
- name: vethin-backend
|
||||
- name: vethin-checker
|
||||
add: veth
|
||||
peer: veth-backend
|
||||
peer: veth-checker
|
||||
bindNS:
|
||||
net: /run/netns/fic-backend
|
||||
net: /run/netns/fic-checker
|
||||
- name: generator-ip-setup
|
||||
image: linuxkit/ip:c88e3272e3b12edec454e4720da8bb70a7655bc7
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.5/24 dev vethin-generator; ip link set vethin-generator up;" ]
|
||||
net: new
|
||||
runtime:
|
||||
interfaces:
|
||||
- name: vethin-generator
|
||||
add: veth
|
||||
peer: veth-generator
|
||||
bindNS:
|
||||
net: /run/netns/fic-generator
|
||||
- name: mysql-ip-setup
|
||||
image: linuxkit/ip:c88e3272e3b12edec454e4720da8bb70a7655bc7
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.4/24 dev vethin-db; ip link set vethin-db up;" ]
|
||||
@ -85,7 +96,7 @@ onboot:
|
||||
net: /run/netns/db
|
||||
- name: bridge-setup
|
||||
image: linuxkit/ip:c88e3272e3b12edec454e4720da8bb70a7655bc7
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.1/24 dev br0; ip link set veth-admin master br0; ip link set veth-backend master br0; ip link set veth-db master br0; ip link set br0 up; ip link set veth-admin up; ip link set veth-backend up; ip link set veth-db up;" ]
|
||||
command: ["/bin/sh", "-c", "ip a add 172.17.0.1/24 dev br0; ip link set veth-admin master br0; ip link set veth-checker master br0; ip link set veth-generator master br0; ip link set veth-db master br0; ip link set br0 up; ip link set veth-admin up; ip link set veth-checker up; ip link set veth-generator up; ip link set veth-db up;" ]
|
||||
runtime:
|
||||
interfaces:
|
||||
- name: br0
|
||||
@ -173,11 +184,11 @@ services:
|
||||
- /var/lib/fic/raw_files:/mnt/fic
|
||||
- /var/lib/fic/dashboard:/srv/DASHBOARD
|
||||
- /var/lib/fic/files:/srv/FILES
|
||||
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
||||
- /var/lib/fic/pki:/srv/PKI
|
||||
- /var/lib/fic/teams:/srv/TEAMS
|
||||
- /var/lib/fic/settings:/srv/SETTINGS
|
||||
- /var/lib/fic/sync:/srv/SYNC
|
||||
- /var/lib/fic/submissions:/srv/submissions:ro
|
||||
- /var/lib/fic/teams:/srv/TEAMS:ro
|
||||
net: /run/netns/fic-admin
|
||||
pid: new
|
||||
ipc: new
|
||||
@ -186,10 +197,10 @@ services:
|
||||
mkdir:
|
||||
- /var/lib/fic/dashboard
|
||||
- /var/lib/fic/files
|
||||
- /var/lib/fic/generator
|
||||
- /var/lib/fic/raw_files
|
||||
- /var/lib/fic/pki
|
||||
- /var/lib/fic/settings
|
||||
- /var/lib/fic/sync
|
||||
- /var/lib/fic/submissions
|
||||
- /var/lib/fic/teams
|
||||
- name: fic-evdist
|
||||
@ -206,21 +217,24 @@ services:
|
||||
mkdir:
|
||||
- /var/lib/fic/settings
|
||||
- /var/lib/fic/settingsdist
|
||||
- name: fic-backend
|
||||
image: nemunaire/fic-backend:latest@sha256:12d3286cdbe6d18d284f21432b4eb92ce8ab9844982177562069bc0f9536c93b
|
||||
- name: fic-checker
|
||||
image: nemunaire/fic-checker:latest@sha256:12d3286cdbe6d18d284f21432b4eb92ce8ab9844982177562069bc0f9536c93b
|
||||
env:
|
||||
- MYSQL_HOST=db
|
||||
- MYSQL_PASSWORD=fic
|
||||
binds:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
- /var/lib/fic/teams:/srv/TEAMS
|
||||
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
||||
- /var/lib/fic/teams:/srv/TEAMS:ro
|
||||
- /var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro
|
||||
- /var/lib/fic/submissions:/srv/submissions
|
||||
net: /run/netns/fic-backend
|
||||
net: /run/netns/fic-checker
|
||||
pid: new
|
||||
ipc: new
|
||||
uts: new
|
||||
runtime:
|
||||
mkdir:
|
||||
- /var/lib/fic/generator
|
||||
- /var/lib/fic/settingsdist
|
||||
- /var/lib/fic/submissions
|
||||
- /var/lib/fic/teams
|
||||
@ -242,6 +256,26 @@ services:
|
||||
- /var/lib/fic/dashboard
|
||||
- /var/lib/fic/teams
|
||||
- /var/lib/fic/settingsdist
|
||||
- name: fic-generator
|
||||
image: nemunaire/fic-generator:latest@sha256:12d3286cdbe6d18d284f21432b4eb92ce8ab9844982177562069bc0f9536c93b
|
||||
command: ["/srv/generator", "-bind=/srv/GENERATOR/generator.socket"]
|
||||
env:
|
||||
- MYSQL_HOST=db
|
||||
- MYSQL_PASSWORD=fic
|
||||
binds:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
||||
- /var/lib/fic/teams:/srv/TEAMS
|
||||
- /var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro
|
||||
net: /run/netns/fic-generator
|
||||
pid: new
|
||||
ipc: new
|
||||
uts: new
|
||||
runtime:
|
||||
mkdir:
|
||||
- /var/lib/fic/generator
|
||||
- /var/lib/fic/settingsdist
|
||||
- /var/lib/fic/teams
|
||||
- name: fic-synchro
|
||||
image: nemunaire/rsync:a3d76b2dd0a9ad73be44dc77ad765b20d96a3285
|
||||
command: ["/bin/ash", "/root/synchro.sh"]
|
||||
|
14
flake.nix
14
flake.nix
@ -40,12 +40,12 @@
|
||||
subPackages = [ "admin" ];
|
||||
};
|
||||
|
||||
fic-backend = pkgs.buildGoModule {
|
||||
pname = "backend";
|
||||
fic-checker = pkgs.buildGoModule {
|
||||
pname = "checker";
|
||||
inherit version vendorSha256 overrideModAttrs;
|
||||
src = ./.;
|
||||
|
||||
subPackages = [ "backend" ];
|
||||
subPackages = [ "checker" ];
|
||||
};
|
||||
|
||||
fic-dashboard = pkgs.buildGoModule {
|
||||
@ -56,6 +56,14 @@
|
||||
subPackages = [ "dashboard" ];
|
||||
};
|
||||
|
||||
fic-generator = pkgs.buildGoModule {
|
||||
pname = "generator";
|
||||
inherit version vendorSha256 overrideModAttrs;
|
||||
src = ./.;
|
||||
|
||||
subPackages = [ "generator" ];
|
||||
};
|
||||
|
||||
fic-synchro = pkgs.writeShellApplication {
|
||||
name = "synchro";
|
||||
runtimeInputs = [ pkgs.rsync pkgs.openssh pkgs.coreutils ];
|
||||
|
1
generator/.gitignore
vendored
Normal file
1
generator/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
generator
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
@ -13,30 +14,14 @@ import (
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
)
|
||||
|
||||
type GenerateType int
|
||||
|
||||
const (
|
||||
GenPublic GenerateType = iota
|
||||
GenEvents
|
||||
GenTeams
|
||||
GenThemes
|
||||
)
|
||||
|
||||
type genStruct struct {
|
||||
Id string
|
||||
Type GenerateType
|
||||
}
|
||||
|
||||
var parallelJobs = runtime.NumCPU()
|
||||
var genTeamQueue chan *fic.Team
|
||||
var genQueue chan genStruct
|
||||
var genQueue chan fic.GenStruct
|
||||
var inQueueMutex sync.RWMutex
|
||||
var inGenQueue map[GenerateType]bool
|
||||
var inGenQueue map[fic.GenerateType]bool
|
||||
|
||||
func init() {
|
||||
genTeamQueue = make(chan *fic.Team)
|
||||
genQueue = make(chan genStruct)
|
||||
inGenQueue = map[GenerateType]bool{}
|
||||
genQueue = make(chan fic.GenStruct)
|
||||
inGenQueue = map[fic.GenerateType]bool{}
|
||||
}
|
||||
|
||||
func launchWorkers() {
|
||||
@ -46,7 +31,29 @@ func launchWorkers() {
|
||||
}
|
||||
}
|
||||
|
||||
func appendGenQueue(gs genStruct) {
|
||||
func enqueueHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var gs fic.GenStruct
|
||||
|
||||
dec := json.NewDecoder(r.Body)
|
||||
err := dec.Decode(&gs)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("POST /enqueue | %v", gs)
|
||||
|
||||
appendGenQueue(gs)
|
||||
http.Error(w, "OK", http.StatusOK)
|
||||
}
|
||||
|
||||
func appendGenQueue(gs fic.GenStruct) {
|
||||
if gs.Type == fic.GenTeam || gs.Type == fic.GenTeamIssues {
|
||||
genQueue <- gs
|
||||
return
|
||||
}
|
||||
|
||||
// Append only if not already in queue
|
||||
inQueueMutex.RLock()
|
||||
if v, ok := inGenQueue[gs.Type]; !ok || !v {
|
||||
inQueueMutex.RUnlock()
|
||||
@ -62,15 +69,11 @@ func appendGenQueue(gs genStruct) {
|
||||
}
|
||||
|
||||
func consumer() {
|
||||
for {
|
||||
var id string
|
||||
var err error
|
||||
|
||||
select {
|
||||
case gt := <-genTeamQueue:
|
||||
err = genTeamMyFile(gt)
|
||||
|
||||
case gs := <-genQueue:
|
||||
for {
|
||||
gs := <-genQueue
|
||||
id = gs.Id
|
||||
|
||||
inQueueMutex.Lock()
|
||||
@ -78,15 +81,18 @@ func consumer() {
|
||||
inQueueMutex.Unlock()
|
||||
|
||||
switch gs.Type {
|
||||
case GenPublic:
|
||||
case fic.GenPublic:
|
||||
err = genMyPublicFile()
|
||||
case GenEvents:
|
||||
case fic.GenEvents:
|
||||
err = genEventsFile()
|
||||
case GenTeams:
|
||||
case fic.GenTeam:
|
||||
err = genTeamMyFile(gs.TeamId)
|
||||
case fic.GenTeams:
|
||||
err = genTeamsFile()
|
||||
case GenThemes:
|
||||
case fic.GenThemes:
|
||||
err = genThemesFile()
|
||||
}
|
||||
case fic.GenTeamIssues:
|
||||
err = genTeamIssuesFile(gs.TeamId)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -96,18 +102,36 @@ func consumer() {
|
||||
}
|
||||
|
||||
// Generate issues.json for a given team
|
||||
func genTeamIssuesFile(team *fic.Team) error {
|
||||
func genTeamIssuesFile(teamid int64) error {
|
||||
team, err := fic.GetTeam(teamid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to GetTeam: %w", err)
|
||||
}
|
||||
|
||||
dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id))
|
||||
|
||||
my, err := team.MyIssueFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if my == nil {
|
||||
if _, err := os.Stat(path.Join(dirPath, "issues.json")); !os.IsNotExist(err) {
|
||||
err = os.Remove(path.Join(dirPath, "issues.json"))
|
||||
if err != nil {
|
||||
log.Printf("Unable to remove empty issues file: %s", path.Join(dirPath, "issues.json"))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if s, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
os.MkdirAll(dirPath, 0777)
|
||||
} else if !s.IsDir() {
|
||||
return fmt.Errorf("%s is not a directory", dirPath)
|
||||
}
|
||||
|
||||
if my, err := team.MyIssueFile(); err != nil {
|
||||
return err
|
||||
} else if j, err := json.Marshal(my); err != nil {
|
||||
if j, err := json.Marshal(my); err != nil {
|
||||
return err
|
||||
} else if err = ioutil.WriteFile(path.Join(dirPath, "issues.json"), j, 0644); err != nil {
|
||||
return err
|
||||
@ -117,7 +141,12 @@ func genTeamIssuesFile(team *fic.Team) error {
|
||||
}
|
||||
|
||||
// Generate my.json, wait.json and scores.json for a given team
|
||||
func genTeamMyFile(team *fic.Team) error {
|
||||
func genTeamMyFile(teamid int64) error {
|
||||
team, err := fic.GetTeam(teamid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to GetTeam: %w", err)
|
||||
}
|
||||
|
||||
dirPath := path.Join(TeamsDir, fmt.Sprintf("%d", team.Id))
|
||||
|
||||
if s, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||
@ -235,17 +264,17 @@ func genThemesFile() error {
|
||||
}
|
||||
|
||||
func genAll() {
|
||||
appendGenQueue(genStruct{Type: GenThemes})
|
||||
appendGenQueue(genStruct{Type: GenTeams})
|
||||
appendGenQueue(genStruct{Type: GenEvents})
|
||||
appendGenQueue(genStruct{Type: GenPublic})
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenThemes})
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenTeams})
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenEvents})
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenPublic})
|
||||
|
||||
if teams, err := fic.GetActiveTeams(); err != nil {
|
||||
log.Println("Team retrieval error: ", err)
|
||||
} else {
|
||||
for _, team := range teams {
|
||||
myteam := team // team is reused, we need to create a new variable here to store the value
|
||||
genTeamQueue <- myteam
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenTeam, TeamId: team.Id})
|
||||
appendGenQueue(fic.GenStruct{Type: fic.GenTeamIssues, TeamId: team.Id})
|
||||
}
|
||||
}
|
||||
}
|
151
generator/main.go
Normal file
151
generator/main.go
Normal file
@ -0,0 +1,151 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"srs.epita.fr/fic-server/libfic"
|
||||
"srs.epita.fr/fic-server/settings"
|
||||
)
|
||||
|
||||
var TeamsDir string
|
||||
|
||||
var ChStarted = false
|
||||
var lastRegeneration time.Time
|
||||
var skipInitialGeneration = false
|
||||
|
||||
func reloadSettings(config *settings.Settings) {
|
||||
fic.HintCoefficient = config.HintCurCoefficient
|
||||
fic.WChoiceCoefficient = config.WChoiceCurCoefficient
|
||||
fic.ExerciceCurrentCoefficient = config.ExerciceCurCoefficient
|
||||
ChStarted = config.Start.Unix() > 0 && time.Since(config.Start) >= 0
|
||||
if lastRegeneration != config.Generation || fic.PartialValidation != config.PartialValidation || fic.UnlockedChallengeDepth != config.UnlockedChallengeDepth || fic.UnlockedChallengeUpTo != config.UnlockedChallengeUpTo || fic.DisplayAllFlags != config.DisplayAllFlags || fic.FirstBlood != config.FirstBlood || fic.SubmissionCostBase != config.SubmissionCostBase || fic.SubmissionUniqueness != config.SubmissionUniqueness || fic.DiscountedFactor != config.DiscountedFactor {
|
||||
fic.PartialValidation = config.PartialValidation
|
||||
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
|
||||
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
|
||||
fic.DisplayAllFlags = config.DisplayAllFlags
|
||||
|
||||
fic.FirstBlood = config.FirstBlood
|
||||
fic.SubmissionCostBase = config.SubmissionCostBase
|
||||
fic.SubmissionUniqueness = config.SubmissionUniqueness
|
||||
fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient
|
||||
fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries
|
||||
fic.DiscountedFactor = config.DiscountedFactor
|
||||
|
||||
if !skipInitialGeneration {
|
||||
log.Println("Generating files...")
|
||||
go func() {
|
||||
genAll()
|
||||
log.Println("Full generation done")
|
||||
}()
|
||||
} else {
|
||||
skipInitialGeneration = false
|
||||
log.Println("Regeneration skipped by option.")
|
||||
}
|
||||
lastRegeneration = config.Generation
|
||||
} else {
|
||||
log.Println("No change found. Skipping regeneration.")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if v, exists := os.LookupEnv("FIC_BASEURL"); exists {
|
||||
fic.FilesDir = v + "files"
|
||||
} else {
|
||||
fic.FilesDir = "/files"
|
||||
}
|
||||
|
||||
var bind = flag.String("bind", "./GENERATOR/generator.socket", "Bind path or port/socket")
|
||||
var dsn = flag.String("dsn", fic.DSNGenerator(), "DSN to connect to the MySQL server")
|
||||
flag.StringVar(&settings.SettingsDir, "settings", "./SETTINGSDIST", "Base directory where load and save settings")
|
||||
flag.StringVar(&TeamsDir, "teams", "./TEAMS", "Base directory where save teams JSON files")
|
||||
flag.StringVar(&fic.FilesDir, "files", fic.FilesDir, "Request path prefix to reach files")
|
||||
flag.BoolVar(&skipInitialGeneration, "skipfullgeneration", skipInitialGeneration, "Skip the initial regeneration")
|
||||
flag.IntVar(¶llelJobs, "jobs", parallelJobs, "Number of generation workers")
|
||||
flag.Parse()
|
||||
|
||||
log.SetPrefix("[generator] ")
|
||||
|
||||
settings.SettingsDir = path.Clean(settings.SettingsDir)
|
||||
TeamsDir = path.Clean(TeamsDir)
|
||||
|
||||
launchWorkers()
|
||||
|
||||
log.Println("Opening DB...")
|
||||
if err := fic.DBInit(*dsn); err != nil {
|
||||
log.Fatal("Cannot open the database: ", err)
|
||||
}
|
||||
defer fic.DBClose()
|
||||
|
||||
// Load configuration
|
||||
settings.LoadAndWatchSettings(path.Join(settings.SettingsDir, settings.SettingsFile), reloadSettings)
|
||||
|
||||
// Register SIGUSR1, SIGUSR2
|
||||
interrupt1 := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt1, syscall.SIGUSR1)
|
||||
interrupt2 := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt2, syscall.SIGUSR2)
|
||||
|
||||
// Prepare graceful shutdown
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: *bind,
|
||||
}
|
||||
|
||||
http.HandleFunc("/enqueue", enqueueHandler)
|
||||
|
||||
// Serve pages
|
||||
go func() {
|
||||
if !strings.Contains(*bind, ":") {
|
||||
if _, err := os.Stat(*bind); !os.IsNotExist(err) {
|
||||
if err := os.Remove(*bind); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
os.MkdirAll(path.Dir(*bind), 0777)
|
||||
|
||||
unixListener, err := net.Listen("unix", *bind)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Fatal(srv.Serve(unixListener))
|
||||
} else if err := srv.ListenAndServe(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
log.Println(fmt.Sprintf("Ready, listening on %s", *bind))
|
||||
|
||||
// Wait shutdown signal
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-interrupt:
|
||||
break loop
|
||||
case <-interrupt1:
|
||||
log.Println("SIGUSR1 received, regenerating all files...")
|
||||
genAll()
|
||||
log.Println("SIGUSR1 treated.")
|
||||
case <-interrupt2:
|
||||
inQueueMutex.Lock()
|
||||
log.Printf("SIGUSR2 received, dumping statistics:\n parallelJobs: %d\n genQueue: %d\n Teams in queue: %v\n Challenge started: %v\n Last regeneration: %v\n", parallelJobs, len(genQueue), inGenQueue, ChStarted, lastRegeneration)
|
||||
inQueueMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
log.Print("The service is shutting down...")
|
||||
srv.Shutdown(context.Background())
|
||||
log.Println("done")
|
||||
}
|
18
libfic/generation.go
Normal file
18
libfic/generation.go
Normal file
@ -0,0 +1,18 @@
|
||||
package fic
|
||||
|
||||
type GenerateType int
|
||||
|
||||
const (
|
||||
GenPublic GenerateType = iota
|
||||
GenEvents
|
||||
GenTeam
|
||||
GenTeams
|
||||
GenThemes
|
||||
GenTeamIssues
|
||||
)
|
||||
|
||||
type GenStruct struct {
|
||||
Id string `json:"id"`
|
||||
Type GenerateType `json:"type"`
|
||||
TeamId int64 `json:"team_id,omitempty"`
|
||||
}
|
@ -3,9 +3,10 @@
|
||||
imports = [
|
||||
./db.nix
|
||||
./fic-admin.nix
|
||||
./fic-backend.nix
|
||||
./fic-checker.nix
|
||||
./fic-dashboard.nix
|
||||
./fic-evdist.nix
|
||||
./fic-generator.nix
|
||||
./fic-synchro.nix
|
||||
];
|
||||
|
||||
|
@ -27,13 +27,13 @@
|
||||
};
|
||||
volumes = [
|
||||
"/etc/hosts:/etc/hosts:ro"
|
||||
"/var/lib/fic/generator:/srv/GENERATOR:ro"
|
||||
"/var/lib/fic/raw_files:/mnt/fic"
|
||||
"/var/lib/fic/dashboard:/srv/DASHBOARD"
|
||||
"/var/lib/fic/files:/srv/FILES"
|
||||
"/var/lib/fic/pki:/srv/PKI"
|
||||
"/var/lib/fic/teams:/srv/TEAMS"
|
||||
"/var/lib/fic/teams:/srv/TEAMS:ro"
|
||||
"/var/lib/fic/settings:/srv/SETTINGS"
|
||||
"/var/lib/fic/sync:/srv/SYNC"
|
||||
"/var/lib/fic/submissions:/srv/submissions:ro"
|
||||
];
|
||||
};
|
||||
|
27
nixos/backend/fic-checker.nix
Normal file
27
nixos/backend/fic-checker.nix
Normal file
@ -0,0 +1,27 @@
|
||||
{ config, inputs, pkgs, ... }:
|
||||
{
|
||||
config.virtualisation.oci-containers.containers.fic-checker = {
|
||||
image = "fic-checker:latest";
|
||||
imageFile = pkgs.dockerTools.buildImage {
|
||||
name = "fic-checker";
|
||||
tag = "latest";
|
||||
created = "now";
|
||||
config = {
|
||||
Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-checker}/bin/checker" ];
|
||||
};
|
||||
};
|
||||
autoStart = true;
|
||||
environment = {
|
||||
MYSQL_HOST = "db";
|
||||
};
|
||||
workdir = "/srv";
|
||||
extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.41" ];
|
||||
volumes = [
|
||||
"/etc/hosts:/etc/hosts:ro"
|
||||
"/var/lib/fic/generator:/srv/GENERATOR:ro"
|
||||
"/var/lib/fic/teams:/srv/TEAMS:ro"
|
||||
"/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro"
|
||||
"/var/lib/fic/submissions:/srv/submissions"
|
||||
];
|
||||
};
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
{ config, inputs, pkgs, ... }:
|
||||
{
|
||||
config.virtualisation.oci-containers.containers.fic-backend = {
|
||||
image = "fic-backend:latest";
|
||||
config.virtualisation.oci-containers.containers.fic-generator = {
|
||||
image = "fic-generator:latest";
|
||||
imageFile = pkgs.dockerTools.buildImage {
|
||||
name = "fic-backend";
|
||||
name = "fic-generator";
|
||||
tag = "latest";
|
||||
created = "now";
|
||||
config = {
|
||||
Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-backend}/bin/backend" ];
|
||||
Cmd = [ "${inputs.ficpkgs.packages.x86_64-linux.fic-generator}/bin/generator" ];
|
||||
};
|
||||
};
|
||||
autoStart = true;
|
||||
@ -18,9 +18,9 @@
|
||||
extraOptions = [ "--network=phobos-lan" "--ip=172.18.0.41" ];
|
||||
volumes = [
|
||||
"/etc/hosts:/etc/hosts:ro"
|
||||
"/var/lib/fic/generator:/srv/GENERATOR"
|
||||
"/var/lib/fic/teams:/srv/TEAMS"
|
||||
"/var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro"
|
||||
"/var/lib/fic/submissions:/srv/submissions"
|
||||
];
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user