Skip to content
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

dyvukov refactor api #5394

Merged
merged 6 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions dashboard/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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.

// Package api provides data structures and helper methods to work with the dashboard JSON API.
// All structures in this package are backwards compatible.
package api

const Version = 1

type BugGroup struct {
Version int `json:"version"`
Bugs []BugSummary
}

type BugSummary struct {
Title string `json:"title,omitempty"`
Link string `json:"link"`
LastUpdated string `json:"last-updated,omitempty"`
FixCommits []Commit `json:"fix-commits,omitempty"`
}

type Bug struct {
Version int `json:"version"`
Title string `json:"title,omitempty"`
ID string `json:"id"`
FixCommits []Commit `json:"fix-commits,omitempty"`
CauseCommit *Commit `json:"cause-commit,omitempty"`
// Links to the discussions.
Discussions []string `json:"discussions,omitempty"`
Crashes []Crash `json:"crashes,omitempty"`
}

type Crash struct {
Title string `json:"title"`
SyzReproducerLink string `json:"syz-reproducer,omitempty"`
CReproducerLink string `json:"c-reproducer,omitempty"`
KernelConfigLink string `json:"kernel-config,omitempty"`
KernelSourceGit string `json:"kernel-source-git,omitempty"`
KernelSourceCommit string `json:"kernel-source-commit,omitempty"`
SyzkallerGit string `json:"syzkaller-git,omitempty"`
SyzkallerCommit string `json:"syzkaller-commit,omitempty"`
CompilerDescription string `json:"compiler-description,omitempty"`
Architecture string `json:"architecture,omitempty"`
CrashReportLink string `json:"crash-report-link,omitempty"`
}

