Skip to content

Commit

Permalink
add report API
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed Aug 14, 2024
1 parent e723847 commit 5465766
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 36 deletions.
5 changes: 3 additions & 2 deletions cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,14 @@ func NewCluster(
}

// ID returns the cluster id
// The ID may not be unique in the openbmclapi cluster runtime
// The ID may not be unique in the OpenBMCLAPI cluster runtime.
// To identify the cluster instance for analyzing, use Name instead.
func (cr *Cluster) ID() string {
return cr.opts.Id
}

// Name returns the cluster's alias name
// The name must be unique in the openbmclapi cluster runtime
// The name must be unique in the OpenBMCLAPI cluster runtime.
func (cr *Cluster) Name() string {
return cr.name
}
Expand Down
10 changes: 9 additions & 1 deletion cluster/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,15 @@ func (cr *Cluster) makeReqWithBody(
}

func (cr *Cluster) makeReqWithAuth(ctx context.Context, method string, relpath string, query url.Values) (req *http.Request, err error) {
req, err = cr.makeReq(ctx, method, relpath, query)
return cr.makeReqWithAuthBody(ctx, method, relpath, query, nil)
}

func (cr *Cluster) makeReqWithAuthBody(
ctx context.Context,
method string, relpath string,
query url.Values, body io.Reader,
) (req *http.Request, err error) {
req, err = cr.makeReqWithBody(ctx, method, relpath, query, nil)
if err != nil {
return
}
Expand Down
30 changes: 30 additions & 0 deletions cluster/config.go → cluster/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,33 @@ func (cr *Cluster) RequestCert(ctx context.Context) (ckp *CertKeyPair, err error
}
return
}

