diff options
| -rw-r--r-- | syz-cluster/email-reporter/main.go | 1 | ||||
| -rw-r--r-- | syz-cluster/pkg/api/reporter.go | 28 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/entities.go | 6 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/migrations/1_initialize.up.sql | 13 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/report_reply_repo.go | 92 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/report_reply_repo_test.go | 61 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/report_repo.go | 11 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/report_repo_test.go | 22 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/spanner.go | 7 | ||||
| -rw-r--r-- | syz-cluster/pkg/db/util_test.go | 8 | ||||
| -rw-r--r-- | syz-cluster/pkg/reporter/api.go | 36 | ||||
| -rw-r--r-- | syz-cluster/pkg/reporter/api_test.go | 78 | ||||
| -rw-r--r-- | syz-cluster/pkg/service/discussion.go | 80 | ||||
| -rw-r--r-- | syz-cluster/reporter-server/main.go | 3 |
14 files changed, 430 insertions, 16 deletions
diff --git a/syz-cluster/email-reporter/main.go b/syz-cluster/email-reporter/main.go index 24b4adb7f..8726204ee 100644 --- a/syz-cluster/email-reporter/main.go +++ b/syz-cluster/email-reporter/main.go @@ -11,6 +11,7 @@ import ( "time" "github.com/google/syzkaller/pkg/email" + "github.com/google/syzkaller/syz-cluster/pkg/api" "github.com/google/syzkaller/syz-cluster/pkg/app" ) diff --git a/syz-cluster/pkg/api/reporter.go b/syz-cluster/pkg/api/reporter.go index 5a7b7bfb4..e73bdb668 100644 --- a/syz-cluster/pkg/api/reporter.go +++ b/syz-cluster/pkg/api/reporter.go @@ -7,6 +7,7 @@ import ( "context" "net/url" "strings" + "time" ) type ReporterClient struct { @@ -53,3 +54,30 @@ func (client ReporterClient) UpstreamReport(ctx context.Context, id string, req _, err := postJSON[UpstreamReportReq, any](ctx, client.baseURL+"/reports/"+id+"/upstream", req) return err } + +type RecordReplyReq struct { + MessageID string `json:"message_id"` + InReplyTo string `json:"in_reply_to"` + Reporter string `json:"reporter"` + Time time.Time `json:"time"` +} + +type RecordReplyResp struct { + New bool `json:"new"` + ReportID string `json:"report_id"` // or empty, if no original message was found +} + +func (client ReporterClient) RecordReply(ctx context.Context, req *RecordReplyReq) (*RecordReplyResp, error) { + return postJSON[RecordReplyReq, RecordReplyResp](ctx, client.baseURL+"/reports/record_reply", req) +} + +type LastReplyResp struct { + Time time.Time `json:"time"` +} + +// Returns nil if no reply has ever been recorded. +func (client ReporterClient) LastReply(ctx context.Context, reporter string) (*LastReplyResp, error) { + v := url.Values{} + v.Add("reporter", reporter) + return postJSON[any, LastReplyResp](ctx, client.baseURL+"/reports/last_reply?"+v.Encode(), nil) +} diff --git a/syz-cluster/pkg/db/entities.go b/syz-cluster/pkg/db/entities.go index a7f08f9ab..c6c469131 100644 --- a/syz-cluster/pkg/db/entities.go +++ b/syz-cluster/pkg/db/entities.go @@ -141,3 +141,9 @@ type SessionReport struct { func (s *SessionReport) SetReportedAt(t time.Time) { s.ReportedAt = spanner.NullTime{Time: t, Valid: true} } + +type ReportReply struct { + MessageID string `spanner:"MessageID"` + ReportID string `spanner:"ReportID"` + Time time.Time `spanner:"Time"` +} diff --git a/syz-cluster/pkg/db/migrations/1_initialize.up.sql b/syz-cluster/pkg/db/migrations/1_initialize.up.sql index ca91b343a..3100c9bb2 100644 --- a/syz-cluster/pkg/db/migrations/1_initialize.up.sql +++ b/syz-cluster/pkg/db/migrations/1_initialize.up.sql @@ -100,14 +100,23 @@ CREATE UNIQUE INDEX NoDupFindings ON Findings(SessionID, TestName, Title); -- Session's bug reports. CREATE TABLE SessionReports ( - ID STRING(36) NOT NULL, -- UUID?? + ID STRING(36) NOT NULL, -- UUID SessionID STRING(36) NOT NULL, -- UUID ReportedAt TIMESTAMP, Moderation BOOL, - MessageID STRING(256), + MessageID STRING(512), Reporter STRING(256), CONSTRAINT FK_SessionReports FOREIGN KEY (SessionID) REFERENCES Sessions (ID), ) PRIMARY KEY(ID); CREATE UNIQUE INDEX NoDupSessionReports ON SessionReports(SessionID, Moderation); CREATE INDEX SessionReportsByStatus ON SessionReports (Reporter, ReportedAt); +CREATE INDEX SessionReportsByMessageID ON SessionReports(Reporter, MessageID); + +-- Replies on a session report. +CREATE TABLE ReportReplies ( + MessageID STRING(512) NOT NULL, -- Gmail sets a limit of 500 characters for Message-ID + ReportID STRING(36) NOT NULL, -- UUID + Time TIMESTAMP, + CONSTRAINT FK_ReplyReportID FOREIGN KEY (ReportID) REFERENCES SessionReports (ID), +) PRIMARY KEY(MessageID, ReportID); diff --git a/syz-cluster/pkg/db/report_reply_repo.go b/syz-cluster/pkg/db/report_reply_repo.go new file mode 100644 index 000000000..7e21403c2 --- /dev/null +++ b/syz-cluster/pkg/db/report_reply_repo.go @@ -0,0 +1,92 @@ +// Copyright 2025 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. + +package db + +import ( + "context" + "errors" + + "cloud.google.com/go/spanner" +) + +type ReportReplyRepository struct { + client *spanner.Client +} + +func NewReportReplyRepository(client *spanner.Client) *ReportReplyRepository { + return &ReportReplyRepository{ + client: client, + } +} + +func (repo *ReportReplyRepository) FindParentReportID(ctx context.Context, reporter, messageID string) (string, error) { + stmt := spanner.Statement{ + SQL: "SELECT `ReportReplies`.ReportID FROM `ReportReplies` " + + "JOIN `SessionReports` ON `SessionReports`.ID = `ReportReplies`.ReportID " + + "WHERE `ReportReplies`.MessageID = @messageID " + + "AND `SessionReports`.Reporter = @reporter LIMIT 1", + Params: map[string]interface{}{ + "reporter": reporter, + "messageID": messageID, + }, + } + iter := repo.client.Single().Query(ctx, stmt) + defer iter.Stop() + + type result struct { + ReportID string `spanner:"ReportID"` + } + ret, err := readOne[result](iter) + if err != nil { + return "", err + } else if ret != nil { + return ret.ReportID, nil + } + return "", nil +} + +var ErrReportReplyExists = errors.New("the reply has already been recorded") + +func (repo *ReportReplyRepository) Insert(ctx context.Context, reply *ReportReply) error { + _, err := repo.client.ReadWriteTransaction(ctx, + func(ctx context.Context, txn *spanner.ReadWriteTransaction) error { + stmt := spanner.Statement{ + SQL: "SELECT * from `ReportReplies` " + + "WHERE `ReportID`=@reportID AND `MessageID`=@messageID", + Params: map[string]interface{}{ + "reportID": reply.ReportID, + "messageID": reply.MessageID, + }, + } + iter := txn.Query(ctx, stmt) + entity, err := readOne[ReportReply](iter) + iter.Stop() + if err != nil { + return err + } else if entity != nil { + return ErrReportReplyExists + } + insert, err := spanner.InsertStruct("ReportReplies", reply) + if err != nil { + return err + } + return txn.BufferWrite([]*spanner.Mutation{insert}) + }) + return err +} + +func (repo *ReportReplyRepository) LastForReporter(ctx context.Context, reporter string) (*ReportReply, error) { + stmt := spanner.Statement{ + SQL: "SELECT `ReportReplies`.* FROM `ReportReplies` " + + "JOIN `SessionReports` ON `SessionReports`.ID=`ReportReplies`.ReportID " + + "WHERE `SessionReports`.Reporter=@reporter " + + "ORDER BY `Time` DESC LIMIT 1", + Params: map[string]interface{}{ + "reporter": reporter, + }, + } + iter := repo.client.Single().Query(ctx, stmt) + defer iter.Stop() + return readOne[ReportReply](iter) +} diff --git a/syz-cluster/pkg/db/report_reply_repo_test.go b/syz-cluster/pkg/db/report_reply_repo_test.go new file mode 100644 index 000000000..27c2849cd --- /dev/null +++ b/syz-cluster/pkg/db/report_reply_repo_test.go @@ -0,0 +1,61 @@ +// Copyright 2025 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. + +package db + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestReportReplyRepository(t *testing.T) { + client, ctx := NewTransientDB(t) + dtd := &dummyTestData{t, ctx, client} + session := dtd.dummySession(dtd.dummySeries()) + + reportRepo := NewReportRepository(client) + report := &SessionReport{SessionID: session.ID, Reporter: dummyReporter} + err := reportRepo.Insert(ctx, report) + assert.NoError(t, err) + + replyRepo := NewReportReplyRepository(client) + baseTime := time.Now() + for i := 0; i < 2; i++ { + err = replyRepo.Insert(ctx, &ReportReply{ + MessageID: fmt.Sprintf("message-id-%d", i), + ReportID: report.ID, + Time: baseTime.Add(time.Duration(i) * time.Second), + }) + assert.NoError(t, err) + } + + t.Run("insert-dup-reply", func(t *testing.T) { + err := replyRepo.Insert(ctx, &ReportReply{ + MessageID: "message-id-0", + ReportID: report.ID, + Time: time.Now(), + }) + assert.Error(t, ErrReportReplyExists, err) + }) + + t.Run("last-report", func(t *testing.T) { + reply, err := replyRepo.LastForReporter(ctx, dummyReporter) + assert.NoError(t, err) + assert.Equal(t, "message-id-1", reply.MessageID) + }) + + t.Run("last-report-unknown", func(t *testing.T) { + reply, err := replyRepo.LastForReporter(ctx, "unknown-reporter") + assert.NoError(t, err) + assert.Nil(t, reply) + }) + + t.Run("find-by-parent", func(t *testing.T) { + reportID, err := replyRepo.FindParentReportID(ctx, dummyReporter, "message-id-0") + assert.NoError(t, err) + assert.Equal(t, report.ID, reportID) + }) +} diff --git a/syz-cluster/pkg/db/report_repo.go b/syz-cluster/pkg/db/report_repo.go index 4e039c4a7..37a09a746 100644 --- a/syz-cluster/pkg/db/report_repo.go +++ b/syz-cluster/pkg/db/report_repo.go @@ -44,3 +44,14 @@ func (repo *ReportRepository) ListNotReported(ctx context.Context, reporter stri addLimit(&stmt, limit) return repo.readEntities(ctx, stmt) } + +func (repo *ReportRepository) FindByMessageID(ctx context.Context, reporter, messageID string) (*SessionReport, error) { + stmt := spanner.Statement{ + SQL: "SELECT * FROM `SessionReports` WHERE `Reporter` = @reporter AND `MessageID` = @messageID", + Params: map[string]interface{}{ + "reporter": reporter, + "messageID": messageID, + }, + } + return repo.readEntity(ctx, stmt) +} diff --git a/syz-cluster/pkg/db/report_repo_test.go b/syz-cluster/pkg/db/report_repo_test.go index bc81c37bd..a0cee2a11 100644 --- a/syz-cluster/pkg/db/report_repo_test.go +++ b/syz-cluster/pkg/db/report_repo_test.go @@ -40,16 +40,30 @@ func TestReportRepository(t *testing.T) { assert.NoError(t, err) assert.Len(t, list, 3) + const messageID = "message-id" err = reportRepo.Update(ctx, keys[0], func(rep *SessionReport) error { rep.SetReportedAt(time.Now()) + rep.MessageID = messageID return nil }) assert.NoError(t, err) - // Now one less. - list, err = reportRepo.ListNotReported(ctx, dummyReporter, 10) - assert.NoError(t, err) - assert.Len(t, list, 2) + t.Run("not-reported-count", func(t *testing.T) { + // Now one less. + list, err := reportRepo.ListNotReported(ctx, dummyReporter, 10) + assert.NoError(t, err) + assert.Len(t, list, 2) + }) + t.Run("find-by-id-found", func(t *testing.T) { + report, err := reportRepo.FindByMessageID(ctx, dummyReporter, messageID) + assert.NoError(t, err) + assert.NotNil(t, report) + }) + t.Run("find-by-id-empty", func(t *testing.T) { + report, err := reportRepo.FindByMessageID(ctx, dummyReporter, "non-existing-id") + assert.NoError(t, err) + assert.Nil(t, report) + }) } func TestSessionsWithoutReports(t *testing.T) { diff --git a/syz-cluster/pkg/db/spanner.go b/syz-cluster/pkg/db/spanner.go index d114727b8..8db081c14 100644 --- a/syz-cluster/pkg/db/spanner.go +++ b/syz-cluster/pkg/db/spanner.go @@ -333,3 +333,10 @@ func (g *genericEntityOps[EntityType, KeyType]) readEntities(ctx context.Context defer iter.Stop() return readEntities[EntityType](iter) } + +func (g *genericEntityOps[EntityType, KeyType]) readEntity(ctx context.Context, + stmt spanner.Statement) (*EntityType, error) { + iter := g.client.Single().Query(ctx, stmt) + defer iter.Stop() + return readOne[EntityType](iter) +} diff --git a/syz-cluster/pkg/db/util_test.go b/syz-cluster/pkg/db/util_test.go index 64fedeab2..cd9e1fff7 100644 --- a/syz-cluster/pkg/db/util_test.go +++ b/syz-cluster/pkg/db/util_test.go @@ -31,6 +31,14 @@ func (d *dummyTestData) addSessionTest(session *Session, names ...string) { } } +func (d *dummyTestData) dummySeries() *Series { + seriesRepo := NewSeriesRepository(d.client) + series := &Series{ExtID: "series-ext-id"} + err := seriesRepo.Insert(d.ctx, series, nil) + assert.NoError(d.t, err) + return series +} + func (d *dummyTestData) dummySession(series *Series) *Session { sessionRepo := NewSessionRepository(d.client) session := &Session{ diff --git a/syz-cluster/pkg/reporter/api.go b/syz-cluster/pkg/reporter/api.go index d2c23c912..e06e1312c 100644 --- a/syz-cluster/pkg/reporter/api.go +++ b/syz-cluster/pkg/reporter/api.go @@ -16,11 +16,15 @@ import ( ) type APIServer struct { - service *service.ReportService + reportService *service.ReportService + discussionService *service.DiscussionService } -func NewAPIServer(service *service.ReportService) *APIServer { - return &APIServer{service: service} +func NewAPIServer(env *app.AppEnvironment) *APIServer { + return &APIServer{ + reportService: service.NewReportService(env), + discussionService: service.NewDiscussionService(env), + } } func (s *APIServer) Mux() *http.ServeMux { @@ -28,6 +32,8 @@ func (s *APIServer) Mux() *http.ServeMux { mux.HandleFunc("/reports/{report_id}/update", s.updateReport) mux.HandleFunc("/reports/{report_id}/upstream", s.upstreamReport) mux.HandleFunc("/reports/{report_id}/confirm", s.confirmReport) + mux.HandleFunc("/reports/record_reply", s.recordReply) + mux.HandleFunc("/reports/last_reply", s.lastReply) mux.HandleFunc("/reports", s.nextReports) return mux } @@ -38,7 +44,7 @@ func (s *APIServer) updateReport(w http.ResponseWriter, r *http.Request) { if req == nil { return // TODO: return StatusBadRequest here and below. } - err := s.service.Update(r.Context(), r.PathValue("report_id"), req) + err := s.reportService.Update(r.Context(), r.PathValue("report_id"), req) reply[interface{}](w, nil, err) } @@ -49,20 +55,34 @@ func (s *APIServer) upstreamReport(w http.ResponseWriter, r *http.Request) { return } // TODO: journal the action. - err := s.service.Upstream(r.Context(), r.PathValue("report_id"), req) + err := s.reportService.Upstream(r.Context(), r.PathValue("report_id"), req) reply[interface{}](w, nil, err) } func (s *APIServer) nextReports(w http.ResponseWriter, r *http.Request) { - resp, err := s.service.Next(r.Context(), r.FormValue("reporter")) + resp, err := s.reportService.Next(r.Context(), r.FormValue("reporter")) reply(w, resp, err) } func (s *APIServer) confirmReport(w http.ResponseWriter, r *http.Request) { - err := s.service.Confirm(r.Context(), r.PathValue("report_id")) + err := s.reportService.Confirm(r.Context(), r.PathValue("report_id")) reply[interface{}](w, nil, err) } +func (s *APIServer) recordReply(w http.ResponseWriter, r *http.Request) { + req := api.ParseJSON[api.RecordReplyReq](w, r) + if req == nil { + return + } + resp, err := s.discussionService.RecordReply(r.Context(), req) + reply(w, resp, err) +} + +func (s *APIServer) lastReply(w http.ResponseWriter, r *http.Request) { + resp, err := s.discussionService.LastReply(r.Context(), r.PathValue("reporter")) + reply(w, resp, err) +} + func reply[T any](w http.ResponseWriter, obj T, err error) { if errors.Is(err, service.ErrReportNotFound) { http.Error(w, fmt.Sprint(err), http.StatusNotFound) @@ -78,7 +98,7 @@ func reply[T any](w http.ResponseWriter, obj T, err error) { } func TestServer(t *testing.T, env *app.AppEnvironment) *api.ReporterClient { - apiServer := NewAPIServer(service.NewReportService(env)) + apiServer := NewAPIServer(env) server := httptest.NewServer(apiServer.Mux()) t.Cleanup(server.Close) return api.NewReporterClient(server.URL) diff --git a/syz-cluster/pkg/reporter/api_test.go b/syz-cluster/pkg/reporter/api_test.go index 599c10e9c..9f94ce5b6 100644 --- a/syz-cluster/pkg/reporter/api_test.go +++ b/syz-cluster/pkg/reporter/api_test.go @@ -5,6 +5,7 @@ package reporter import ( "testing" + "time" "github.com/google/syzkaller/syz-cluster/pkg/api" "github.com/google/syzkaller/syz-cluster/pkg/app" @@ -84,3 +85,80 @@ func TestAPIReportFlow(t *testing.T) { assert.False(t, nextResp3.Report.Moderation) assert.Equal(t, nextResp2.Report.Series, nextResp3.Report.Series) } + +func TestReplyReporting(t *testing.T) { + env, ctx := app.TestEnvironment(t) + client := controller.TestServer(t, env) + + // Create series/session/test/findings. + testSeries := controller.DummySeries() + controller.FakeSeriesWithFindings(t, ctx, env, client, testSeries) + + generator := NewGenerator(env) + err := generator.Process(ctx, 1) + assert.NoError(t, err) + + // Create a report. + reportClient := TestServer(t, env) + nextResp, err := reportClient.GetNextReport(ctx, api.LKMLReporter) + assert.NoError(t, err) + + // Confirm the report and set its message ID. + reportID := nextResp.Report.ID + err = reportClient.ConfirmReport(ctx, reportID) + assert.NoError(t, err) + + err = reportClient.UpdateReport(ctx, reportID, &api.UpdateReportReq{ + MessageID: "message-id", + }) + assert.NoError(t, err) + + // Direct reply to the report. + resp, err := reportClient.RecordReply(ctx, &api.RecordReplyReq{ + MessageID: "direct-reply-id", + InReplyTo: "message-id", + Reporter: api.LKMLReporter, + Time: time.Now(), + }) + assert.NoError(t, err) + assert.Equal(t, &api.RecordReplyResp{ + New: true, + ReportID: reportID, + }, resp) + + // Reply to the reply. + replyToReply := &api.RecordReplyReq{ + MessageID: "reply-to-reply-id", + InReplyTo: "direct-reply-id", + Reporter: api.LKMLReporter, + Time: time.Now(), + } + resp, err = reportClient.RecordReply(ctx, replyToReply) + assert.NoError(t, err) + assert.Equal(t, &api.RecordReplyResp{ + New: true, + ReportID: reportID, + }, resp) + + t.Run("dup-report", func(t *testing.T) { + resp, err := reportClient.RecordReply(ctx, replyToReply) + assert.NoError(t, err) + assert.Equal(t, &api.RecordReplyResp{ + New: false, + ReportID: reportID, + }, resp) + }) + + t.Run("unknown-message", func(t *testing.T) { + resp, err := reportClient.RecordReply(ctx, &api.RecordReplyReq{ + MessageID: "whatever", + InReplyTo: "unknown-id", + Reporter: api.LKMLReporter, + }) + assert.NoError(t, err) + assert.Equal(t, &api.RecordReplyResp{ + New: false, + ReportID: "", + }, resp) + }) +} diff --git a/syz-cluster/pkg/service/discussion.go b/syz-cluster/pkg/service/discussion.go new file mode 100644 index 000000000..f4be21fbd --- /dev/null +++ b/syz-cluster/pkg/service/discussion.go @@ -0,0 +1,80 @@ +// Copyright 2025 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. + +package service + +import ( + "context" + "errors" + "fmt" + + "github.com/google/syzkaller/syz-cluster/pkg/api" + "github.com/google/syzkaller/syz-cluster/pkg/app" + "github.com/google/syzkaller/syz-cluster/pkg/db" +) + +// DiscussionService implements the functionality necessary for tracking replies under the bug reports. +// Each report is assumed to have an ID and have an InReplyTo ID that either points to another reply or +// to the original bug report. +// DiscussionService offers the methods to record such replies and, for each reply, to determine the original +// discussed bug report. +type DiscussionService struct { + reportRepo *db.ReportRepository + reportReplyRepo *db.ReportReplyRepository +} + +func NewDiscussionService(env *app.AppEnvironment) *DiscussionService { + return &DiscussionService{ + reportRepo: db.NewReportRepository(env.Spanner), + reportReplyRepo: db.NewReportReplyRepository(env.Spanner), + } +} + +func (d *DiscussionService) RecordReply(ctx context.Context, req *api.RecordReplyReq) (*api.RecordReplyResp, error) { + // First check if the message was a directl reply to the report. + report, err := d.reportRepo.FindByMessageID(ctx, req.Reporter, req.InReplyTo) + if err != nil { + return nil, fmt.Errorf("failed to search among the reports: %w", err) + } + var reportID string + if report != nil { + reportID = report.ID + } else { + // Now try to find a matching reply. + reportID, err = d.reportReplyRepo.FindParentReportID(ctx, req.Reporter, req.InReplyTo) + if err != nil { + return nil, fmt.Errorf("failed to search among the replies: %w", err) + } + } + if reportID == "" { + // We could not find the related report. + return &api.RecordReplyResp{}, nil + } + err = d.reportReplyRepo.Insert(ctx, &db.ReportReply{ + ReportID: reportID, + MessageID: req.MessageID, + Time: req.Time, + }) + if errors.Is(err, db.ErrReportReplyExists) { + return &api.RecordReplyResp{ + ReportID: reportID, + }, nil + } else if err != nil { + return nil, fmt.Errorf("failed to save the reply: %w", err) + } + return &api.RecordReplyResp{ + ReportID: reportID, + New: true, + }, nil +} + +func (d *DiscussionService) LastReply(ctx context.Context, reporter string) (*api.LastReplyResp, error) { + reply, err := d.reportReplyRepo.LastForReporter(ctx, reporter) + if err != nil { + return nil, fmt.Errorf("failed to query the last report: %w", err) + } + if reply != nil { + return &api.LastReplyResp{Time: reply.Time}, nil + } + return &api.LastReplyResp{}, nil +} diff --git a/syz-cluster/reporter-server/main.go b/syz-cluster/reporter-server/main.go index c7d8b88f1..23197cff3 100644 --- a/syz-cluster/reporter-server/main.go +++ b/syz-cluster/reporter-server/main.go @@ -10,7 +10,6 @@ import ( "github.com/google/syzkaller/syz-cluster/pkg/app" "github.com/google/syzkaller/syz-cluster/pkg/reporter" - "github.com/google/syzkaller/syz-cluster/pkg/service" ) func main() { @@ -23,7 +22,7 @@ func main() { generator := reporter.NewGenerator(env) go generator.Loop(ctx) - api := reporter.NewAPIServer(service.NewReportService(env)) + api := reporter.NewAPIServer(env) log.Printf("listening on port 8080") app.Fatalf("listen failed: %v", http.ListenAndServe(":8080", api.Mux())) } |
