ipfs-cluster/cmd/ipfs-cluster-ctl/formatters.go
Hector Sanjuan d1700dbe81 Fixes #1319: Status wrongly shows pins as REMOTE
The Allocations of a pin that has been added with default replication factor
are kept even when the replication factor turns out to be -1.

This resulted in the Status(cid) code skipping calls to a number of peers
and setting the pin directly as REMOTE.

The fix, on one side makes sure Allocations is always nil when the replication
factor is -1. On the other size, lets the globalPinInfoCid method check the
replication factor value, rather than the number of allocations to decide if
any nodes are bound to be remote.

On the plus side, the pin tracker used the IsRemotePin method, which uses the
replication factor, so things were pinned even if the Status(cid) method shows
them as remote.
2021-03-24 00:47:15 +01:00

305 lines
6.6 KiB
Go

package main
import (
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/ipfs/ipfs-cluster/api"
peer "github.com/libp2p/go-libp2p-core/peer"
humanize "github.com/dustin/go-humanize"
)
type addedOutputQuiet struct {
*api.AddedOutput
quiet bool
}
func jsonFormatObject(resp interface{}) {
switch r := resp.(type) {
case nil:
return
case []*addedOutputQuiet:
// print original objects as in JSON it makes
// no sense to have a human "quiet" output
var actual []*api.AddedOutput
for _, s := range r {
actual = append(actual, s.AddedOutput)
}
jsonFormatPrint(actual)
default:
jsonFormatPrint(resp)
}
}
func jsonFormatPrint(obj interface{}) {
j, err := json.MarshalIndent(obj, "", " ")
checkErr("generating json output", err)
fmt.Printf("%s\n", j)
}
func textFormatObject(resp interface{}) {
switch r := resp.(type) {
case nil:
return
case string:
fmt.Println(resp)
case *api.ID:
textFormatPrintID(r)
case *api.GlobalPinInfo:
textFormatPrintGPInfo(r)
case *api.Pin:
textFormatPrintPin(r)
case *api.AddedOutput:
textFormatPrintAddedOutput(r)
case *addedOutputQuiet:
textFormatPrintAddedOutputQuiet(r)
case *api.Version:
textFormatPrintVersion(r)
case *api.Error:
textFormatPrintError(r)
case *api.Metric:
textFormatPrintMetric(r)
case *api.Alert:
textFormatPrintAlert(r)
case []*api.ID:
for _, item := range r {
textFormatObject(item)
}
case []*api.GlobalPinInfo:
for _, item := range r {
textFormatObject(item)
}
case []*api.Pin:
for _, item := range r {
textFormatObject(item)
}
case []*api.AddedOutput:
for _, item := range r {
textFormatObject(item)
}
case []*addedOutputQuiet:
for _, item := range r {
textFormatObject(item)
}
case []*api.Metric:
for _, item := range r {
textFormatObject(item)
}
case *api.GlobalRepoGC:
textFormatPrintGlobalRepoGC(r)
case []string:
for _, item := range r {
textFormatObject(item)
}
case []*api.Alert:
for _, item := range r {
textFormatObject(item)
}
default:
checkErr("", errors.New("unsupported type returned"))
}
}
func textFormatPrintID(obj *api.ID) {
if obj.Error != "" {
fmt.Printf("%s | ERROR: %s\n", obj.ID.Pretty(), obj.Error)
return
}
fmt.Printf(
"%s | %s | Sees %d other peers\n",
obj.ID.Pretty(),
obj.Peername,
len(obj.ClusterPeers)-1,
)
addrs := make(sort.StringSlice, 0, len(obj.Addresses))
for _, a := range obj.Addresses {
addrs = append(addrs, a.String())
}
addrs.Sort()
fmt.Println(" > Addresses:")
for _, a := range addrs {
fmt.Printf(" - %s\n", a)
}
if obj.IPFS.Error != "" {
fmt.Printf(" > IPFS ERROR: %s\n", obj.IPFS.Error)
return
}
ipfsAddrs := make(sort.StringSlice, 0, len(obj.Addresses))
for _, a := range obj.IPFS.Addresses {
ipfsAddrs = append(ipfsAddrs, a.String())
}
ipfsAddrs.Sort()
fmt.Printf(" > IPFS: %s\n", obj.IPFS.ID.Pretty())
for _, a := range ipfsAddrs {
fmt.Printf(" - %s\n", a)
}
}
func textFormatPrintGPInfo(obj *api.GlobalPinInfo) {
var b strings.Builder
peers := make([]string, 0, len(obj.PeerMap))
for k := range obj.PeerMap {
peers = append(peers, k)
}
sort.Strings(peers)
fmt.Fprintf(&b, "%s", obj.Cid)
if obj.Name != "" {
fmt.Fprintf(&b, " | %s", obj.Name)
}
b.WriteString(":\n")
for _, k := range peers {
v := obj.PeerMap[k]
if len(v.PeerName) > 0 {
fmt.Fprintf(&b, " > %-20s : %s", v.PeerName, strings.ToUpper(v.Status.String()))
} else {
fmt.Fprintf(&b, " > %-20s : %s", k, strings.ToUpper(v.Status.String()))
}
if v.Error != "" {
fmt.Fprintf(&b, ": %s", v.Error)
}
txt, _ := v.TS.MarshalText()
fmt.Fprintf(&b, " | %s\n", txt)
}
fmt.Print(b.String())
}
func textFormatPrintVersion(obj *api.Version) {
fmt.Println(obj.Version)
}
func textFormatPrintPin(obj *api.Pin) {
t := strings.ToUpper(obj.Type.String())
if obj.Mode == api.PinModeDirect {
t = t + "-DIRECT"
}
fmt.Printf("%s | %s | %s | ", obj.Cid, obj.Name, t)
if obj.IsPinEverywhere() {
fmt.Printf("Repl. Factor: -1 | Allocations: [everywhere]")
} else {
sortAlloc := api.PeersToStrings(obj.Allocations)
sort.Strings(sortAlloc)
fmt.Printf("Repl. Factor: %d--%d | Allocations: %s",
obj.ReplicationFactorMin, obj.ReplicationFactorMax,
sortAlloc)
}
var recStr string
switch obj.MaxDepth {
case 0:
recStr = "Direct"
case -1:
recStr = "Recursive"
default:
recStr = fmt.Sprintf("Recursive-%d", obj.MaxDepth)
}
fmt.Printf(" | %s", recStr)
fmt.Printf(" | Metadata:")
if len(obj.Metadata) == 0 {
fmt.Printf(" no")
} else {
fmt.Printf(" yes")
}
expireAt := "Exp: ∞"
if !obj.ExpireAt.IsZero() {
expireAt = humanize.Time(obj.ExpireAt)
}
fmt.Printf(" | %s\n", expireAt)
}
func textFormatPrintAddedOutput(obj *api.AddedOutput) {
fmt.Printf("added %s %s\n", obj.Cid, obj.Name)
}
func textFormatPrintAddedOutputQuiet(obj *addedOutputQuiet) {
if obj.quiet {
fmt.Printf("%s\n", obj.AddedOutput.Cid)
} else {
textFormatPrintAddedOutput(obj.AddedOutput)
}
}
func textFormatPrintMetric(obj *api.Metric) {
if obj.Name == "freespace" {
u, err := strconv.ParseUint(obj.Value, 10, 64)
checkErr("parsing to uint64", err)
fmt.Printf("%s | freespace: %s | Expires in: %s\n", peer.Encode(obj.Peer), humanize.Bytes(u), humanize.Time(time.Unix(0, obj.Expire)))
return
}
fmt.Printf("%s | %s | Expires in: %s\n", peer.Encode(obj.Peer), obj.Name, humanize.Time(time.Unix(0, obj.Expire)))
}
func textFormatPrintAlert(obj *api.Alert) {
fmt.Printf("%s: %s. Expired at: %s. Triggered at: %s\n",
obj.Peer,
obj.Name,
humanize.Time(time.Unix(0, obj.Expire)),
humanize.Time(obj.TriggeredAt),
)
}
func textFormatPrintGlobalRepoGC(obj *api.GlobalRepoGC) {
peers := make(sort.StringSlice, 0, len(obj.PeerMap))
for peer := range obj.PeerMap {
peers = append(peers, peer)
}
peers.Sort()
for _, peer := range peers {
item := obj.PeerMap[peer]
// If peer name is set, use it instead of peer ID.
if len(item.Peername) > 0 {
peer = item.Peername
}
if item.Error != "" {
fmt.Printf("%-15s | ERROR: %s\n", peer, item.Error)
} else {
fmt.Printf("%-15s\n", peer)
}
fmt.Printf(" > CIDs:\n")
for _, key := range item.Keys {
if key.Error != "" {
// key.Key will be empty
fmt.Printf(" - ERROR: %s\n", key.Error)
continue
}
fmt.Printf(" - %s\n", key.Key)
}
}
}
func textFormatPrintError(obj *api.Error) {
fmt.Printf("An error occurred:\n")
fmt.Printf(" Code: %d\n", obj.Code)
fmt.Printf(" Message: %s\n", obj.Message)
}
func trackerStatusAllString() string {
var strs []string
for _, st := range api.TrackerStatusAll() {
strs = append(strs, " - "+st.String())
}
sort.Strings(strs)
return strings.Join(strs, "\n")
}