func (cr *Cluster) ReportDownload(ctx context.Context, request *http.Request, err error) error {
type ReportPayload struct {
Urls []string `json:"urls"`
Error utils.EmbedJSON[struct{ Message string }] `json:"error"`
}
var payload ReportPayload
redirects := utils.GetRedirects(request)
payload.Urls = make([]string, len(redirects))
for i, u := range redirects {
payload.Urls[i] = u.String()
}
payload.Error.V.Message = err.Error()
data, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := cr.makeReqWithAuthBody(ctx, http.MethodPost, "/openbmclapi/report", nil, bytes.NewReader(data))
if err != nil {
return err
}
resp, err := cr.client.Do(req)
if err != nil {
return err
}
if resp.StatusCode/100 != 2 {
return utils.NewHTTPStatusErrorFromResponse(resp)
}
return nil
}
35 changes: 14 additions & 21 deletions cluster/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -641,29 +641,22 @@ func (c *HTTPClient) fetchFileWithBuf(
return
}
defer res.Body.Close()
if err = ctx.Err(); err != nil {
return
}
if res.StatusCode != http.StatusOK {
err = utils.ErrorFromRedirect(utils.NewHTTPStatusErrorFromResponse(res), res)
return
}
switch ce := strings.ToLower(res.Header.Get("Content-Encoding")); ce {
case "":
r = res.Body
case "gzip":
if r, err = gzip.NewReader(res.Body); err != nil {
err = utils.ErrorFromRedirect(err, res)
return
err = utils.NewHTTPStatusErrorFromResponse(res)
}else {
switch ce := strings.ToLower(res.Header.Get("Content-Encoding")); ce {
case "":
r = res.Body
case "gzip":
r, err = gzip.NewReader(res.Body)
case "deflate":
r, err = zlib.NewReader(res.Body)
default:
err = fmt.Errorf("Unexpected Content-Encoding %q", ce)
}
case "deflate":
if r, err = zlib.NewReader(res.Body); err != nil {
err = utils.ErrorFromRedirect(err, res)
return
}
default:
err = utils.ErrorFromRedirect(fmt.Errorf("Unexpected Content-Encoding %q", ce), res)
return
}
if err != nil {
return "", utils.ErrorFromRedirect(err, res)
}
if wrapper != nil {
r = wrapper(r)
Expand Down
26 changes: 26 additions & 0 deletions utils/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package utils

import (
"encoding/json"
"time"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -107,3 +108,28 @@ func (d *YAMLDuration) UnmarshalYAML(n *yaml.Node) (err error) {
*d = (YAMLDuration)(td)
return nil
}

type EmbedJSON[T any] struct {
V T
}

var (
_ json.Marshaler = EmbedJSON[any]{}
_ json.Unmarshaler = (*EmbedJSON[any])(nil)
)

func (e EmbedJSON[T]) MarshalJSON() ([]byte, error) {
data, err := json.Marshal(e.V)
if err != nil {
return nil, err
}
return json.Marshal((string)(data))
}

func (e *EmbedJSON[T]) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
return json.Unmarshal(([]byte)(str), &e.V)
}
69 changes: 69 additions & 0 deletions utils/encoding_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* OpenBmclAPI (Golang Edition)
* Copyright (C) 2024 Kevin Z <zyxkad@gmail.com>
* All rights reserved
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package utils_test

import (
"testing"

"encoding/json"
"reflect"

"github.com/LiterMC/go-openbmclapi/utils"
)

func TestEmbedJSON(t *testing.T) {
type testPayload struct {
A int
B utils.EmbedJSON[struct {
C string
D float64
E utils.EmbedJSON[*string]
F *utils.EmbedJSON[string]
}]
G utils.EmbedJSON[*string]
H *utils.EmbedJSON[string]
I utils.EmbedJSON[*string] `json:",omitempty"`
J *utils.EmbedJSON[string] `json:",omitempty"`
}
var v testPayload
v.A = 1
v.B.V.C = "2\""
v.B.V.D = 3.4
v.B.V.E.V = new(string)
*v.B.V.E.V = `{5"6"7}`
v.B.V.F = &utils.EmbedJSON[string]{
V: "f",
}
data, err := json.Marshal(v)
if err != nil {
t.Fatalf("Marshal error: %v", err)
}
dataStr := (string)(data)
if want := `{"A":1,"B":"{\"C\":\"2\\\"\",\"D\":3.4,\"E\":\"\\\"{5\\\\\\\"6\\\\\\\"7}\\\"\",\"F\":\"\\\"f\\\"\"}","G":"null","H":null,"I":"null"}`; dataStr != want {
t.Fatalf("Marshal error, got %s, want %s", dataStr, want)
}
var w testPayload
if err := json.Unmarshal(data, &w); err != nil {
t.Fatalf("Unmarshal error: %v", err)
}
if !reflect.DeepEqual(w, v) {
t.Fatalf("Unmarshal error, got %#v, want %#v", w, v)
}
}
31 changes: 19 additions & 12 deletions utils/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,30 +506,37 @@ func (c *connHeadReader) Read(buf []byte) (n int, err error) {
return c.Conn.Read(buf)
}

func GetRedirects(req *http.Request) []*url.URL {
redirects := make([]*url.URL, 0, 5)
for req != nil {
redirects = append(redirects, req.URL)
resp := req.Response
if resp == nil {
break
}
req = resp.Request
}
if len(redirects) == 0 {
return nil
}
slices.Reverse(redirects)
return redirects
}

type RedirectError struct {
Redirects []*url.URL
Err error
}

func ErrorFromRedirect(err error, resp *http.Response) *RedirectError {
redirects := make([]*url.URL, 0, 4)
for resp != nil && resp.Request != nil {
redirects = append(redirects, resp.Request.URL)
resp = resp.Request.Response
}
if len(redirects) > 1 {
slices.Reverse(redirects)
} else {
redirects = nil
}
return &RedirectError{
Redirects: redirects,
Redirects: GetRedirects(resp.Request),
Err: err,
}
}

func (e *RedirectError) Error() string {
if len(e.Redirects) == 0 {
if len(e.Redirects) <= 1 {
return e.Err.Error()
}

Expand Down

0 comments on commit 5465766

Please sign in to comment.