feat: run single command in sandbox
This commit is contained in:
		
							parent
							
								
									d1317cde79
								
							
						
					
					
						commit
						d5a104e7c4
					
				
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,10 +1,16 @@ | ||||||
| # JOJ3 | # JOJ3 | ||||||
| 
 | 
 | ||||||
|  | In order to register sandbox executor, you need to run go-judge before running this program. | ||||||
|  | 
 | ||||||
| ```bash | ```bash | ||||||
| $ make clean && make && ./build/joj3 | $ make clean && make && ./build/joj3 | ||||||
| rm -rf ./build/* | rm -rf ./build/* | ||||||
| rm -rf *.out | rm -rf *.out | ||||||
| go build -o ./build/joj3 ./cmd/joj3 | go build -o ./build/joj3 ./cmd/joj3 | ||||||
| stage 0: score: 100, comment: dummy comment for stage 0 | 2024/03/03 18:01:11 INFO stage start name="stage 0" | ||||||
| stage 1: score: 101, comment: dummy comment for stage 1 | 2024/03/03 18:01:11 INFO sandbox run cmd="{Args:[ls] Env:[PATH=/usr/bin:/bin] Files:[0xc0000aa340 0xc0000aa380 0xc0000aa3c0] CPULimit:10000000000 RealCPULimit:0 ClockLimit:0 MemoryLimit:104857600 StackLimit:0 ProcLimit:50 CPURateLimit:0 CPUSetLimit: CopyIn:map[] CopyOut:[stdout stderr] CopyOutCached:[] CopyOutMax:0 CopyOutDir: TTY:false StrictMemoryLimit:false DataSegmentLimit:false AddressSpaceLimit:false}" | ||||||
|  | 2024/03/03 18:01:11 INFO sandbox run ret="results:{status:Accepted time:1162000 runTime:3847400 memory:14929920 files:{key:\"stderr\" value:\"\"} files:{key:\"stdout\" value:\"stderr\\nstdout\\n\"}}" | ||||||
|  | 2024/03/03 18:01:11 INFO executor done result="{Status:Accepted ExitStatus:0 Error: Time:1.162ms RunTime:3.8474ms Memory:14.2 MiB Files:map[stderr:len:0 stdout:len:14] FileIDs:map[] FileError:[]}" | ||||||
|  | 2024/03/03 18:01:11 INFO parser done result="&{Score:100 Comment:dummy comment for stage 0, executor status: run time: 3847400 ns, memory: 14929920 bytes}" | ||||||
|  | stage 0: score: 100, comment: dummy comment for stage 0, executor status: run time: 3847400 ns, memory: 14929920 bytes | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | @ -10,28 +10,30 @@ import ( | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	tomlConfig := ` | 	tomlConfig := ` | ||||||
| 	[[stages]] | [[stages]] | ||||||
| 	name = "stage 0" | name = "stage 0" | ||||||
| 	  [stages.executor] | 	[stages.executor] | ||||||
| 	  name = "dummy" | 	name = "sandbox" | ||||||
| 	  [stages.executor.with] | 	[stages.executor.with] | ||||||
| 	    args = [ "/usr/bin/cat", "/dev/null" ] | 	args = [ "ls" ] | ||||||
| 	  [stages.parser] | 	env = [ "PATH=/usr/bin:/bin" ] | ||||||
| 	  name = "dummy" | 	cpuLimit = 10_000_000_000 | ||||||
| 	  [stages.parser.with] | 	memoryLimit = 104_857_600 | ||||||
| 	    score = 100 | 	procLimit = 50 | ||||||
| 	    comment = "dummy comment for stage 0" | 	copyOut = [ "stdout", "stderr" ] | ||||||
| 	[[stages]] | 		[[stages.executor.with.files]] | ||||||
| 	name = "stage 1" | 		content = "" | ||||||
| 	  [stages.executor] | 		[[stages.executor.with.files]] | ||||||
| 	  name = "dummy" | 		name = "stdout" | ||||||
| 	  [stages.executor.with] | 		max = 4_096 | ||||||
| 	    args = [ "/usr/bin/cat", "/dev/null" ] | 		[[stages.executor.with.files]] | ||||||
| 	  [stages.parser] | 		name = "stderr" | ||||||
| 	  name = "dummy" | 		max = 4_096 | ||||||
| 	  [stages.parser.with] | 	[stages.parser] | ||||||
| 	    score = 101 | 	name = "dummy" | ||||||
| 	    comment = "dummy comment for stage 1" | 	[stages.parser.with] | ||||||
|  | 	score = 100 | ||||||
|  | 	comment = "dummy comment for stage 0" | ||||||
| 	` | 	` | ||||||
| 	stages := stage.ParseStages(tomlConfig) | 	stages := stage.ParseStages(tomlConfig) | ||||||
| 	results := stage.Run(stages) | 	results := stage.Run(stages) | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							|  | @ -5,9 +5,15 @@ go 1.22.0 | ||||||
| require ( | require ( | ||||||
| 	github.com/criyle/go-judge v1.8.1 | 	github.com/criyle/go-judge v1.8.1 | ||||||
| 	github.com/mitchellh/mapstructure v1.5.0 | 	github.com/mitchellh/mapstructure v1.5.0 | ||||||
|  | 	google.golang.org/grpc v1.61.0 | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
|  | 	github.com/golang/protobuf v1.5.3 // indirect | ||||||
|  | 	golang.org/x/net v0.20.0 // indirect | ||||||
|  | 	golang.org/x/text v0.14.0 // indirect | ||||||
|  | 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe // indirect | ||||||
|  | 	google.golang.org/protobuf v1.32.0 // indirect | ||||||
| 	github.com/creack/pty v1.1.21 // indirect | 	github.com/creack/pty v1.1.21 // indirect | ||||||
| 	github.com/criyle/go-sandbox v0.10.1 // indirect | 	github.com/criyle/go-sandbox v0.10.1 // indirect | ||||||
| 	github.com/pelletier/go-toml/v2 v2.1.1 | 	github.com/pelletier/go-toml/v2 v2.1.1 | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								go.sum
									
									
									
									
									
								
							|  | @ -7,6 +7,10 @@ github.com/criyle/go-sandbox v0.10.1/go.mod h1:ivPw/HEh5unxVRlXJxCgkgTCuy+cxTkQD | ||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= | ||||||
|  | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= | ||||||
|  | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= | ||||||
|  | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||||
| github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= | github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= | ||||||
|  | @ -20,10 +24,23 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ | ||||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||||
| github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= | ||||||
| github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||||||
|  | golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= | ||||||
|  | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= | ||||||
| golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= | ||||||
| golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
| golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= | ||||||
| golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
|  | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= | ||||||
|  | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe h1:bQnxqljG/wqi4NTXu2+DJ3n7APcEA882QZ1JvhQAq9o= | ||||||
|  | google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= | ||||||
|  | google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= | ||||||
|  | google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= | ||||||
|  | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||||
|  | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||||
|  | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= | ||||||
|  | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ package executors | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy" | 	_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/dummy" | ||||||
|  | 	_ "focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/executors/sandbox" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // this file does nothing but imports to ensure all the init() functions
 | // this file does nothing but imports to ensure all the init() functions
 | ||||||
|  |  | ||||||
|  | @ -7,8 +7,8 @@ import ( | ||||||
| 
 | 
 | ||||||
| type Dummy struct{} | type Dummy struct{} | ||||||
| 
 | 
 | ||||||
| func (e *Dummy) Run(model.Cmd) model.Result { | func (e *Dummy) Run(model.Cmd) (*model.Result, error) { | ||||||
| 	return model.Result{ | 	return &model.Result{ | ||||||
| 		Status:     model.Status(envexec.StatusInvalid), | 		Status:     model.Status(envexec.StatusInvalid), | ||||||
| 		ExitStatus: 0, | 		ExitStatus: 0, | ||||||
| 		Error:      "I'm a dummy", | 		Error:      "I'm a dummy", | ||||||
|  | @ -17,5 +17,5 @@ func (e *Dummy) Run(model.Cmd) model.Result { | ||||||
| 		RunTime:    0, | 		RunTime:    0, | ||||||
| 		Files:      map[string]string{}, | 		Files:      map[string]string{}, | ||||||
| 		FileIDs:    map[string]string{}, | 		FileIDs:    map[string]string{}, | ||||||
| 	} | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										176
									
								
								internal/executors/sandbox/convert.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								internal/executors/sandbox/convert.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,176 @@ | ||||||
|  | package sandbox | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/criyle/go-judge/cmd/go-judge/model" | ||||||
|  | 	"github.com/criyle/go-judge/pb" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // copied from https://github.com/criyle/go-judge/blob/master/cmd/go-judge-shell/grpc.go
 | ||||||
|  | func convertPBCmd(cmd []model.Cmd) []*pb.Request_CmdType { | ||||||
|  | 	var ret []*pb.Request_CmdType | ||||||
|  | 	for _, c := range cmd { | ||||||
|  | 		ret = append(ret, &pb.Request_CmdType{ | ||||||
|  | 			Args:              c.Args, | ||||||
|  | 			Env:               c.Env, | ||||||
|  | 			Tty:               c.TTY, | ||||||
|  | 			Files:             convertPBFiles(c.Files), | ||||||
|  | 			CpuTimeLimit:      c.CPULimit, | ||||||
|  | 			ClockTimeLimit:    c.ClockLimit, | ||||||
|  | 			MemoryLimit:       c.MemoryLimit, | ||||||
|  | 			StackLimit:        c.StackLimit, | ||||||
|  | 			ProcLimit:         c.ProcLimit, | ||||||
|  | 			CpuRateLimit:      c.CPURateLimit, | ||||||
|  | 			CpuSetLimit:       c.CPUSetLimit, | ||||||
|  | 			DataSegmentLimit:  c.DataSegmentLimit, | ||||||
|  | 			AddressSpaceLimit: c.AddressSpaceLimit, | ||||||
|  | 			CopyIn:            convertPBCopyIn(c.CopyIn), | ||||||
|  | 			CopyOut:           convertPBCopyOut(c.CopyOut), | ||||||
|  | 			CopyOutCached:     convertPBCopyOut(c.CopyOutCached), | ||||||
|  | 			CopyOutMax:        c.CopyOutMax, | ||||||
|  | 			CopyOutDir:        c.CopyOutDir, | ||||||
|  | 			Symlinks:          convertSymlink(c.CopyIn), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBCopyIn(copyIn map[string]model.CmdFile) map[string]*pb.Request_File { | ||||||
|  | 	rt := make(map[string]*pb.Request_File, len(copyIn)) | ||||||
|  | 	for k, i := range copyIn { | ||||||
|  | 		if i.Symlink != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		rt[k] = convertPBFile(i) | ||||||
|  | 	} | ||||||
|  | 	return rt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBCopyOut(copyOut []string) []*pb.Request_CmdCopyOutFile { | ||||||
|  | 	rt := make([]*pb.Request_CmdCopyOutFile, 0) | ||||||
|  | 	for _, n := range copyOut { | ||||||
|  | 		optional := false | ||||||
|  | 		if strings.HasSuffix(n, "?") { | ||||||
|  | 			optional = true | ||||||
|  | 			n = strings.TrimSuffix(n, "?") | ||||||
|  | 		} | ||||||
|  | 		rt = append(rt, &pb.Request_CmdCopyOutFile{ | ||||||
|  | 			Name:     n, | ||||||
|  | 			Optional: optional, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return rt | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertSymlink(copyIn map[string]model.CmdFile) map[string]string { | ||||||
|  | 	ret := make(map[string]string) | ||||||
|  | 	for k, v := range copyIn { | ||||||
|  | 		if v.Symlink == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		ret[k] = *v.Symlink | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBFiles(files []*model.CmdFile) []*pb.Request_File { | ||||||
|  | 	var ret []*pb.Request_File | ||||||
|  | 	for _, f := range files { | ||||||
|  | 		if f == nil { | ||||||
|  | 			ret = append(ret, nil) | ||||||
|  | 		} else { | ||||||
|  | 			ret = append(ret, convertPBFile(*f)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBFile(i model.CmdFile) *pb.Request_File { | ||||||
|  | 	switch { | ||||||
|  | 	case i.Src != nil: | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_Local{Local: &pb.Request_LocalFile{Src: *i.Src}}} | ||||||
|  | 	case i.Content != nil: | ||||||
|  | 		s := strToBytes(*i.Content) | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_Memory{Memory: &pb.Request_MemoryFile{Content: s}}} | ||||||
|  | 	case i.FileID != nil: | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_Cached{Cached: &pb.Request_CachedFile{FileID: *i.FileID}}} | ||||||
|  | 	case i.Name != nil && i.Max != nil: | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_Pipe{Pipe: &pb.Request_PipeCollector{Name: *i.Name, Max: *i.Max, Pipe: i.Pipe}}} | ||||||
|  | 	case i.StreamIn: | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_StreamIn{}} | ||||||
|  | 	case i.StreamOut: | ||||||
|  | 		return &pb.Request_File{File: &pb.Request_File_StreamOut{}} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBPipeMapping(pm []model.PipeMap) []*pb.Request_PipeMap { | ||||||
|  | 	var ret []*pb.Request_PipeMap | ||||||
|  | 	for _, p := range pm { | ||||||
|  | 		ret = append(ret, &pb.Request_PipeMap{ | ||||||
|  | 			In:    convertPBPipeIndex(p.In), | ||||||
|  | 			Out:   convertPBPipeIndex(p.Out), | ||||||
|  | 			Name:  p.Name, | ||||||
|  | 			Proxy: p.Proxy, | ||||||
|  | 			Max:   uint64(p.Max), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBPipeIndex(pi model.PipeIndex) *pb.Request_PipeMap_PipeIndex { | ||||||
|  | 	return &pb.Request_PipeMap_PipeIndex{Index: int32(pi.Index), Fd: int32(pi.Fd)} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBResult(res []*pb.Response_Result) []model.Result { | ||||||
|  | 	var ret []model.Result | ||||||
|  | 	for _, r := range res { | ||||||
|  | 		ret = append(ret, model.Result{ | ||||||
|  | 			Status:     model.Status(r.Status), | ||||||
|  | 			ExitStatus: int(r.ExitStatus), | ||||||
|  | 			Error:      r.Error, | ||||||
|  | 			Time:       r.Time, | ||||||
|  | 			RunTime:    r.RunTime, | ||||||
|  | 			Memory:     r.Memory, | ||||||
|  | 			Files:      convertFiles(r.Files), | ||||||
|  | 			Buffs:      r.Files, | ||||||
|  | 			FileIDs:    r.FileIDs, | ||||||
|  | 			FileError:  convertPBFileError(r.FileError), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertFiles(buf map[string][]byte) map[string]string { | ||||||
|  | 	ret := make(map[string]string, len(buf)) | ||||||
|  | 	for k, v := range buf { | ||||||
|  | 		ret[k] = byteArrayToString(v) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBRequest(req *model.Request) *pb.StreamRequest { | ||||||
|  | 	ret := &pb.StreamRequest{ | ||||||
|  | 		Request: &pb.StreamRequest_ExecRequest{ | ||||||
|  | 			ExecRequest: &pb.Request{ | ||||||
|  | 				RequestID:   req.RequestID, | ||||||
|  | 				Cmd:         convertPBCmd(req.Cmd), | ||||||
|  | 				PipeMapping: convertPBPipeMapping(req.PipeMapping), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func convertPBFileError(fe []*pb.Response_FileError) []model.FileError { | ||||||
|  | 	var ret []model.FileError | ||||||
|  | 	for _, v := range fe { | ||||||
|  | 		ret = append(ret, model.FileError{ | ||||||
|  | 			Name:    v.Name, | ||||||
|  | 			Type:    model.FileErrorType(v.Type), | ||||||
|  | 			Message: v.Message, | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 	return ret | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								internal/executors/sandbox/executor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								internal/executors/sandbox/executor.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,28 @@ | ||||||
|  | package sandbox | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 
 | ||||||
|  | 	"github.com/criyle/go-judge/cmd/go-judge/model" | ||||||
|  | 	"github.com/criyle/go-judge/pb" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | type Sandbox struct { | ||||||
|  | 	execClient pb.ExecutorClient | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *Sandbox) Run(cmd model.Cmd) (*model.Result, error) { | ||||||
|  | 	slog.Info("sandbox run", "cmd", cmd) | ||||||
|  | 	req := &pb.Request{Cmd: convertPBCmd([]model.Cmd{cmd})} | ||||||
|  | 	ret, err := e.execClient.Exec(context.TODO(), req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if ret.Error != "" { | ||||||
|  | 		return nil, fmt.Errorf("compile error: %s", ret.Error) | ||||||
|  | 	} | ||||||
|  | 	slog.Info("sandbox run", "ret", ret) | ||||||
|  | 	return &convertPBResult(ret.Results)[0], nil | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								internal/executors/sandbox/grpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								internal/executors/sandbox/grpc.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | ||||||
|  | package sandbox | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
|  | 	"github.com/criyle/go-judge/pb" | ||||||
|  | 	"google.golang.org/grpc" | ||||||
|  | 	"google.golang.org/grpc/credentials" | ||||||
|  | 	"google.golang.org/grpc/credentials/insecure" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // copied from https://github.com/criyle/go-judger-demo/blob/master/apigateway/main.go
 | ||||||
|  | func createExecClient(execServer, token string) pb.ExecutorClient { | ||||||
|  | 	conn, err := createGRPCConnection(execServer, token) | ||||||
|  | 	if err != nil { | ||||||
|  | 		slog.Error("gRPC connection", "error", err) | ||||||
|  | 		os.Exit(1) | ||||||
|  | 	} | ||||||
|  | 	return pb.NewExecutorClient(conn) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func createGRPCConnection(addr, token string) (*grpc.ClientConn, error) { | ||||||
|  | 	opts := []grpc.DialOption{ | ||||||
|  | 		grpc.WithTransportCredentials(insecure.NewCredentials()), | ||||||
|  | 	} | ||||||
|  | 	if token != "" { | ||||||
|  | 		opts = append(opts, grpc.WithPerRPCCredentials(newTokenAuth(token))) | ||||||
|  | 	} | ||||||
|  | 	return grpc.Dial(addr, opts...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type tokenAuth struct { | ||||||
|  | 	token string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newTokenAuth(token string) credentials.PerRPCCredentials { | ||||||
|  | 	return &tokenAuth{token: token} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Return value is mapped to request headers.
 | ||||||
|  | func (t *tokenAuth) GetRequestMetadata(ctx context.Context, in ...string) ( | ||||||
|  | 	map[string]string, error, | ||||||
|  | ) { | ||||||
|  | 	return map[string]string{ | ||||||
|  | 		"authorization": "Bearer " + t.token, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (*tokenAuth) RequireTransportSecurity() bool { | ||||||
|  | 	return false | ||||||
|  | } | ||||||
							
								
								
									
										14
									
								
								internal/executors/sandbox/meta.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								internal/executors/sandbox/meta.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,14 @@ | ||||||
|  | package sandbox | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var name = "sandbox" | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	stage.RegisterExecutor(name, &Sandbox{ | ||||||
|  | 		// TODO: read from config
 | ||||||
|  | 		execClient: createExecClient("localhost:5051", ""), | ||||||
|  | 	}) | ||||||
|  | } | ||||||
							
								
								
									
										12
									
								
								internal/executors/sandbox/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								internal/executors/sandbox/util.go
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | ||||||
|  | package sandbox | ||||||
|  | 
 | ||||||
|  | import "unsafe" | ||||||
|  | 
 | ||||||
|  | // faster with no memory copy
 | ||||||
|  | func strToBytes(s string) []byte { | ||||||
|  | 	return unsafe.Slice(unsafe.StringData(s), len(s)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func byteArrayToString(buf []byte) string { | ||||||
|  | 	return *(*string)(unsafe.Pointer(&buf)) | ||||||
|  | } | ||||||
|  | @ -1,6 +1,9 @@ | ||||||
| package dummy | package dummy | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log/slog" | ||||||
|  | 
 | ||||||
| 	"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" | 	"focs.ji.sjtu.edu.cn/git/FOCS-dev/JOJ3/internal/stage" | ||||||
| 	"github.com/criyle/go-judge/cmd/go-judge/model" | 	"github.com/criyle/go-judge/cmd/go-judge/model" | ||||||
| 	"github.com/mitchellh/mapstructure" | 	"github.com/mitchellh/mapstructure" | ||||||
|  | @ -13,14 +16,20 @@ type Config struct { | ||||||
| 
 | 
 | ||||||
| type Dummy struct{} | type Dummy struct{} | ||||||
| 
 | 
 | ||||||
| func (e *Dummy) Run(result model.Result, configAny any) stage.ParserResult { | func (e *Dummy) Run(result *model.Result, configAny any) ( | ||||||
|  | 	*stage.ParserResult, error, | ||||||
|  | ) { | ||||||
| 	var config Config | 	var config Config | ||||||
| 	err := mapstructure.Decode(configAny, &config) | 	err := mapstructure.Decode(configAny, &config) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		slog.Error("failed to decode config", "err", err) | ||||||
| 	} | 		return nil, err | ||||||
| 	return stage.ParserResult{ |  | ||||||
| 		Score:   config.Score, |  | ||||||
| 		Comment: config.Comment, |  | ||||||
| 	} | 	} | ||||||
|  | 	return &stage.ParserResult{ | ||||||
|  | 		Score: config.Score, | ||||||
|  | 		Comment: fmt.Sprintf( | ||||||
|  | 			"%s, executor status: run time: %d ns, memory: %d bytes", | ||||||
|  | 			config.Comment, result.RunTime, result.Memory, | ||||||
|  | 		), | ||||||
|  | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import ( | ||||||
| var executorMap = map[string]Executor{} | var executorMap = map[string]Executor{} | ||||||
| 
 | 
 | ||||||
| type Executor interface { | type Executor interface { | ||||||
| 	Run(model.Cmd) model.Result | 	Run(model.Cmd) (*model.Result, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func RegisterExecutor(name string, executor Executor) { | func RegisterExecutor(name string, executor Executor) { | ||||||
|  |  | ||||||
|  | @ -6,8 +6,10 @@ import ( | ||||||
| 
 | 
 | ||||||
| type Stage struct { | type Stage struct { | ||||||
| 	Name         string | 	Name         string | ||||||
|  | 	ExecutorName string | ||||||
| 	Executor     Executor | 	Executor     Executor | ||||||
| 	ExecutorCmd  model.Cmd | 	ExecutorCmd  model.Cmd | ||||||
|  | 	ParserName   string | ||||||
| 	Parser       Parser | 	Parser       Parser | ||||||
| 	ParserConfig any | 	ParserConfig any | ||||||
| } | } | ||||||
|  | @ -19,7 +21,7 @@ type ParserResult struct { | ||||||
| 
 | 
 | ||||||
| type StageResult struct { | type StageResult struct { | ||||||
| 	Name string | 	Name string | ||||||
| 	ParserResult | 	*ParserResult | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type StagesConfig struct { | type StagesConfig struct { | ||||||
|  | @ -27,7 +29,7 @@ type StagesConfig struct { | ||||||
| 		Name     string | 		Name     string | ||||||
| 		Executor struct { | 		Executor struct { | ||||||
| 			Name string | 			Name string | ||||||
| 			With interface{} | 			With model.Cmd | ||||||
| 		} | 		} | ||||||
| 		Parser struct { | 		Parser struct { | ||||||
| 			Name string | 			Name string | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import "github.com/criyle/go-judge/cmd/go-judge/model" | ||||||
| var parserMap = map[string]Parser{} | var parserMap = map[string]Parser{} | ||||||
| 
 | 
 | ||||||
| type Parser interface { | type Parser interface { | ||||||
| 	Run(model.Result, any) ParserResult | 	Run(*model.Result, any) (*ParserResult, error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func RegisterParser(name string, parser Parser) { | func RegisterParser(name string, parser Parser) { | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
| package stage | package stage | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/criyle/go-judge/cmd/go-judge/model" | 	"log/slog" | ||||||
|  | 	"os" | ||||||
|  | 
 | ||||||
| 	"github.com/pelletier/go-toml/v2" | 	"github.com/pelletier/go-toml/v2" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -9,14 +11,17 @@ func ParseStages(tomlConfig string) []Stage { | ||||||
| 	var stagesConfig StagesConfig | 	var stagesConfig StagesConfig | ||||||
| 	err := toml.Unmarshal([]byte(tomlConfig), &stagesConfig) | 	err := toml.Unmarshal([]byte(tomlConfig), &stagesConfig) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic(err) | 		slog.Error("parse stages config", "error", err) | ||||||
|  | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| 	stages := []Stage{} | 	stages := []Stage{} | ||||||
| 	for _, stage := range stagesConfig.Stages { | 	for _, stage := range stagesConfig.Stages { | ||||||
| 		stages = append(stages, Stage{ | 		stages = append(stages, Stage{ | ||||||
| 			Name:         stage.Name, | 			Name:         stage.Name, | ||||||
|  | 			ExecutorName: stage.Executor.Name, | ||||||
| 			Executor:     executorMap[stage.Executor.Name], | 			Executor:     executorMap[stage.Executor.Name], | ||||||
| 			ExecutorCmd:  model.Cmd{}, | 			ExecutorCmd:  stage.Executor.With, | ||||||
|  | 			ParserName:   stage.Parser.Name, | ||||||
| 			Parser:       parserMap[stage.Parser.Name], | 			Parser:       parserMap[stage.Parser.Name], | ||||||
| 			ParserConfig: stage.Parser.With, | 			ParserConfig: stage.Parser.With, | ||||||
| 		}) | 		}) | ||||||
|  | @ -27,8 +32,19 @@ func ParseStages(tomlConfig string) []Stage { | ||||||
| func Run(stages []Stage) []StageResult { | func Run(stages []Stage) []StageResult { | ||||||
| 	var parserResults []StageResult | 	var parserResults []StageResult | ||||||
| 	for _, stage := range stages { | 	for _, stage := range stages { | ||||||
| 		executorResult := stage.Executor.Run(stage.ExecutorCmd) | 		slog.Info("stage start", "name", stage.Name) | ||||||
| 		parserResult := stage.Parser.Run(executorResult, stage.ParserConfig) | 		executorResult, err := stage.Executor.Run(stage.ExecutorCmd) | ||||||
|  | 		if err != nil { | ||||||
|  | 			slog.Error("executor error", "name", stage.ExecutorName, "error", err) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		slog.Info("executor done", "result", executorResult) | ||||||
|  | 		parserResult, err := stage.Parser.Run(executorResult, stage.ParserConfig) | ||||||
|  | 		if err != nil { | ||||||
|  | 			slog.Error("parser error", "name", stage.ExecutorName, "error", err) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		slog.Info("parser done", "result", parserResult) | ||||||
| 		parserResults = append(parserResults, StageResult{ | 		parserResults = append(parserResults, StageResult{ | ||||||
| 			Name:         stage.Name, | 			Name:         stage.Name, | ||||||
| 			ParserResult: parserResult, | 			ParserResult: parserResult, | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user