github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/controller/api.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  // Package controller provides the server part of the *api.Client interface.
     5  // nolint: dupl
     6  package controller
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  
    13  	"github.com/google/syzkaller/syz-cluster/pkg/api"
    14  	"github.com/google/syzkaller/syz-cluster/pkg/app"
    15  	"github.com/google/syzkaller/syz-cluster/pkg/service"
    16  )
    17  
    18  type APIServer struct {
    19  	seriesService      *service.SeriesService
    20  	sessionService     *service.SessionService
    21  	buildService       *service.BuildService
    22  	testService        *service.SessionTestService
    23  	findingService     *service.FindingService
    24  	baseFindingService *service.BaseFindingService
    25  }
    26  
    27  func NewAPIServer(env *app.AppEnvironment) *APIServer {
    28  	return &APIServer{
    29  		seriesService:      service.NewSeriesService(env),
    30  		sessionService:     service.NewSessionService(env),
    31  		buildService:       service.NewBuildService(env),
    32  		testService:        service.NewSessionTestService(env),
    33  		findingService:     service.NewFindingService(env),
    34  		baseFindingService: service.NewBaseFindingService(env),
    35  	}
    36  }
    37  
    38  func (c APIServer) Mux() *http.ServeMux {
    39  	mux := http.NewServeMux()
    40  	mux.HandleFunc("/builds/last", c.getLastBuild)
    41  	mux.HandleFunc("/builds/upload", c.uploadBuild)
    42  	mux.HandleFunc("/findings/upload", c.uploadFinding)
    43  	mux.HandleFunc("/series/upload", c.uploadSeries)
    44  	mux.HandleFunc("/series/{series_id}", c.getSeries)
    45  	mux.HandleFunc("/sessions/upload", c.uploadSession)
    46  	mux.HandleFunc("/sessions/{session_id}/series", c.getSessionSeries)
    47  	mux.HandleFunc("/sessions/{session_id}/triage_result", c.triageResult)
    48  	mux.HandleFunc("/tests/upload_artifacts", c.uploadTestArtifact)
    49  	mux.HandleFunc("/tests/upload", c.uploadTest)
    50  	mux.HandleFunc("/trees", c.getTrees)
    51  	mux.HandleFunc("/base_findings/upload", c.uploadBaseFinding)
    52  	mux.HandleFunc("/base_findings/status", c.baseFindingStatus)
    53  	return mux
    54  }
    55  
    56  func (c APIServer) getSessionSeries(w http.ResponseWriter, r *http.Request) {
    57  	resp, err := c.seriesService.GetSessionSeries(r.Context(), r.PathValue("session_id"))
    58  	if err == service.ErrSeriesNotFound || err == service.ErrSessionNotFound {
    59  		http.Error(w, fmt.Sprint(err), http.StatusNotFound)
    60  		return
    61  	} else if err != nil {
    62  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
    63  		return
    64  	}
    65  	api.ReplyJSON(w, resp)
    66  }
    67  
    68  func (c APIServer) triageResult(w http.ResponseWriter, r *http.Request) {
    69  	req := api.ParseJSON[api.UploadTriageResultReq](w, r)
    70  	if req == nil {
    71  		return
    72  	}
    73  	err := c.sessionService.TriageResult(r.Context(), r.PathValue("session_id"), req)
    74  	if errors.Is(err, service.ErrSessionNotFound) {
    75  		http.Error(w, fmt.Sprint(err), http.StatusNotFound)
    76  		return
    77  	} else if err != nil {
    78  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
    79  		return
    80  	}
    81  	api.ReplyJSON[interface{}](w, nil)
    82  }
    83  
    84  func (c APIServer) getSeries(w http.ResponseWriter, r *http.Request) {
    85  	resp, err := c.seriesService.GetSeries(r.Context(), r.PathValue("series_id"))
    86  	if errors.Is(err, service.ErrSeriesNotFound) {
    87  		http.Error(w, fmt.Sprint(err), http.StatusNotFound)
    88  		return
    89  	} else if err != nil {
    90  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
    91  		return
    92  	}
    93  	api.ReplyJSON(w, resp)
    94  }
    95  
    96  func (c APIServer) uploadBuild(w http.ResponseWriter, r *http.Request) {
    97  	req := api.ParseJSON[api.UploadBuildReq](w, r)
    98  	if req == nil {
    99  		return
   100  	}
   101  	resp, err := c.buildService.Upload(r.Context(), req)
   102  	if err != nil {
   103  		// TODO: sometimes it's not StatusInternalServerError.
   104  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   105  		return
   106  	}
   107  	api.ReplyJSON(w, resp)
   108  }
   109  
   110  func (c APIServer) uploadTest(w http.ResponseWriter, r *http.Request) {
   111  	req := api.ParseJSON[api.TestResult](w, r)
   112  	if req == nil {
   113  		return
   114  	}
   115  	// TODO: add parameters validation (and also of the Log size).
   116  	err := c.testService.Save(r.Context(), req)
   117  	if err != nil {
   118  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   119  		return
   120  	}
   121  	api.ReplyJSON[interface{}](w, nil)
   122  }
   123  
   124  func (c APIServer) uploadTestArtifact(w http.ResponseWriter, r *http.Request) {
   125  	const maxMemory = 16 * 1000 * 1000 // 16 MB.
   126  	if err := r.ParseMultipartForm(maxMemory); err != nil {
   127  		http.Error(w, "could not parse the multipart form", http.StatusBadRequest)
   128  		return
   129  	}
   130  	defer r.MultipartForm.RemoveAll()
   131  
   132  	file, _, err := r.FormFile("content")
   133  	if err != nil {
   134  		if err == http.ErrMissingFile {
   135  			http.Error(w, "the 'content' file must be present", http.StatusBadRequest)
   136  			return
   137  		}
   138  		http.Error(w, fmt.Sprintf("failed to query the file: %s", err), http.StatusInternalServerError)
   139  		return
   140  	}
   141  	defer file.Close()
   142  
   143  	err = c.testService.SaveArtifacts(r.Context(),
   144  		r.FormValue("session"),
   145  		r.FormValue("test"),
   146  		file)
   147  	if err != nil {
   148  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   149  		return
   150  	}
   151  	api.ReplyJSON[interface{}](w, nil)
   152  }
   153  
   154  func (c APIServer) uploadFinding(w http.ResponseWriter, r *http.Request) {
   155  	req := api.ParseJSON[api.NewFinding](w, r)
   156  	if req == nil {
   157  		return
   158  	}
   159  	// TODO: add parameters validation.
   160  	err := c.findingService.Save(r.Context(), req)
   161  	if err != nil {
   162  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   163  		return
   164  	}
   165  	api.ReplyJSON[interface{}](w, nil)
   166  }
   167  
   168  func (c APIServer) getLastBuild(w http.ResponseWriter, r *http.Request) {
   169  	req := api.ParseJSON[api.LastBuildReq](w, r)
   170  	if req == nil {
   171  		return
   172  	}
   173  	resp, err := c.buildService.LastBuild(r.Context(), req)
   174  	if err != nil {
   175  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   176  		return
   177  	}
   178  	api.ReplyJSON[*api.Build](w, resp)
   179  }
   180  
   181  func (c APIServer) uploadSeries(w http.ResponseWriter, r *http.Request) {
   182  	req := api.ParseJSON[api.Series](w, r)
   183  	if req == nil {
   184  		return
   185  	}
   186  	resp, err := c.seriesService.UploadSeries(r.Context(), req)
   187  	if err != nil {
   188  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   189  		return
   190  	}
   191  	api.ReplyJSON[*api.UploadSeriesResp](w, resp)
   192  }
   193  
   194  func (c APIServer) uploadSession(w http.ResponseWriter, r *http.Request) {
   195  	req := api.ParseJSON[api.NewSession](w, r)
   196  	if req == nil {
   197  		return
   198  	}
   199  	resp, err := c.sessionService.UploadSession(r.Context(), req)
   200  	if err != nil {
   201  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   202  		return
   203  	}
   204  	api.ReplyJSON[*api.UploadSessionResp](w, resp)
   205  }
   206  
   207  func (c APIServer) getTrees(w http.ResponseWriter, r *http.Request) {
   208  	api.ReplyJSON(w, &api.TreesResp{
   209  		Trees:       api.DefaultTrees,
   210  		FuzzTargets: api.FuzzTargets,
   211  	})
   212  }
   213  
   214  func (c APIServer) uploadBaseFinding(w http.ResponseWriter, r *http.Request) {
   215  	req := api.ParseJSON[api.BaseFindingInfo](w, r)
   216  	if req == nil {
   217  		return
   218  	}
   219  	err := c.baseFindingService.Upload(r.Context(), req)
   220  	if errors.Is(err, service.ErrBuildNotFound) {
   221  		http.Error(w, fmt.Sprint(err), http.StatusNotFound)
   222  		return
   223  	} else if err != nil {
   224  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   225  		return
   226  	}
   227  	api.ReplyJSON[interface{}](w, nil)
   228  }
   229  
   230  func (c APIServer) baseFindingStatus(w http.ResponseWriter, r *http.Request) {
   231  	req := api.ParseJSON[api.BaseFindingInfo](w, r)
   232  	if req == nil {
   233  		return
   234  	}
   235  	resp, err := c.baseFindingService.Status(r.Context(), req)
   236  	if err != nil {
   237  		http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
   238  		return
   239  	}
   240  	api.ReplyJSON[*api.BaseFindingStatus](w, resp)
   241  }