type Commit struct {
Title string `json:"title"`
Link string `json:"link,omitempty"`
Hash string `json:"hash,omitempty"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
}
142 changes: 142 additions & 0 deletions dashboard/api/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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.

package api

import (
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"net/url"
"reflect"
"strings"
"time"
)

type Client struct {
url string
token string
throttle bool
ctor requestCtor
doer requestDoer
}

// accessToken is OAuth access token obtained with "gcloud auth print-access-token"
// (provided your account has at least user level access to the dashboard).
// If the token is provided, dashboard should disable API throttling.
// The token can be empty, in which case the dashboard may throttle requests.
func NewClient(dashboardURL, accessToken string) *Client {
return &Client{
url: strings.TrimSuffix(dashboardURL, "/"),
token: accessToken,
throttle: true,
ctor: http.NewRequest,
doer: http.DefaultClient.Do,
}
}

type (
requestCtor func(method, url string, body io.Reader) (*http.Request, error)
requestDoer func(req *http.Request) (*http.Response, error)
)

func NewTestClient(ctor requestCtor, doer requestDoer) *Client {
return &Client{
url: "http://localhost",
ctor: ctor,
doer: doer,
}
}

type BugGroupType int

const (
BugGroupOpen BugGroupType = 1 << iota
BugGroupFixed
BugGroupInvalid
BugGroupAll = ^0
)

var groupSuffix = map[BugGroupType]string{
BugGroupFixed: "/fixed",
BugGroupInvalid: "/invalid",
}

func (c *Client) BugGroups(ns string, groups BugGroupType) ([]BugSummary, error) {
var bugs []BugSummary
for _, typ := range []BugGroupType{BugGroupOpen, BugGroupFixed, BugGroupInvalid} {
if (groups & typ) == 0 {
continue
}
url := "/" + ns + groupSuffix[typ]
var group BugGroup
if err := c.query(url, &group); err != nil {
return nil, err
}
bugs = append(bugs, group.Bugs...)
}
return bugs, nil
}

func (c *Client) Bug(link string) (*Bug, error) {
bug := new(Bug)
return bug, c.query(link, bug)
}

func (c *Client) Text(query string) ([]byte, error) {
queryURL, err := c.queryURL(query)
if err != nil {
return nil, err
}
req, err := c.ctor(http.MethodGet, queryURL, nil)
if err != nil {
return nil, fmt.Errorf("http.NewRequest: %w", err)
}
if c.token != "" {
req.Header.Add("Authorization", "Bearer "+c.token)
} else if c.throttle {
<-throttler
}
res, err := c.doer(req)
if err != nil {
return nil, fmt.Errorf("http.Get(%v): %w", queryURL, err)
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if res.StatusCode < 200 || res.StatusCode >= 300 || err != nil {
return nil, fmt.Errorf("api request %q failed: %v (%w)", queryURL, res.StatusCode, err)
}
return body, nil
}

func (c *Client) query(query string, result any) error {
data, err := c.Text(query)
if err != nil {
return err
}
if err := json.Unmarshal(data, result); err != nil {
return fmt.Errorf("json.Unmarshal: %w\n%s", err, data)
}
if ver := reflect.ValueOf(result).Elem().FieldByName("Version").Int(); ver != Version {
return fmt.Errorf("unsupported export version %v (expect %v)", ver, Version)
}
return nil
}

func (c *Client) queryURL(query string) (string, error) {
// All links in API are html escaped for some reason, unescape them.
query = c.url + html.UnescapeString(query)
u, err := url.Parse(query)
if err != nil {
return "", fmt.Errorf("url.Parse(%v): %w", query, err)
}
vals := u.Query()
// json=1 is ignored for text end points, so we don't bother not adding it.
vals.Set("json", "1")
u.RawQuery = vals.Encode()
return u.String(), nil
}

var throttler = time.NewTicker(time.Second).C
111 changes: 32 additions & 79 deletions dashboard/app/public_json_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,13 @@ package main

import (
"encoding/json"
)

// publicApiBugDescription is used to serve the /bug HTTP requests
// and provide JSON description of the BUG. Backward compatible.
type publicAPIBugDescription struct {
Version int `json:"version"`
Title string `json:"title,omitempty"`
ID string `json:"id"`
FixCommits []vcsCommit `json:"fix-commits,omitempty"`
CauseCommit *vcsCommit `json:"cause-commit,omitempty"`
// links to the discussions
Discussions []string `json:"discussions,omitempty"`
Crashes []publicAPICrashDescription `json:"crashes,omitempty"`
}

type vcsCommit struct {
Title string `json:"title"`
Link string `json:"link,omitempty"`
Hash string `json:"hash,omitempty"`
Repo string `json:"repo,omitempty"`
Branch string `json:"branch,omitempty"`
}

type publicAPICrashDescription struct {
Title string `json:"title"`
SyzReproducer string `json:"syz-reproducer,omitempty"`
CReproducer string `json:"c-reproducer,omitempty"`
KernelConfig string `json:"kernel-config,omitempty"`
KernelSourceGit string `json:"kernel-source-git,omitempty"`
KernelSourceCommit string `json:"kernel-source-commit,omitempty"`
SyzkallerGit string `json:"syzkaller-git,omitempty"`
SyzkallerCommit string `json:"syzkaller-commit,omitempty"`
CompilerDescription string `json:"compiler-description,omitempty"`
Architecture string `json:"architecture,omitempty"`
CrashReport string `json:"crash-report-link,omitempty"`
}
"github.com/google/syzkaller/dashboard/api"
)

func getExtAPIDescrForBugPage(bugPage *uiBugPage) *publicAPIBugDescription {
return &publicAPIBugDescription{
Version: 1,
func getExtAPIDescrForBugPage(bugPage *uiBugPage) *api.Bug {
return &api.Bug{
Version: api.Version,
Title: bugPage.Bug.Title,
ID: bugPage.Bug.ID,
Discussions: func() []string {
Expand All @@ -54,44 +21,44 @@ func getExtAPIDescrForBugPage(bugPage *uiBugPage) *publicAPIBugDescription {
return []string{bugPage.Bug.ExternalLink}
}(),
FixCommits: getBugFixCommits(bugPage.Bug),
CauseCommit: func() *vcsCommit {
CauseCommit: func() *api.Commit {
if bugPage.BisectCause == nil || bugPage.BisectCause.Commit == nil {
return nil
}
bisectCause := bugPage.BisectCause
return &vcsCommit{
return &api.Commit{
Title: bisectCause.Commit.Title,
Link: bisectCause.Commit.Link,
Hash: bisectCause.Commit.Hash,
Repo: bisectCause.KernelRepo,
Branch: bisectCause.KernelBranch}
}(),
Crashes: func() []publicAPICrashDescription {
var res []publicAPICrashDescription
Crashes: func() []api.Crash {
var res []api.Crash
for _, crash := range bugPage.Crashes.Crashes {
res = append(res, publicAPICrashDescription{
res = append(res, api.Crash{
Title: crash.Title,
SyzReproducer: crash.ReproSyzLink,
CReproducer: crash.ReproCLink,
KernelConfig: crash.KernelConfigLink,
SyzReproducerLink: crash.ReproSyzLink,
CReproducerLink: crash.ReproCLink,
KernelConfigLink: crash.KernelConfigLink,
KernelSourceGit: crash.KernelCommitLink,
KernelSourceCommit: crash.KernelCommit,
SyzkallerGit: crash.SyzkallerCommitLink,
SyzkallerCommit: crash.SyzkallerCommit,
// TODO: add the CompilerDescription
// TODO: add the Architecture
CrashReport: crash.ReportLink,
CrashReportLink: crash.ReportLink,
})
}
return res
}(),
}
}

func getBugFixCommits(bug *uiBug) []vcsCommit {
var res []vcsCommit
func getBugFixCommits(bug *uiBug) []api.Commit {
var res []api.Commit
for _, commit := range bug.Commits {
res = append(res, vcsCommit{
res = append(res, api.Commit{
Title: commit.Title,
Link: commit.Link,
Hash: commit.Hash,
Expand All @@ -102,34 +69,20 @@ func getBugFixCommits(bug *uiBug) []vcsCommit {
return res
}

type publicAPIBugGroup struct {
Version int `json:"version"`
Bugs []publicAPIBug
}

type publicAPIBug struct {
Title string `json:"title,omitempty"`
Link string `json:"link"`
LastUpdated string `json:"last-updated,omitempty"`
FixCommits []vcsCommit `json:"fix-commits,omitempty"`
}

func getExtAPIDescrForBugGroups(bugGroups []*uiBugGroup) *publicAPIBugGroup {
return &publicAPIBugGroup{
Version: 1,
Bugs: func() []publicAPIBug {
var res []publicAPIBug
for _, group := range bugGroups {
for _, bug := range group.Bugs {
res = append(res, publicAPIBug{
Title: bug.Title,
Link: bug.Link,
FixCommits: getBugFixCommits(bug),
})
}
}
return res
}(),
func getExtAPIDescrForBugGroups(bugGroups []*uiBugGroup) *api.BugGroup {
var bugs []api.BugSummary
for _, group := range bugGroups {
for _, bug := range group.Bugs {
bugs = append(bugs, api.BugSummary{
Title: bug.Title,
Link: bug.Link,
FixCommits: getBugFixCommits(bug),
})
}
}
return &api.BugGroup{
Version: api.Version,
Bugs: bugs,
}
}

Expand Down Expand Up @@ -162,7 +115,7 @@ type publicAPIBackports struct {

func getExtAPIDescrForBackports(groups []*uiBackportGroup) *publicAPIBackports {
return &publicAPIBackports{
Version: 1,
Version: api.Version,
List: func() []publicMissingBackport {
var res []publicMissingBackport
for _, group := range groups {
Expand Down
Loading
Loading