9b9d76f92d
This commit introduces the new go-libp2p-gorpc streaming capabilities for Cluster. The main aim is to work towards heavily reducing memory usage when working with very large pinsets. As a side-effect, it takes the chance to revampt all types for all public methods so that pointers to static what should be static objects are not used anymore. This should heavily reduce heap allocations and GC activity. The main change is that state.List now returns a channel from which to read the pins, rather than pins being all loaded into a huge slice. Things reading pins have been all updated to iterate on the channel rather than on the slice. The full pinset is no longer fully loaded onto memory for things that run regularly like StateSync(). Additionally, the /allocations endpoint of the rest API no longer returns an array of pins, but rather streams json-encoded pin objects directly. This change has extended to the restapi client (which puts pins into a channel as they arrive) and to ipfs-cluster-ctl. There are still pending improvements like StatusAll() calls which should also stream responses, and specially BlockPut calls which should stream blocks directly into IPFS on a single call. These are coming up in future commits.
784 lines
17 KiB
Go
784 lines
17 KiB
Go
// Package rest implements an IPFS Cluster API component. It provides
|
|
// a REST-ish API to interact with Cluster.
|
|
//
|
|
// The implented API is based on the common.API component (refer to module
|
|
// description there). The only thing this module does is to provide route
|
|
// handling for the otherwise common API component.
|
|
package rest
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
"github.com/ipfs/ipfs-cluster/adder/adderutils"
|
|
types "github.com/ipfs/ipfs-cluster/api"
|
|
"github.com/ipfs/ipfs-cluster/api/common"
|
|
"go.uber.org/multierr"
|
|
|
|
logging "github.com/ipfs/go-log/v2"
|
|
"github.com/libp2p/go-libp2p-core/host"
|
|
peer "github.com/libp2p/go-libp2p-core/peer"
|
|
rpc "github.com/libp2p/go-libp2p-gorpc"
|
|
|
|
mux "github.com/gorilla/mux"
|
|
)
|
|
|
|
func init() {
|
|
rand.Seed(time.Now().UnixNano())
|
|
}
|
|
|
|
var (
|
|
logger = logging.Logger("restapi")
|
|
apiLogger = logging.Logger("restapilog")
|
|
)
|
|
|
|
type peerAddBody struct {
|
|
PeerID string `json:"peer_id"`
|
|
}
|
|
|
|
// API implements the REST API Component.
|
|
// It embeds a common.API.
|
|
type API struct {
|
|
*common.API
|
|
|
|
rpcClient *rpc.Client
|
|
config *Config
|
|
}
|
|
|
|
// NewAPI creates a new REST API component.
|
|
func NewAPI(ctx context.Context, cfg *Config) (*API, error) {
|
|
return NewAPIWithHost(ctx, cfg, nil)
|
|
}
|
|
|
|
// NewAPI creates a new REST API component using the given libp2p Host.
|
|
func NewAPIWithHost(ctx context.Context, cfg *Config, h host.Host) (*API, error) {
|
|
api := API{
|
|
config: cfg,
|
|
}
|
|
capi, err := common.NewAPIWithHost(ctx, &cfg.Config, h, api.routes)
|
|
api.API = capi
|
|
return &api, err
|
|
}
|
|
|
|
// Routes returns endpoints supported by this API.
|
|
func (api *API) routes(c *rpc.Client) []common.Route {
|
|
api.rpcClient = c
|
|
return []common.Route{
|
|
{
|
|
Name: "ID",
|
|
Method: "GET",
|
|
Pattern: "/id",
|
|
HandlerFunc: api.idHandler,
|
|
},
|
|
|
|
{
|
|
Name: "Version",
|
|
Method: "GET",
|
|
Pattern: "/version",
|
|
HandlerFunc: api.versionHandler,
|
|
},
|
|
|
|
{
|
|
Name: "Peers",
|
|
Method: "GET",
|
|
Pattern: "/peers",
|
|
HandlerFunc: api.peerListHandler,
|
|
},
|
|
{
|
|
Name: "PeerAdd",
|
|
Method: "POST",
|
|
Pattern: "/peers",
|
|
HandlerFunc: api.peerAddHandler,
|
|
},
|
|
{
|
|
Name: "PeerRemove",
|
|
Method: "DELETE",
|
|
Pattern: "/peers/{peer}",
|
|
HandlerFunc: api.peerRemoveHandler,
|
|
},
|
|
{
|
|
Name: "Add",
|
|
Method: "POST",
|
|
Pattern: "/add",
|
|
HandlerFunc: api.addHandler,
|
|
},
|
|
{
|
|
Name: "Allocations",
|
|
Method: "GET",
|
|
Pattern: "/allocations",
|
|
HandlerFunc: api.allocationsHandler,
|
|
},
|
|
{
|
|
Name: "Allocation",
|
|
Method: "GET",
|
|
Pattern: "/allocations/{hash}",
|
|
HandlerFunc: api.allocationHandler,
|
|
},
|
|
{
|
|
Name: "StatusAll",
|
|
Method: "GET",
|
|
Pattern: "/pins",
|
|
HandlerFunc: api.statusAllHandler,
|
|
},
|
|
{
|
|
Name: "Recover",
|
|
Method: "POST",
|
|
Pattern: "/pins/{hash}/recover",
|
|
HandlerFunc: api.recoverHandler,
|
|
},
|
|
{
|
|
Name: "RecoverAll",
|
|
Method: "POST",
|
|
Pattern: "/pins/recover",
|
|
HandlerFunc: api.recoverAllHandler,
|
|
},
|
|
{
|
|
Name: "Status",
|
|
Method: "GET",
|
|
Pattern: "/pins/{hash}",
|
|
HandlerFunc: api.statusHandler,
|
|
},
|
|
{
|
|
Name: "Pin",
|
|
Method: "POST",
|
|
Pattern: "/pins/{hash}",
|
|
HandlerFunc: api.pinHandler,
|
|
},
|
|
{
|
|
Name: "PinPath",
|
|
Method: "POST",
|
|
Pattern: "/pins/{keyType:ipfs|ipns|ipld}/{path:.*}",
|
|
HandlerFunc: api.pinPathHandler,
|
|
},
|
|
{
|
|
Name: "Unpin",
|
|
Method: "DELETE",
|
|
Pattern: "/pins/{hash}",
|
|
HandlerFunc: api.unpinHandler,
|
|
},
|
|
{
|
|
Name: "UnpinPath",
|
|
Method: "DELETE",
|
|
Pattern: "/pins/{keyType:ipfs|ipns|ipld}/{path:.*}",
|
|
HandlerFunc: api.unpinPathHandler,
|
|
},
|
|
{
|
|
Name: "RepoGC",
|
|
Method: "POST",
|
|
Pattern: "/ipfs/gc",
|
|
HandlerFunc: api.repoGCHandler,
|
|
},
|
|
{
|
|
Name: "ConnectionGraph",
|
|
Method: "GET",
|
|
Pattern: "/health/graph",
|
|
HandlerFunc: api.graphHandler,
|
|
},
|
|
{
|
|
Name: "Alerts",
|
|
Method: "GET",
|
|
Pattern: "/health/alerts",
|
|
HandlerFunc: api.alertsHandler,
|
|
},
|
|
{
|
|
Name: "Metrics",
|
|
Method: "GET",
|
|
Pattern: "/monitor/metrics/{name}",
|
|
HandlerFunc: api.metricsHandler,
|
|
},
|
|
{
|
|
Name: "MetricNames",
|
|
Method: "GET",
|
|
Pattern: "/monitor/metrics",
|
|
HandlerFunc: api.metricNamesHandler,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (api *API) idHandler(w http.ResponseWriter, r *http.Request) {
|
|
var id types.ID
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"ID",
|
|
struct{}{},
|
|
&id,
|
|
)
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, &id)
|
|
}
|
|
|
|
func (api *API) versionHandler(w http.ResponseWriter, r *http.Request) {
|
|
var v types.Version
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Version",
|
|
struct{}{},
|
|
&v,
|
|
)
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, v)
|
|
}
|
|
|
|
func (api *API) graphHandler(w http.ResponseWriter, r *http.Request) {
|
|
var graph types.ConnectGraph
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"ConnectGraph",
|
|
struct{}{},
|
|
&graph,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, graph)
|
|
}
|
|
|
|
func (api *API) metricsHandler(w http.ResponseWriter, r *http.Request) {
|
|
vars := mux.Vars(r)
|
|
name := vars["name"]
|
|
|
|
var metrics []types.Metric
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"PeerMonitor",
|
|
"LatestMetrics",
|
|
name,
|
|
&metrics,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, metrics)
|
|
}
|
|
|
|
func (api *API) metricNamesHandler(w http.ResponseWriter, r *http.Request) {
|
|
var metricNames []string
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"PeerMonitor",
|
|
"MetricNames",
|
|
struct{}{},
|
|
&metricNames,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, metricNames)
|
|
}
|
|
|
|
func (api *API) alertsHandler(w http.ResponseWriter, r *http.Request) {
|
|
var alerts []types.Alert
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Alerts",
|
|
struct{}{},
|
|
&alerts,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, alerts)
|
|
}
|
|
|
|
func (api *API) addHandler(w http.ResponseWriter, r *http.Request) {
|
|
reader, err := r.MultipartReader()
|
|
if err != nil {
|
|
api.SendResponse(w, http.StatusBadRequest, err, nil)
|
|
return
|
|
}
|
|
|
|
params, err := types.AddParamsFromQuery(r.URL.Query())
|
|
if err != nil {
|
|
api.SendResponse(w, http.StatusBadRequest, err, nil)
|
|
return
|
|
}
|
|
|
|
api.SetHeaders(w)
|
|
|
|
// any errors sent as trailer
|
|
adderutils.AddMultipartHTTPHandler(
|
|
r.Context(),
|
|
api.rpcClient,
|
|
params,
|
|
reader,
|
|
w,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func (api *API) peerListHandler(w http.ResponseWriter, r *http.Request) {
|
|
var peers []types.ID
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Peers",
|
|
struct{}{},
|
|
&peers,
|
|
)
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, peers)
|
|
}
|
|
|
|
func (api *API) peerAddHandler(w http.ResponseWriter, r *http.Request) {
|
|
dec := json.NewDecoder(r.Body)
|
|
defer r.Body.Close()
|
|
|
|
var addInfo peerAddBody
|
|
err := dec.Decode(&addInfo)
|
|
if err != nil {
|
|
api.SendResponse(w, http.StatusBadRequest, errors.New("error decoding request body"), nil)
|
|
return
|
|
}
|
|
|
|
pid, err := peer.Decode(addInfo.PeerID)
|
|
if err != nil {
|
|
api.SendResponse(w, http.StatusBadRequest, errors.New("error decoding peer_id"), nil)
|
|
return
|
|
}
|
|
|
|
var id types.ID
|
|
err = api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"PeerAdd",
|
|
pid,
|
|
&id,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, &id)
|
|
}
|
|
|
|
func (api *API) peerRemoveHandler(w http.ResponseWriter, r *http.Request) {
|
|
if p := api.ParsePidOrFail(w, r); p != "" {
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"PeerRemove",
|
|
p,
|
|
&struct{}{},
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, nil)
|
|
}
|
|
}
|
|
|
|
func (api *API) pinHandler(w http.ResponseWriter, r *http.Request) {
|
|
if pin := api.ParseCidOrFail(w, r); pin.Defined() {
|
|
api.config.Logger.Debugf("rest api pinHandler: %s", pin.Cid)
|
|
// span.AddAttributes(trace.StringAttribute("cid", pin.Cid))
|
|
var pinObj types.Pin
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Pin",
|
|
pin,
|
|
&pinObj,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinObj)
|
|
api.config.Logger.Debug("rest api pinHandler done")
|
|
}
|
|
}
|
|
|
|
func (api *API) unpinHandler(w http.ResponseWriter, r *http.Request) {
|
|
if pin := api.ParseCidOrFail(w, r); pin.Defined() {
|
|
api.config.Logger.Debugf("rest api unpinHandler: %s", pin.Cid)
|
|
// span.AddAttributes(trace.StringAttribute("cid", pin.Cid))
|
|
var pinObj types.Pin
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Unpin",
|
|
pin,
|
|
&pinObj,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinObj)
|
|
api.config.Logger.Debug("rest api unpinHandler done")
|
|
}
|
|
}
|
|
|
|
func (api *API) pinPathHandler(w http.ResponseWriter, r *http.Request) {
|
|
var pin types.Pin
|
|
if pinpath := api.ParsePinPathOrFail(w, r); pinpath.Defined() {
|
|
api.config.Logger.Debugf("rest api pinPathHandler: %s", pinpath.Path)
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"PinPath",
|
|
pinpath,
|
|
&pin,
|
|
)
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pin)
|
|
api.config.Logger.Debug("rest api pinPathHandler done")
|
|
}
|
|
}
|
|
|
|
func (api *API) unpinPathHandler(w http.ResponseWriter, r *http.Request) {
|
|
var pin types.Pin
|
|
if pinpath := api.ParsePinPathOrFail(w, r); pinpath.Defined() {
|
|
api.config.Logger.Debugf("rest api unpinPathHandler: %s", pinpath.Path)
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"UnpinPath",
|
|
pinpath,
|
|
&pin,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pin)
|
|
api.config.Logger.Debug("rest api unpinPathHandler done")
|
|
}
|
|
}
|
|
|
|
func (api *API) allocationsHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
filterStr := queryValues.Get("filter")
|
|
var filter types.PinType
|
|
for _, f := range strings.Split(filterStr, ",") {
|
|
filter |= types.PinTypeFromString(f)
|
|
}
|
|
|
|
if filter == types.BadType {
|
|
api.SendResponse(w, http.StatusBadRequest, errors.New("invalid filter value"), nil)
|
|
return
|
|
}
|
|
|
|
in := make(chan struct{})
|
|
close(in)
|
|
|
|
pins := make(chan types.Pin)
|
|
|
|
ctx, cancel := context.WithCancel(r.Context())
|
|
defer cancel()
|
|
|
|
go func() {
|
|
err := api.rpcClient.Stream(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Pins",
|
|
in,
|
|
pins,
|
|
)
|
|
if err != nil {
|
|
logger.Error(err)
|
|
cancel()
|
|
}
|
|
}()
|
|
|
|
iter := func() (interface{}, bool, error) {
|
|
var p types.Pin
|
|
var ok bool
|
|
iterloop:
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
break iterloop
|
|
case p, ok = <-pins:
|
|
if !ok {
|
|
break iterloop
|
|
}
|
|
// this means we keep iterating if no filter
|
|
// matched
|
|
if filter == types.AllType || filter&p.Type > 0 {
|
|
break iterloop
|
|
}
|
|
}
|
|
}
|
|
return p, ok, ctx.Err()
|
|
}
|
|
|
|
api.StreamResponse(w, iter)
|
|
}
|
|
|
|
func (api *API) allocationHandler(w http.ResponseWriter, r *http.Request) {
|
|
if pin := api.ParseCidOrFail(w, r); pin.Defined() {
|
|
var pinResp types.Pin
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"PinGet",
|
|
pin.Cid,
|
|
&pinResp,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinResp)
|
|
}
|
|
}
|
|
|
|
func (api *API) statusAllHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
if queryValues.Get("cids") != "" {
|
|
api.statusCidsHandler(w, r)
|
|
return
|
|
}
|
|
|
|
local := queryValues.Get("local")
|
|
|
|
var globalPinInfos []types.GlobalPinInfo
|
|
|
|
filterStr := queryValues.Get("filter")
|
|
filter := types.TrackerStatusFromString(filterStr)
|
|
// FIXME: This is a bit lazy, as "invalidxx,pinned" would result in a
|
|
// valid "pinned" filter.
|
|
if filter == types.TrackerStatusUndefined && filterStr != "" {
|
|
api.SendResponse(w, http.StatusBadRequest, errors.New("invalid filter value"), nil)
|
|
return
|
|
}
|
|
|
|
if local == "true" {
|
|
var pinInfos []types.PinInfo
|
|
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"StatusAllLocal",
|
|
filter,
|
|
&pinInfos,
|
|
)
|
|
if err != nil {
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, nil)
|
|
return
|
|
}
|
|
globalPinInfos = pinInfosToGlobal(pinInfos)
|
|
} else {
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"StatusAll",
|
|
filter,
|
|
&globalPinInfos,
|
|
)
|
|
if err != nil {
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, nil)
|
|
return
|
|
}
|
|
}
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, nil, globalPinInfos)
|
|
}
|
|
|
|
// request statuses for multiple CIDs in parallel.
|
|
func (api *API) statusCidsHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
filterCidsStr := strings.Split(queryValues.Get("cids"), ",")
|
|
var cids []cid.Cid
|
|
|
|
for _, cidStr := range filterCidsStr {
|
|
c, err := cid.Decode(cidStr)
|
|
if err != nil {
|
|
api.SendResponse(w, http.StatusBadRequest, fmt.Errorf("error decoding Cid: %w", err), nil)
|
|
return
|
|
}
|
|
cids = append(cids, c)
|
|
}
|
|
|
|
local := queryValues.Get("local")
|
|
|
|
type gpiResult struct {
|
|
gpi types.GlobalPinInfo
|
|
err error
|
|
}
|
|
gpiCh := make(chan gpiResult, len(cids))
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(cids))
|
|
|
|
// Close channel when done
|
|
go func() {
|
|
wg.Wait()
|
|
close(gpiCh)
|
|
}()
|
|
|
|
if local == "true" {
|
|
for _, ci := range cids {
|
|
go func(c cid.Cid) {
|
|
defer wg.Done()
|
|
var pinInfo types.PinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"StatusLocal",
|
|
c,
|
|
&pinInfo,
|
|
)
|
|
gpiCh <- gpiResult{gpi: pinInfo.ToGlobal(), err: err}
|
|
}(ci)
|
|
}
|
|
} else {
|
|
for _, ci := range cids {
|
|
go func(c cid.Cid) {
|
|
defer wg.Done()
|
|
var pinInfo types.GlobalPinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Status",
|
|
c,
|
|
&pinInfo,
|
|
)
|
|
gpiCh <- gpiResult{gpi: pinInfo, err: err}
|
|
}(ci)
|
|
}
|
|
}
|
|
|
|
var gpis []types.GlobalPinInfo
|
|
var err error
|
|
for gpiResult := range gpiCh {
|
|
gpis = append(gpis, gpiResult.gpi)
|
|
err = multierr.Append(err, gpiResult.err)
|
|
}
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, gpis)
|
|
}
|
|
|
|
func (api *API) statusHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
local := queryValues.Get("local")
|
|
|
|
if pin := api.ParseCidOrFail(w, r); pin.Defined() {
|
|
if local == "true" {
|
|
var pinInfo types.PinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"StatusLocal",
|
|
pin.Cid,
|
|
&pinInfo,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinInfo.ToGlobal())
|
|
} else {
|
|
var pinInfo types.GlobalPinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Status",
|
|
pin.Cid,
|
|
&pinInfo,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (api *API) recoverAllHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
local := queryValues.Get("local")
|
|
if local == "true" {
|
|
var pinInfos []types.PinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"RecoverAllLocal",
|
|
struct{}{},
|
|
&pinInfos,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinInfosToGlobal(pinInfos))
|
|
} else {
|
|
var globalPinInfos []types.GlobalPinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"RecoverAll",
|
|
struct{}{},
|
|
&globalPinInfos,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, globalPinInfos)
|
|
}
|
|
}
|
|
|
|
func (api *API) recoverHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
local := queryValues.Get("local")
|
|
|
|
if pin := api.ParseCidOrFail(w, r); pin.Defined() {
|
|
if local == "true" {
|
|
var pinInfo types.PinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"RecoverLocal",
|
|
pin.Cid,
|
|
&pinInfo,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinInfo.ToGlobal())
|
|
} else {
|
|
var pinInfo types.GlobalPinInfo
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"Recover",
|
|
pin.Cid,
|
|
&pinInfo,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, pinInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (api *API) repoGCHandler(w http.ResponseWriter, r *http.Request) {
|
|
queryValues := r.URL.Query()
|
|
local := queryValues.Get("local")
|
|
|
|
if local == "true" {
|
|
var localRepoGC types.RepoGC
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"RepoGCLocal",
|
|
struct{}{},
|
|
&localRepoGC,
|
|
)
|
|
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, repoGCToGlobal(localRepoGC))
|
|
return
|
|
}
|
|
|
|
var repoGC types.GlobalRepoGC
|
|
err := api.rpcClient.CallContext(
|
|
r.Context(),
|
|
"",
|
|
"Cluster",
|
|
"RepoGC",
|
|
struct{}{},
|
|
&repoGC,
|
|
)
|
|
api.SendResponse(w, common.SetStatusAutomatically, err, repoGC)
|
|
}
|
|
|
|
func repoGCToGlobal(r types.RepoGC) types.GlobalRepoGC {
|
|
return types.GlobalRepoGC{
|
|
PeerMap: map[string]types.RepoGC{
|
|
peer.Encode(r.Peer): r,
|
|
},
|
|
}
|
|
}
|
|
|
|
func pinInfosToGlobal(pInfos []types.PinInfo) []types.GlobalPinInfo {
|
|
gPInfos := make([]types.GlobalPinInfo, len(pInfos))
|
|
for i, p := range pInfos {
|
|
gpi := p.ToGlobal()
|
|
gPInfos[i] = gpi
|
|
}
|
|
return gPInfos
|
|
}
|