aboutsummaryrefslogtreecommitdiffstats
path: root/syz-cluster/pkg/controller/api.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2025-02-13 13:59:19 +0100
committerAleksandr Nogikh <nogikh@google.com>2025-02-14 13:40:12 +0000
commiteaf86f3f4dc8a7190abf09fe840e20bcf83709d8 (patch)
treec28d030a8923833ffc39005b1a57946cd48fea62 /syz-cluster/pkg/controller/api.go
parent59c86b9e1c7a0f91fbb1b680676f33b4cc7bf137 (diff)
syz-cluster/controller: move the API server to pkg/controller
This will facilitate its reuse in tests.
Diffstat (limited to 'syz-cluster/pkg/controller/api.go')
-rw-r--r--syz-cluster/pkg/controller/api.go198
1 files changed, 198 insertions, 0 deletions
diff --git a/syz-cluster/pkg/controller/api.go b/syz-cluster/pkg/controller/api.go
new file mode 100644
index 000000000..270803c3b
--- /dev/null
+++ b/syz-cluster/pkg/controller/api.go
@@ -0,0 +1,198 @@
+// Copyright 2024 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+// nolint: dupl // The methods look similar, but extracting the common parts will only make the code worse.
+package controller
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/google/syzkaller/syz-cluster/pkg/api"
+ "github.com/google/syzkaller/syz-cluster/pkg/app"
+ "github.com/google/syzkaller/syz-cluster/pkg/service"
+)
+
+type APIServer struct {
+ seriesService *service.SeriesService
+ sessionService *service.SessionService
+ buildService *service.BuildService
+ testService *service.SessionTestService
+ findingService *service.FindingService
+}
+
+func NewAPIServer(env *app.AppEnvironment) *APIServer {
+ return &APIServer{
+ seriesService: service.NewSeriesService(env),
+ sessionService: service.NewSessionService(env),
+ buildService: service.NewBuildService(env),
+ testService: service.NewSessionTestService(env),
+ findingService: service.NewFindingService(env),
+ }
+}
+
+func (c APIServer) Mux() *http.ServeMux {
+ mux := http.NewServeMux()
+ mux.HandleFunc("/sessions/{session_id}/series", c.getSessionSeries)
+ mux.HandleFunc("/sessions/{session_id}/skip", c.skipSession)
+ mux.HandleFunc("/sessions/upload", c.uploadSession)
+ mux.HandleFunc("/series/{series_id}", c.getSeries)
+ mux.HandleFunc("/builds/last", c.getLastBuild)
+ mux.HandleFunc("/builds/upload", c.uploadBuild)
+ mux.HandleFunc("/tests/upload", c.uploadTest)
+ mux.HandleFunc("/findings/upload", c.uploadFinding)
+ mux.HandleFunc("/series/upload", c.uploadSeries)
+ return mux
+}
+
+func (c APIServer) getSessionSeries(w http.ResponseWriter, r *http.Request) {
+ resp, err := c.seriesService.GetSessionSeries(r.Context(), r.PathValue("session_id"))
+ if err == service.ErrSeriesNotFound || err == service.ErrSessionNotFound {
+ http.Error(w, fmt.Sprint(err), http.StatusNotFound)
+ return
+ } else if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply(w, resp)
+}
+
+func (c APIServer) skipSession(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.SkipRequest](w, r)
+ if req == nil {
+ return
+ }
+ err := c.sessionService.SkipSession(r.Context(), r.PathValue("session_id"), req)
+ if errors.Is(err, service.ErrSessionNotFound) {
+ http.Error(w, fmt.Sprint(err), http.StatusNotFound)
+ return
+ } else if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[interface{}](w, nil)
+}
+
+func (c APIServer) getSeries(w http.ResponseWriter, r *http.Request) {
+ resp, err := c.seriesService.GetSeries(r.Context(), r.PathValue("series_id"))
+ if errors.Is(err, service.ErrSeriesNotFound) {
+ http.Error(w, fmt.Sprint(err), http.StatusNotFound)
+ return
+ } else if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply(w, resp)
+}
+
+func (c APIServer) uploadBuild(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.UploadBuildReq](w, r)
+ if req == nil {
+ return
+ }
+ resp, err := c.buildService.Upload(r.Context(), req)
+ if err != nil {
+ // TODO: sometimes it's not StatusInternalServerError.
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply(w, resp)
+}
+
+func (c APIServer) uploadTest(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.TestResult](w, r)
+ if req == nil {
+ return
+ }
+ // TODO: add parameters validation (and also of the Log size).
+ err := c.testService.Save(r.Context(), req)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[interface{}](w, nil)
+}
+
+func (c APIServer) uploadFinding(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.Finding](w, r)
+ if req == nil {
+ return
+ }
+ // TODO: add parameters validation.
+ err := c.findingService.Save(r.Context(), req)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[interface{}](w, nil)
+}
+
+func (c APIServer) getLastBuild(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.LastBuildReq](w, r)
+ if req == nil {
+ return
+ }
+ resp, err := c.buildService.LastBuild(r.Context(), req)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[*api.Build](w, resp)
+}
+
+func (c APIServer) uploadSeries(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.Series](w, r)
+ if req == nil {
+ return
+ }
+ resp, err := c.seriesService.UploadSeries(r.Context(), req)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[*api.UploadSeriesResp](w, resp)
+}
+
+func (c APIServer) uploadSession(w http.ResponseWriter, r *http.Request) {
+ req := api.ParseJSON[api.NewSession](w, r)
+ if req == nil {
+ return
+ }
+ resp, err := c.sessionService.UploadSession(r.Context(), req)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+ reply[*api.UploadSessionResp](w, resp)
+}
+
+func reply[T any](w http.ResponseWriter, resp T) {
+ w.Header().Set("Content-Type", "application/json")
+ err := json.NewEncoder(w).Encode(resp)
+ if err != nil {
+ http.Error(w, "failed to serialize the response", http.StatusInternalServerError)
+ return
+ }
+}
+
+func parseBody[T any](w http.ResponseWriter, r *http.Request) *T {
+ if r.Method != http.MethodPost {
+ http.Error(w, "must be called via POST", http.StatusMethodNotAllowed)
+ return nil
+ }
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, "failed to read body", http.StatusBadRequest)
+ return nil
+ }
+ var data T
+ err = json.Unmarshal(body, &data)
+ if err != nil {
+ http.Error(w, "invalid body", http.StatusBadRequest)
+ return nil
+ }
+ return &data
+}