-
Notifications
You must be signed in to change notification settings - Fork 0
Invoker base #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invoker base #5
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package config | ||
|
|
||
| import ( | ||
| "os" | ||
|
|
||
| "github.com/xorcare/pointer" | ||
| "gopkg.in/yaml.v3" | ||
| ) | ||
|
|
||
| type Config struct { | ||
| Port int `yaml:"Port"` | ||
| Host *string `yaml:"Host,omitempty"` // leave empty for localhost | ||
|
|
||
| LogPath *string `yaml:"LogPath,omitempty"` | ||
| LogLevel *int `yaml:"LogLevel,omitempty"` | ||
|
|
||
| Invoker *InvokerConfig `yaml:"Invoker,omitempty"` | ||
| // TODO: Add instances here | ||
|
|
||
| DB DBConfig `yaml:"DB"` | ||
| // if instance is set up on server, leave connection empty | ||
| MasterConnection *Connection `yaml:"MasterConnection,omitempty"` | ||
| StorageConnection *Connection `yaml:"StorageConnection,omitempty"` | ||
| } | ||
|
|
||
| func ReadConfig(configPath string) *Config { | ||
| content, err := os.ReadFile(configPath) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| config := new(Config) | ||
| err = yaml.Unmarshal(content, config) | ||
| if err != nil { | ||
| panic(err) | ||
| } | ||
|
|
||
| fillInConfig(config) | ||
|
|
||
| return config | ||
| } | ||
|
|
||
| func fillInConfig(config *Config) { | ||
| if config.Host == nil { | ||
| config.Host = pointer.String("localhost") | ||
| } | ||
|
|
||
| fillInConnections(config) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package config | ||
|
|
||
| type Connection struct { | ||
| Address string `yaml:"Address"` | ||
| // TODO: Add authentification | ||
| } | ||
|
|
||
| func fillInConnections(config *Config) { | ||
| // TODO: Add auto connection creation if master or storageconn are set up | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package config | ||
|
|
||
| type DBConfig struct { | ||
| Dsn string `yaml:"dsn"` | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package config | ||
|
|
||
| import "github.com/xorcare/pointer" | ||
|
|
||
| type InvokerConfig struct { | ||
| Instances int `yaml:"Instances"` | ||
| QueueSize *int `yaml:"QueueSize,omitempty"` // default is 10 | ||
|
|
||
| CacheSize uint64 `yaml:"CacheSize"` | ||
| CachePath string `yaml:"CachePath"` | ||
| } | ||
|
|
||
| func fillInInvokerConfig(config *InvokerConfig) { | ||
| if config.QueueSize == nil { | ||
| config.QueueSize = pointer.Int(10) | ||
| } | ||
| if len(config.CachePath) == 0 { | ||
| panic("No invoker cache path specified") | ||
| } | ||
| if config.CacheSize == 0 { | ||
| panic("No invoker cache size specified") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package invokerconn | ||
|
|
||
| import ( | ||
| "testing_system/common/db/models" | ||
| ) | ||
|
|
||
| // This will be changed in later commits | ||
|
|
||
| const ( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. вообще мне кажется, что можно для "енумов" свои типы делать отдельные, чтобы не передать что-то не то (вот как в storageconn/structs.go)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ага, так и будет в следующем коммите |
||
| JobTypeCompile = iota | ||
| JobTypeTest | ||
| ) | ||
|
|
||
| type Job struct { | ||
| SubmitID uint `json:"submitID" binding:"required"` | ||
| JobType int `json:"jobType" binding:"required"` | ||
| Test int `json:"test"` | ||
|
|
||
| Submit *models.Submission `json:"submit,omitempty"` | ||
| Problem *models.Problem `json:"problem,omitempty"` | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package storageconn | ||
|
|
||
| import ( | ||
| "testing_system/common" | ||
| ) | ||
|
|
||
| type Connector struct { | ||
| // TODO: Add storage data loader | ||
| } | ||
|
|
||
| func NewStorageConnector(ts *common.TestingSystem) *Connector { | ||
| return nil | ||
| } | ||
|
|
||
| func (s *Connector) Download(request Request) *ResponseFiles { | ||
| return nil | ||
| } | ||
|
|
||
| func (s *Connector) Upload(request Request) *Response { | ||
| return nil | ||
| } | ||
|
|
||
| func (s *Connector) Delete(request Request) *Response { | ||
| return nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| package storageconn | ||
|
|
||
| //go:generate stringer -type=ResourceType | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "strconv" | ||
| "testing_system/lib/logger" | ||
| ) | ||
|
|
||
| type ResourceType int | ||
|
|
||
| const ( | ||
| SourceCode ResourceType = iota | ||
| CompiledBinary | ||
| Test | ||
| Checker | ||
| Interactor | ||
| // Will be increased | ||
| ) | ||
|
|
||
| type Request struct { | ||
| // Should be always specified | ||
| Resource ResourceType `json:"resource"` | ||
|
|
||
| // If resource is part of problem, ProblemID is used | ||
| ProblemID uint64 `json:"problemID"` | ||
| // If resource is part of submit, SubmitID is used | ||
| SubmitID uint64 `json:"submitID"` | ||
| // If resource is a test, TestID should be specified | ||
| TestID uint64 `json:"testID"` | ||
|
|
||
| // For any download, BaseFolder should be specified. The files with original filenames will be placed there | ||
| BaseFolder string `json:"-"` | ||
|
|
||
| // For uploads, FilePath or FilePaths should be specified (depending on whether the resource is single-file or not). | ||
| // Filename will be taken from filename inside the path. | ||
| FilePath string `json:"-"` | ||
| FilePaths []string `json:"-"` | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. почему не хранить всегда []string?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Это вообще сильно поменяю в следующем коммите, пока можно забить |
||
| } | ||
|
|
||
| func (s *Request) FillBaseFolder(parent string) { | ||
| s.BaseFolder = filepath.Join(parent, s.Resource.String()) | ||
| switch s.Resource { | ||
| case SourceCode, CompiledBinary: | ||
| s.BaseFolder = filepath.Join(s.BaseFolder, strconv.FormatUint(s.SubmitID, 10)) | ||
| case Checker, Interactor: | ||
| s.BaseFolder = filepath.Join(s.BaseFolder, strconv.FormatUint(s.ProblemID, 10)) | ||
| case Test: | ||
| s.BaseFolder = filepath.Join(s.BaseFolder, fmt.Sprintf("%d-%d", s.SubmitID, s.TestID)) | ||
| default: | ||
| logger.Panic("Can not fill base folder for storageconn request of type %s", s.Resource) | ||
| } | ||
| } | ||
|
|
||
| type Response struct { | ||
| R Request | ||
| Error error | ||
| } | ||
|
|
||
| type ResponseFiles struct { | ||
| Response | ||
| fileNames []string | ||
| Size uint64 | ||
| } | ||
|
|
||
| func (r *ResponseFiles) File() (string, bool) { | ||
| if len(r.fileNames) == 0 { | ||
| return "", false | ||
| } | ||
| return filepath.Join(r.R.BaseFolder, r.fileNames[0]), true | ||
| } | ||
|
|
||
| func (r *ResponseFiles) Get(fileName string) (string, bool) { | ||
| for _, f := range r.fileNames { | ||
| if fileName == f { | ||
| return filepath.Join(r.R.BaseFolder, f), true | ||
| } | ||
| } | ||
| return "", false | ||
| } | ||
|
|
||
| func (r *ResponseFiles) CleanUp() { | ||
| if r.Error != nil { | ||
| return | ||
| } | ||
| if len(r.R.BaseFolder) == 0 { | ||
| return | ||
| } | ||
| err := os.RemoveAll(r.R.BaseFolder) | ||
| if err != nil { | ||
| logger.Panic("Can not remove resource folder, error: %s", err.Error()) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package common | ||
|
|
||
| import ( | ||
| "context" | ||
| "github.com/gin-gonic/gin" | ||
| "gorm.io/gorm" | ||
| "net/http" | ||
| "os/signal" | ||
| "strconv" | ||
| "sync" | ||
| "syscall" | ||
| "testing_system/common/config" | ||
| "testing_system/common/db" | ||
| "testing_system/lib/logger" | ||
| ) | ||
|
|
||
| type TestingSystem struct { | ||
| Config *config.Config | ||
| Router *gin.Engine | ||
| DB *gorm.DB | ||
|
|
||
| processes []func() | ||
| defers []func() | ||
|
|
||
| StopCtx context.Context | ||
| stopFunc context.CancelFunc | ||
| stopWG sync.WaitGroup | ||
| } | ||
|
|
||
| func InitTestingSystem(configPath string) *TestingSystem { | ||
| ts := &TestingSystem{ | ||
| Config: config.ReadConfig(configPath), | ||
| } | ||
| logger.InitLogger(ts.Config) | ||
| ts.Router = gin.Default() // TODO: Add router options (e.g debug) | ||
|
|
||
| var err error | ||
| ts.DB, err = db.NewDB(ts.Config.DB) | ||
| if err != nil { | ||
| logger.Panic("Can not set up db connection, error: %s", err.Error()) | ||
| } | ||
|
|
||
| return ts | ||
| } | ||
|
|
||
| func (ts *TestingSystem) AddProcess(f func()) { | ||
| ts.processes = append(ts.processes, f) | ||
| } | ||
|
|
||
| func (ts *TestingSystem) AddDefer(f func()) { | ||
| ts.defers = append(ts.defers, f) | ||
| } | ||
|
|
||
| func (ts *TestingSystem) Run() { | ||
| var ctx context.Context | ||
| var cancel context.CancelFunc | ||
| ctx, ts.stopFunc = context.WithCancel(context.Background()) | ||
| ts.StopCtx, cancel = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) | ||
| defer cancel() | ||
|
|
||
| for _, process := range ts.processes { | ||
| ts.runProcess(process) | ||
| } | ||
|
|
||
| ts.runServer() | ||
|
|
||
| ts.stopWG.Wait() | ||
|
|
||
| for _, d := range ts.defers { | ||
| d() | ||
| } | ||
| } | ||
|
|
||
| func (ts *TestingSystem) runServer() { | ||
| addr := ":" + strconv.Itoa(ts.Config.Port) | ||
| if ts.Config.Host != nil { | ||
| addr = *ts.Config.Host + addr | ||
| } | ||
| logger.Info("Starting server at " + addr) | ||
| server := http.Server{ | ||
| Addr: addr, | ||
| Handler: ts.Router, | ||
| } | ||
| go func() { | ||
| <-ts.StopCtx.Done() | ||
| logger.Info("Shutting down server") | ||
| server.Shutdown(context.Background()) | ||
| }() | ||
| server.ListenAndServe() | ||
| } | ||
|
|
||
| func (ts *TestingSystem) runProcess(f func()) { | ||
| ts.stopWG.Add(1) | ||
| defer func() { | ||
| v := recover() | ||
| if v != nil { | ||
| logger.Error("One process got panic, shutting down all processes gracefully") | ||
| ts.stopFunc() | ||
| } | ||
| ts.stopWG.Done() | ||
| }() | ||
|
|
||
| f() | ||
| } |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
в константу бы
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
хз на самом деле, тут по сути все эти fillIN это одна большая константная функция, там тогда константы на каждое поле надо делать и читать это будет невозможно. Тут как раз удобно что ты быстренько на код посмотрел и понял какие дефолт значения