diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c842b882c..8de7f6ee1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -34,6 +34,15 @@ jobs: uses: golangci/golangci-lint-action@v3 with: args: --timeout=30m + - name: Jape Analyzer + uses: SiaFoundation/action-golang-analysis@HEAD + with: + analyzers: | + go.sia.tech/jape.Analyzer@master + directories: | + autopilot + bus bus/client + worker worker/client - name: Test uses: n8maninger/action-golang-test@v1 with: @@ -53,12 +62,5 @@ jobs: with: package: "./internal/testing/..." args: "-race;-tags='testing';-timeout=30m" - - name: Check Endpoints - uses: SiaFoundation/action-golang-analysis@HEAD - with: - analyzers: | - go.sia.tech/jape.Analyzer - directories: | - autopilot - name: Build run: go build -o bin/ ./cmd/renterd diff --git a/bus/bus.go b/bus/bus.go index 2d6d4ca14..e3d5a96a3 100644 --- a/bus/bus.go +++ b/bus/bus.go @@ -1998,10 +1998,7 @@ func (b *bus) metricsHandlerPUT(jc jape.Context) { } func (b *bus) metricsHandlerGET(jc jape.Context) { - key := jc.PathParam("key") - // parse mandatory query parameters - var err error var start time.Time var n uint64 var interval time.Duration @@ -2014,31 +2011,30 @@ func (b *bus) metricsHandlerGET(jc jape.Context) { } // parse optional query parameters - switch key { + switch key := jc.PathParam("key"); key { case api.MetricContract: - var metrics []api.ContractMetric var opts api.ContractMetricsQueryOpts if jc.DecodeForm("fcid", &opts.ContractID) != nil { return } else if jc.DecodeForm("host", &opts.HostKey) != nil { return - } else if metrics, err = b.mtrcs.ContractMetrics(jc.Request.Context(), start, n, interval, opts); jc.Check("failed to get contract metrics", err) != nil { + } else if metrics, err := b.metrics(jc.Request.Context(), key, start, n, interval, opts); jc.Check("failed to get contract metrics", err) != nil { + return + } else { + jc.Encode(metrics) return } - jc.Encode(metrics) - return case api.MetricContractSet: - var metrics []api.ContractSetMetric var opts api.ContractSetMetricsQueryOpts if jc.DecodeForm("name", &opts.Name) != nil { return - } else if metrics, err = b.mtrcs.ContractSetMetrics(jc.Request.Context(), start, n, interval, opts); jc.Check("failed to get contract set metrics", err) != nil { + } else if metrics, err := b.metrics(jc.Request.Context(), key, start, n, interval, opts); jc.Check("failed to get contract set metrics", err) != nil { + return + } else { + jc.Encode(metrics) return } - jc.Encode(metrics) - return case api.MetricContractSetChurn: - var metrics []api.ContractSetChurnMetric var opts api.ContractSetChurnMetricsQueryOpts if jc.DecodeForm("name", &opts.Name) != nil { return @@ -2046,17 +2042,30 @@ func (b *bus) metricsHandlerGET(jc jape.Context) { return } else if jc.DecodeForm("reason", &opts.Reason) != nil { return - } else if metrics, err = b.mtrcs.ContractSetChurnMetrics(jc.Request.Context(), start, n, interval, opts); jc.Check("failed to get contract churn metrics", err) != nil { + } else if metrics, err := b.metrics(jc.Request.Context(), key, start, n, interval, opts); jc.Check("failed to get contract churn metrics", err) != nil { + return + } else { + jc.Encode(metrics) return } - jc.Encode(metrics) - return default: jc.Error(fmt.Errorf("unknown metric '%s'", key), http.StatusBadRequest) return } } +func (b *bus) metrics(ctx context.Context, key string, start time.Time, n uint64, interval time.Duration, opts interface{}) (interface{}, error) { + switch key { + case api.MetricContract: + return b.mtrcs.ContractMetrics(ctx, start, n, interval, opts.(api.ContractMetricsQueryOpts)) + case api.MetricContractSet: + return b.mtrcs.ContractSetMetrics(ctx, start, n, interval, opts.(api.ContractSetMetricsQueryOpts)) + case api.MetricContractSetChurn: + return b.mtrcs.ContractSetChurnMetrics(ctx, start, n, interval, opts.(api.ContractSetChurnMetricsQueryOpts)) + } + return nil, nil +} + func (b *bus) multipartHandlerCreatePOST(jc jape.Context) { var req api.MultipartCreateRequest if jc.Decode(&req) != nil { diff --git a/bus/client/metrics.go b/bus/client/metrics.go index f6a220de0..f8b1e4270 100644 --- a/bus/client/metrics.go +++ b/bus/client/metrics.go @@ -2,7 +2,11 @@ package client import ( "context" + "encoding/json" + "errors" "fmt" + "io" + "net/http" "net/url" "time" @@ -10,17 +14,20 @@ import ( "go.sia.tech/renterd/api" ) -func (c *Client) ContractSetMetrics(ctx context.Context, start time.Time, n uint64, interval time.Duration, opts api.ContractSetMetricsQueryOpts) ([]api.ContractSetMetric, error) { +func (c *Client) ContractMetrics(ctx context.Context, start time.Time, n uint64, interval time.Duration, opts api.ContractMetricsQueryOpts) ([]api.ContractMetric, error) { values := url.Values{} values.Set("start", api.TimeRFC3339(start).String()) values.Set("n", fmt.Sprint(n)) values.Set("interval", api.DurationMS(interval).String()) - if opts.Name != "" { - values.Set("name", opts.Name) + if opts.ContractID != (types.FileContractID{}) { + values.Set("fcid", opts.ContractID.String()) } - var resp []api.ContractSetMetric - err := c.c.WithContext(ctx).GET(fmt.Sprintf("/metric/%s?%s", api.MetricContractSet, values.Encode()), &resp) - if err != nil { + if opts.HostKey != (types.PublicKey{}) { + values.Set("hostKey", opts.HostKey.String()) + } + + var resp []api.ContractMetric + if err := c.metric(ctx, api.MetricContract, values, &resp); err != nil { return nil, err } return resp, nil @@ -40,9 +47,25 @@ func (c *Client) ContractSetChurnMetrics(ctx context.Context, start time.Time, n if opts.Reason != "" { values.Set("reason", string(opts.Reason)) } + var resp []api.ContractSetChurnMetric - err := c.c.WithContext(ctx).GET(fmt.Sprintf("/metric/%s?%s", api.MetricContractSetChurn, values.Encode()), &resp) - if err != nil { + if err := c.metric(ctx, api.MetricContractSetChurn, values, &resp); err != nil { + return nil, err + } + return resp, nil +} + +func (c *Client) ContractSetMetrics(ctx context.Context, start time.Time, n uint64, interval time.Duration, opts api.ContractSetMetricsQueryOpts) ([]api.ContractSetMetric, error) { + values := url.Values{} + values.Set("start", api.TimeRFC3339(start).String()) + values.Set("n", fmt.Sprint(n)) + values.Set("interval", api.DurationMS(interval).String()) + if opts.Name != "" { + values.Set("name", opts.Name) + } + + var resp []api.ContractSetMetric + if err := c.metric(ctx, api.MetricContractSet, values, &resp); err != nil { return nil, err } return resp, nil @@ -54,21 +77,29 @@ func (c *Client) RecordContractSetChurnMetric(ctx context.Context, metrics ...ap }) } -func (c *Client) ContractMetrics(ctx context.Context, start time.Time, n uint64, interval time.Duration, opts api.ContractMetricsQueryOpts) ([]api.ContractMetric, error) { - values := url.Values{} - values.Set("start", api.TimeRFC3339(start).String()) - values.Set("n", fmt.Sprint(n)) - values.Set("interval", api.DurationMS(interval).String()) - if opts.ContractID != (types.FileContractID{}) { - values.Set("fcid", opts.ContractID.String()) +func (c *Client) metric(ctx context.Context, key string, values url.Values, res interface{}) error { + c.c.Custom("GET", fmt.Sprintf("/metric/%s", key), nil, (*interface{})(nil)) + + u, err := url.Parse(fmt.Sprintf("%s/metric/%s", c.c.BaseURL, key)) + if err != nil { + panic(err) } - if opts.HostKey != (types.PublicKey{}) { - values.Set("hostKey", opts.HostKey.String()) + u.RawQuery = values.Encode() + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + if err != nil { + panic(err) } - var resp []api.ContractMetric - err := c.c.WithContext(ctx).GET(fmt.Sprintf("/metric/%s?%s", api.MetricContract, values.Encode()), &resp) + req.SetBasicAuth("", c.c.WithContext(ctx).Password) + resp, err := http.DefaultClient.Do(req) if err != nil { - return nil, err + return err } - return resp, nil + defer io.Copy(io.Discard, resp.Body) + defer resp.Body.Close() + + if resp.StatusCode != 200 && resp.StatusCode != 206 { + err, _ := io.ReadAll(resp.Body) + return errors.New(string(err)) + } + return json.NewDecoder(resp.Body).Decode(&res) } diff --git a/go.mod b/go.mod index e1fd80282..df01f6ba9 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( go.sia.tech/core v0.1.12-0.20231011172826-6ca0ac7b3b6b go.sia.tech/gofakes3 v0.0.0-20231109151325-e0d47c10dce2 go.sia.tech/hostd v0.2.1-0.20231013174940-920057ff41c8 - go.sia.tech/jape v0.11.0 + go.sia.tech/jape v0.11.1 go.sia.tech/mux v1.2.0 go.sia.tech/siad v1.5.10-0.20230228235644-3059c0b930ca go.sia.tech/web/renterd v0.33.0 diff --git a/go.sum b/go.sum index 5622051c3..85353b11d 100644 --- a/go.sum +++ b/go.sum @@ -311,8 +311,8 @@ go.sia.tech/gofakes3 v0.0.0-20231109151325-e0d47c10dce2 h1:ulzfJNjxN5DjXHClkW2pT go.sia.tech/gofakes3 v0.0.0-20231109151325-e0d47c10dce2/go.mod h1:PlsiVCn6+wssrR7bsOIlZm0DahsVrDydrlbjY4F14sg= go.sia.tech/hostd v0.2.1-0.20231013174940-920057ff41c8 h1:0kVIAauXG2+C5EZWlnJDVtf6TrLLgB+EcObuCJyDNfY= go.sia.tech/hostd v0.2.1-0.20231013174940-920057ff41c8/go.mod h1:B+jY+eJ2jlcowcXwYOb28N/A6/cy2dblX/WUec9pFM8= -go.sia.tech/jape v0.11.0 h1:S2JTONZ4FGl5JFmh3VFGkieuFB1wXRLImkHc859V0FY= -go.sia.tech/jape v0.11.0/go.mod h1:4QqmBB+t3W7cNplXPj++ZqpoUb2PeiS66RLpXmEGap4= +go.sia.tech/jape v0.11.1 h1:M7IP+byXL7xOqzxcHUQuXW+q3sYMkYzmMlMw+q8ZZw0= +go.sia.tech/jape v0.11.1/go.mod h1:4QqmBB+t3W7cNplXPj++ZqpoUb2PeiS66RLpXmEGap4= go.sia.tech/mux v1.2.0 h1:ofa1Us9mdymBbGMY2XH/lSpY8itFsKIo/Aq8zwe+GHU= go.sia.tech/mux v1.2.0/go.mod h1:Yyo6wZelOYTyvrHmJZ6aQfRoer3o4xyKQ4NmQLJrBSo= go.sia.tech/siad v1.5.10-0.20230228235644-3059c0b930ca h1:aZMg2AKevn7jKx+wlusWQfwSM5pNU9aGtRZme29q3O4= diff --git a/worker/client/rhp.go b/worker/client/rhp.go index 175df47e3..a8ff950ba 100644 --- a/worker/client/rhp.go +++ b/worker/client/rhp.go @@ -14,23 +14,23 @@ import ( ) // RHPBroadcast broadcasts the latest revision for a contract. -func (c *Client) RHPBroadcast(ctx context.Context, fcid types.FileContractID) (err error) { - err = c.c.WithContext(ctx).POST(fmt.Sprintf("/rhp/contract/%s/broadcast", fcid), nil, nil) +func (c *Client) RHPBroadcast(ctx context.Context, contractID types.FileContractID) (err error) { + err = c.c.WithContext(ctx).POST(fmt.Sprintf("/rhp/contract/%s/broadcast", contractID), nil, nil) return } // RHPContractRoots fetches the roots of the contract with given id. -func (c *Client) RHPContractRoots(ctx context.Context, fcid types.FileContractID) (roots []types.Hash256, err error) { - err = c.c.WithContext(ctx).GET(fmt.Sprintf("/rhp/contract/%s/roots", fcid), &roots) +func (c *Client) RHPContractRoots(ctx context.Context, contractID types.FileContractID) (roots []types.Hash256, err error) { + err = c.c.WithContext(ctx).GET(fmt.Sprintf("/rhp/contract/%s/roots", contractID), &roots) return } // RHPForm forms a contract with a host. -func (c *Client) RHPForm(ctx context.Context, endHeight uint64, hk types.PublicKey, hostIP string, renterAddress types.Address, renterFunds types.Currency, hostCollateral types.Currency) (rhpv2.ContractRevision, []types.Transaction, error) { +func (c *Client) RHPForm(ctx context.Context, endHeight uint64, hostKey types.PublicKey, hostIP string, renterAddress types.Address, renterFunds types.Currency, hostCollateral types.Currency) (rhpv2.ContractRevision, []types.Transaction, error) { req := api.RHPFormRequest{ EndHeight: endHeight, HostCollateral: hostCollateral, - HostKey: hk, + HostKey: hostKey, HostIP: hostIP, RenterFunds: renterFunds, RenterAddress: renterAddress, @@ -64,9 +64,9 @@ func (c *Client) RHPPriceTable(ctx context.Context, hostKey types.PublicKey, sia } // RHPPruneContract prunes deleted sectors from the contract with given id. -func (c *Client) RHPPruneContract(ctx context.Context, fcid types.FileContractID, timeout time.Duration) (pruned, remaining uint64, err error) { +func (c *Client) RHPPruneContract(ctx context.Context, contractID types.FileContractID, timeout time.Duration) (pruned, remaining uint64, err error) { var res api.RHPPruneContractResponse - err = c.c.WithContext(ctx).POST(fmt.Sprintf("/rhp/contract/%s/prune", fcid), api.RHPPruneContractRequest{ + err = c.c.WithContext(ctx).POST(fmt.Sprintf("/rhp/contract/%s/prune", contractID), api.RHPPruneContractRequest{ Timeout: api.DurationMS(timeout), }, &res) pruned = res.Pruned @@ -87,12 +87,12 @@ func (c *Client) RHPReadRegistry(ctx context.Context, hostKey types.PublicKey, s } // RHPRenew renews an existing contract with a host. -func (c *Client) RHPRenew(ctx context.Context, fcid types.FileContractID, endHeight uint64, hk types.PublicKey, siamuxAddr string, hostAddress, renterAddress types.Address, renterFunds, newCollateral types.Currency, windowSize uint64) (rhpv2.ContractRevision, []types.Transaction, error) { +func (c *Client) RHPRenew(ctx context.Context, contractID types.FileContractID, endHeight uint64, hostKey types.PublicKey, siamuxAddr string, hostAddress, renterAddress types.Address, renterFunds, newCollateral types.Currency, windowSize uint64) (rhpv2.ContractRevision, []types.Transaction, error) { req := api.RHPRenewRequest{ - ContractID: fcid, + ContractID: contractID, EndHeight: endHeight, HostAddress: hostAddress, - HostKey: hk, + HostKey: hostKey, NewCollateral: newCollateral, RenterAddress: renterAddress, RenterFunds: renterFunds, diff --git a/worker/worker.go b/worker/worker.go index 81f4c4278..b537d448e 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -909,7 +909,7 @@ func (w *worker) slabMigrateHandler(jc jape.Context) { // migrate the slab numShardsMigrated, surchargeApplied, err := migrateSlab(ctx, w.downloadManager, w.uploadManager, &slab, dlContracts, ulContracts, up.CurrentHeight, w.logger) - if jc.Check("couldn't migrate slabs", err) != nil { + if err != nil { jc.Encode(api.MigrateSlabResponse{ NumShardsMigrated: numShardsMigrated, SurchargeApplied: surchargeApplied,