c4b18cd5f6
License: MIT Signed-off-by: Hector Sanjuan <hector@protocol.ai>
824 lines
24 KiB
Go
824 lines
24 KiB
Go
// Package api holds declarations for types used in ipfs-cluster APIs to make
|
|
// them re-usable across differen tools. This include RPC API "Serial[izable]"
|
|
// versions for types. The Go API uses natives types, while RPC API,
|
|
// REST APIs etc use serializable types (i.e. json format). Conversion methods
|
|
// exists between types.
|
|
//
|
|
// Note that all conversion methods ignore any parsing errors. All values must
|
|
// be validated first before initializing any of the types defined here.
|
|
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
pb "github.com/ipfs/ipfs-cluster/api/pb"
|
|
|
|
proto "github.com/gogo/protobuf/proto"
|
|
cid "github.com/ipfs/go-cid"
|
|
logging "github.com/ipfs/go-log"
|
|
peer "github.com/libp2p/go-libp2p-peer"
|
|
protocol "github.com/libp2p/go-libp2p-protocol"
|
|
multiaddr "github.com/multiformats/go-multiaddr"
|
|
|
|
// needed to parse /ws multiaddresses
|
|
_ "github.com/libp2p/go-ws-transport"
|
|
// needed to parse /dns* multiaddresses
|
|
_ "github.com/multiformats/go-multiaddr-dns"
|
|
)
|
|
|
|
var logger = logging.Logger("apitypes")
|
|
|
|
func init() {
|
|
// intialize trackerStatusString
|
|
stringTrackerStatus = make(map[string]TrackerStatus)
|
|
for k, v := range trackerStatusString {
|
|
stringTrackerStatus[v] = k
|
|
}
|
|
}
|
|
|
|
// TrackerStatus values
|
|
const (
|
|
// IPFSStatus should never take this value.
|
|
// When used as a filter. It means "all".
|
|
TrackerStatusUndefined TrackerStatus = 0
|
|
// The cluster node is offline or not responding
|
|
TrackerStatusClusterError TrackerStatus = 1 << iota
|
|
// An error occurred pinning
|
|
TrackerStatusPinError
|
|
// An error occurred unpinning
|
|
TrackerStatusUnpinError
|
|
// The IPFS daemon has pinned the item
|
|
TrackerStatusPinned
|
|
// The IPFS daemon is currently pinning the item
|
|
TrackerStatusPinning
|
|
// The IPFS daemon is currently unpinning the item
|
|
TrackerStatusUnpinning
|
|
// The IPFS daemon is not pinning the item
|
|
TrackerStatusUnpinned
|
|
// The IPFS daemon is not pinning the item but it is being tracked
|
|
TrackerStatusRemote
|
|
// The item has been queued for pinning on the IPFS daemon
|
|
TrackerStatusPinQueued
|
|
// The item has been queued for unpinning on the IPFS daemon
|
|
TrackerStatusUnpinQueued
|
|
// The IPFS daemon is not pinning the item through this cid but it is
|
|
// tracked in a cluster dag
|
|
TrackerStatusSharded
|
|
)
|
|
|
|
// Composite TrackerStatus.
|
|
const (
|
|
TrackerStatusError = TrackerStatusClusterError | TrackerStatusPinError | TrackerStatusUnpinError
|
|
TrackerStatusQueued = TrackerStatusPinQueued | TrackerStatusUnpinQueued
|
|
)
|
|
|
|
// TrackerStatus represents the status of a tracked Cid in the PinTracker
|
|
type TrackerStatus int
|
|
|
|
var trackerStatusString = map[TrackerStatus]string{
|
|
TrackerStatusUndefined: "undefined",
|
|
TrackerStatusClusterError: "cluster_error",
|
|
TrackerStatusPinError: "pin_error",
|
|
TrackerStatusUnpinError: "unpin_error",
|
|
TrackerStatusError: "error",
|
|
TrackerStatusPinned: "pinned",
|
|
TrackerStatusPinning: "pinning",
|
|
TrackerStatusUnpinning: "unpinning",
|
|
TrackerStatusUnpinned: "unpinned",
|
|
TrackerStatusRemote: "remote",
|
|
TrackerStatusPinQueued: "pin_queued",
|
|
TrackerStatusUnpinQueued: "unpin_queued",
|
|
TrackerStatusQueued: "queued",
|
|
}
|
|
|
|
// values autofilled in init()
|
|
var stringTrackerStatus map[string]TrackerStatus
|
|
|
|
// String converts a TrackerStatus into a readable string.
|
|
// If the given TrackerStatus is a filter (with several
|
|
// bits set), it will return a comma-separated list.
|
|
func (st TrackerStatus) String() string {
|
|
var values []string
|
|
|
|
// simple and known composite values
|
|
if v, ok := trackerStatusString[st]; ok {
|
|
return v
|
|
}
|
|
|
|
// other filters
|
|
for k, v := range trackerStatusString {
|
|
if st&k > 0 {
|
|
values = append(values, v)
|
|
}
|
|
}
|
|
|
|
return strings.Join(values, ",")
|
|
}
|
|
|
|
// Match returns true if the tracker status matches the given filter.
|
|
// For example TrackerStatusPinError will match TrackerStatusPinError
|
|
// and TrackerStatusError
|
|
func (st TrackerStatus) Match(filter TrackerStatus) bool {
|
|
return filter == 0 || st&filter > 0
|
|
}
|
|
|
|
// MarshalJSON uses the string representation of TrackerStatus for JSON
|
|
// encoding.
|
|
func (st TrackerStatus) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(st.String())
|
|
}
|
|
|
|
// UnmarshalJSON sets a tracker status from its JSON representation.
|
|
func (st *TrackerStatus) UnmarshalJSON(data []byte) error {
|
|
var v string
|
|
err := json.Unmarshal(data, &v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
*st = TrackerStatusFromString(v)
|
|
return nil
|
|
}
|
|
|
|
// TrackerStatusFromString parses a string and returns the matching
|
|
// TrackerStatus value. The string can be a comma-separated list
|
|
// representing a TrackerStatus filter. Unknown status names are
|
|
// ignored.
|
|
func TrackerStatusFromString(str string) TrackerStatus {
|
|
values := strings.Split(strings.Replace(str, " ", "", -1), ",")
|
|
var status TrackerStatus
|
|
for _, v := range values {
|
|
st, ok := stringTrackerStatus[v]
|
|
if ok {
|
|
status |= st
|
|
}
|
|
}
|
|
return status
|
|
}
|
|
|
|
// TrackerStatusAll all known TrackerStatus values.
|
|
func TrackerStatusAll() []TrackerStatus {
|
|
var list []TrackerStatus
|
|
for k := range trackerStatusString {
|
|
if k != TrackerStatusUndefined {
|
|
list = append(list, k)
|
|
}
|
|
}
|
|
|
|
return list
|
|
}
|
|
|
|
// IPFSPinStatus values
|
|
// FIXME include maxdepth
|
|
const (
|
|
IPFSPinStatusBug IPFSPinStatus = iota
|
|
IPFSPinStatusError
|
|
IPFSPinStatusDirect
|
|
IPFSPinStatusRecursive
|
|
IPFSPinStatusIndirect
|
|
IPFSPinStatusUnpinned
|
|
)
|
|
|
|
// IPFSPinStatus represents the status of a pin in IPFS (direct, recursive etc.)
|
|
type IPFSPinStatus int
|
|
|
|
// IPFSPinStatusFromString parses a string and returns the matching
|
|
// IPFSPinStatus.
|
|
func IPFSPinStatusFromString(t string) IPFSPinStatus {
|
|
// Since indirect statuses are of the form "indirect through <cid>",
|
|
// use a regexp to match
|
|
var ind, _ = regexp.MatchString("^indirect", t)
|
|
var rec, _ = regexp.MatchString("^recursive", t)
|
|
switch {
|
|
case ind:
|
|
return IPFSPinStatusIndirect
|
|
case rec:
|
|
// FIXME: Maxdepth?
|
|
return IPFSPinStatusRecursive
|
|
case t == "direct":
|
|
return IPFSPinStatusDirect
|
|
default:
|
|
return IPFSPinStatusBug
|
|
}
|
|
}
|
|
|
|
// IsPinned returns true if the item is pinned as expected by the
|
|
// maxDepth parameter.
|
|
func (ips IPFSPinStatus) IsPinned(maxDepth int) bool {
|
|
switch {
|
|
case maxDepth < 0:
|
|
return ips == IPFSPinStatusRecursive
|
|
case maxDepth == 0:
|
|
return ips == IPFSPinStatusDirect
|
|
case maxDepth > 0:
|
|
// FIXME: when we know how ipfs returns partial pins.
|
|
return ips == IPFSPinStatusRecursive
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ToTrackerStatus converts the IPFSPinStatus value to the
|
|
// appropriate TrackerStatus value.
|
|
func (ips IPFSPinStatus) ToTrackerStatus() TrackerStatus {
|
|
return ipfsPinStatus2TrackerStatusMap[ips]
|
|
}
|
|
|
|
var ipfsPinStatus2TrackerStatusMap = map[IPFSPinStatus]TrackerStatus{
|
|
IPFSPinStatusDirect: TrackerStatusPinned,
|
|
IPFSPinStatusRecursive: TrackerStatusPinned,
|
|
IPFSPinStatusIndirect: TrackerStatusUnpinned,
|
|
IPFSPinStatusUnpinned: TrackerStatusUnpinned,
|
|
IPFSPinStatusBug: TrackerStatusUndefined,
|
|
IPFSPinStatusError: TrackerStatusClusterError, //TODO(ajl): check suitability
|
|
}
|
|
|
|
// GlobalPinInfo contains cluster-wide status information about a tracked Cid,
|
|
// indexed by cluster peer.
|
|
type GlobalPinInfo struct {
|
|
Cid cid.Cid `json:"cid" codec:"c"`
|
|
// https://github.com/golang/go/issues/28827
|
|
// Peer IDs are of string Kind(). We can't use peer IDs here
|
|
// as Go ignores TextMarshaler.
|
|
PeerMap map[string]*PinInfo `json:"peer_map" codec:"pm,omitempty"`
|
|
}
|
|
|
|
// PinInfo holds information about local pins.
|
|
type PinInfo struct {
|
|
Cid cid.Cid `json:"cid" codec:"c"`
|
|
Peer peer.ID `json:"peer" codec:"p,omitempty"`
|
|
PeerName string `json:"peername" codec:"pn,omitempty"`
|
|
Status TrackerStatus `json:"status" codec:"st,omitempty"`
|
|
TS time.Time `json:"timestamp" codec:"ts,omitempty"`
|
|
Error string `json:"error" codec:"e,omitempty"`
|
|
}
|
|
|
|
// Version holds version information
|
|
type Version struct {
|
|
Version string `json:"Version" codec:"v"`
|
|
}
|
|
|
|
// ConnectGraph holds information about the connectivity of the cluster To
|
|
// read, traverse the keys of ClusterLinks. Each such id is one of the peers
|
|
// of the "ClusterID" peer running the query. ClusterLinks[id] in turn lists
|
|
// the ids that peer "id" sees itself connected to. It is possible that id is
|
|
// a peer of ClusterID, but ClusterID can not reach id over rpc, in which case
|
|
// ClusterLinks[id] == [], as id's view of its connectivity can not be
|
|
// retrieved.
|
|
//
|
|
// Iff there was an error reading the IPFSID of the peer then id will not be a
|
|
// key of ClustertoIPFS or IPFSLinks. Finally iff id is a key of ClustertoIPFS
|
|
// then id will be a key of IPFSLinks. In the event of a SwarmPeers error
|
|
// IPFSLinks[id] == [].
|
|
type ConnectGraph struct {
|
|
ClusterID peer.ID
|
|
// ipfs to ipfs links
|
|
IPFSLinks map[string][]peer.ID `json:"ipfs_links" codec:"il,omitempty"`
|
|
// cluster to cluster links
|
|
ClusterLinks map[string][]peer.ID `json:"cluster_links" codec:"cl,omitempty"`
|
|
// cluster to ipfs links
|
|
ClustertoIPFS map[string]peer.ID `json:"cluster_to_ipfs" codec:"ci,omitempty"`
|
|
}
|
|
|
|
// Multiaddr is a concrete type to wrap a Multiaddress so that it knows how to
|
|
// serialize and deserialize itself.
|
|
type Multiaddr struct {
|
|
multiaddr.Multiaddr
|
|
}
|
|
|
|
// NewMultiaddr returns a cluster Multiaddr wrapper creating the
|
|
// multiaddr.Multiaddr with the given string.
|
|
func NewMultiaddr(mstr string) (Multiaddr, error) {
|
|
m, err := multiaddr.NewMultiaddr(mstr)
|
|
return Multiaddr{Multiaddr: m}, err
|
|
}
|
|
|
|
// NewMultiaddrWithValue returns a new cluster Multiaddr wrapper using the
|
|
// given multiaddr.Multiaddr.
|
|
func NewMultiaddrWithValue(ma multiaddr.Multiaddr) Multiaddr {
|
|
return Multiaddr{Multiaddr: ma}
|
|
}
|
|
|
|
// MarshalJSON returns a JSON-formatted multiaddress.
|
|
func (maddr Multiaddr) MarshalJSON() ([]byte, error) {
|
|
return maddr.Multiaddr.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON parses a cluster Multiaddr from the JSON representation.
|
|
func (maddr *Multiaddr) UnmarshalJSON(data []byte) error {
|
|
maddr.Multiaddr, _ = multiaddr.NewMultiaddr("")
|
|
return maddr.Multiaddr.UnmarshalJSON(data)
|
|
}
|
|
|
|
// MarshalBinary returs the bytes of the wrapped multiaddress.
|
|
func (maddr Multiaddr) MarshalBinary() ([]byte, error) {
|
|
return maddr.Multiaddr.MarshalBinary()
|
|
}
|
|
|
|
// UnmarshalBinary casts some bytes as a multiaddress wraps it with
|
|
// the given cluster Multiaddr.
|
|
func (maddr *Multiaddr) UnmarshalBinary(data []byte) error {
|
|
datacopy := make([]byte, len(data)) // This is super important
|
|
copy(datacopy, data)
|
|
maddr.Multiaddr, _ = multiaddr.NewMultiaddr("")
|
|
return maddr.Multiaddr.UnmarshalBinary(datacopy)
|
|
}
|
|
|
|
// Value returns the wrapped multiaddr.Multiaddr.
|
|
func (maddr Multiaddr) Value() multiaddr.Multiaddr {
|
|
return maddr.Multiaddr
|
|
}
|
|
|
|
// ID holds information about the Cluster peer
|
|
type ID struct {
|
|
ID peer.ID `json:"id" codec:"i,omitempty"`
|
|
Addresses []Multiaddr `json:"addresses" codec:"a,omitempty"`
|
|
ClusterPeers []peer.ID `json:"cluster_peers" codec:"cp,omitempty"`
|
|
ClusterPeersAddresses []Multiaddr `json:"cluster_peers_addresses" codec:"cpa,omitempty"`
|
|
Version string `json:"version" codec:"v,omitempty"`
|
|
Commit string `json:"commit" codec:"c,omitempty"`
|
|
RPCProtocolVersion protocol.ID `json:"rpc_protocol_version" codec:"rv,omitempty"`
|
|
Error string `json:"error" codec:"e,omitempty"`
|
|
IPFS IPFSID `json:"ipfs" codec:"ip,omitempty"`
|
|
Peername string `json:"peername" codec:"pn,omitempty"`
|
|
//PublicKey crypto.PubKey
|
|
}
|
|
|
|
// IPFSID is used to store information about the underlying IPFS daemon
|
|
type IPFSID struct {
|
|
ID peer.ID `json:"id" codec:"i,omitempty"`
|
|
Addresses []Multiaddr `json:"addresses" codec:"a,omitempty"`
|
|
Error string `json:"error" codec:"e,omitempty"`
|
|
}
|
|
|
|
// PinType specifies which sort of Pin object we are dealing with.
|
|
// In practice, the PinType decides how a Pin object is treated by the
|
|
// PinTracker.
|
|
// See descriptions above.
|
|
// A sharded Pin would look like:
|
|
//
|
|
// [ Meta ] (not pinned on IPFS, only present in cluster state)
|
|
// |
|
|
// v
|
|
// [ Cluster DAG ] (pinned everywhere in "direct")
|
|
// | .. |
|
|
// v v
|
|
// [Shard1] .. [ShardN] (allocated to peers and pinned with max-depth=1
|
|
// | | .. | | | .. |
|
|
// v v .. v v v .. v
|
|
// [][]..[] [][]..[] Blocks (indirectly pinned on ipfs, not tracked in cluster)
|
|
//
|
|
//
|
|
type PinType uint64
|
|
|
|
// PinType values. See PinType documentation for further explanation.
|
|
const (
|
|
// BadType type showing up anywhere indicates a bug
|
|
BadType PinType = 1 << iota
|
|
// DataType is a regular, non-sharded pin. It is pinned recursively.
|
|
// It has no associated reference.
|
|
DataType
|
|
// MetaType tracks the original CID of a sharded DAG. Its Reference
|
|
// points to the Cluster DAG CID.
|
|
MetaType
|
|
// ClusterDAGType pins carry the CID of the root node that points to
|
|
// all the shard-root-nodes of the shards in which a DAG has been
|
|
// divided. Its Reference carries the MetaType CID.
|
|
// ClusterDAGType pins are pinned directly everywhere.
|
|
ClusterDAGType
|
|
// ShardType pins carry the root CID of a shard, which points
|
|
// to individual blocks on the original DAG that the user is adding,
|
|
// which has been sharded.
|
|
// They carry a Reference to the previous shard.
|
|
// ShardTypes are pinned with MaxDepth=1 (root and
|
|
// direct children only).
|
|
ShardType
|
|
)
|
|
|
|
// AllType is a PinType used for filtering all pin types
|
|
const AllType PinType = DataType | MetaType | ClusterDAGType | ShardType
|
|
|
|
// PinTypeFromString is the inverse of String. It returns the PinType value
|
|
// corresponding to the input string
|
|
func PinTypeFromString(str string) PinType {
|
|
switch str {
|
|
case "pin":
|
|
return DataType
|
|
case "meta-pin":
|
|
return MetaType
|
|
case "clusterdag-pin":
|
|
return ClusterDAGType
|
|
case "shard-pin":
|
|
return ShardType
|
|
case "all":
|
|
return AllType
|
|
default:
|
|
return BadType
|
|
}
|
|
}
|
|
|
|
// String returns a printable value to identify the PinType
|
|
func (pT PinType) String() string {
|
|
switch pT {
|
|
case DataType:
|
|
return "pin"
|
|
case MetaType:
|
|
return "meta-pin"
|
|
case ClusterDAGType:
|
|
return "clusterdag-pin"
|
|
case ShardType:
|
|
return "shard-pin"
|
|
case AllType:
|
|
return "all"
|
|
default:
|
|
return "bad-type"
|
|
}
|
|
}
|
|
|
|
var pinOptionsMetaPrefix = "meta-"
|
|
|
|
// PinOptions wraps user-defined options for Pins
|
|
type PinOptions struct {
|
|
ReplicationFactorMin int `json:"replication_factor_min" codec:"rn,omitempty"`
|
|
ReplicationFactorMax int `json:"replication_factor_max" codec:"rx,omitempty"`
|
|
Name string `json:"name" codec:"n,omitempty"`
|
|
ShardSize uint64 `json:"shard_size" codec:"s,omitempty"`
|
|
UserAllocations []string `json:"user_allocations" codec:"ua,omitempty"`
|
|
Metadata map[string]string `json:"metadata" codec:"m,omitempty"`
|
|
}
|
|
|
|
// Equals returns true if two PinOption objects are equivalent.
|
|
func (po *PinOptions) Equals(po2 *PinOptions) bool {
|
|
if po.ReplicationFactorMax != po2.ReplicationFactorMax {
|
|
return false
|
|
}
|
|
|
|
if po.ReplicationFactorMin != po2.ReplicationFactorMin {
|
|
return false
|
|
}
|
|
|
|
if po.ShardSize != po2.ShardSize {
|
|
return false
|
|
}
|
|
|
|
lenAllocs1 := len(po.UserAllocations)
|
|
lenAllocs2 := len(po2.UserAllocations)
|
|
if lenAllocs1 != lenAllocs2 {
|
|
return false
|
|
}
|
|
|
|
// avoid side effects in the original objects
|
|
allocs1 := make([]string, lenAllocs1, lenAllocs1)
|
|
allocs2 := make([]string, lenAllocs2, lenAllocs2)
|
|
copy(allocs1, po.UserAllocations)
|
|
copy(allocs2, po2.UserAllocations)
|
|
sort.Strings(allocs1)
|
|
sort.Strings(allocs2)
|
|
if strings.Join(allocs1, ",") != strings.Join(allocs2, ",") {
|
|
return false
|
|
}
|
|
|
|
for k, v := range po.Metadata {
|
|
v2 := po2.Metadata[k]
|
|
if k != "" && v != v2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ToQuery returns the PinOption as query arguments.
|
|
func (po *PinOptions) ToQuery() string {
|
|
q := url.Values{}
|
|
q.Set("replication-min", fmt.Sprintf("%d", po.ReplicationFactorMin))
|
|
q.Set("replication-max", fmt.Sprintf("%d", po.ReplicationFactorMax))
|
|
q.Set("name", po.Name)
|
|
q.Set("shard-size", fmt.Sprintf("%d", po.ShardSize))
|
|
q.Set("user-allocations", strings.Join(po.UserAllocations, ","))
|
|
for k, v := range po.Metadata {
|
|
if k == "" {
|
|
continue
|
|
}
|
|
q.Set(fmt.Sprintf("%s%s", pinOptionsMetaPrefix, k), v)
|
|
}
|
|
return q.Encode()
|
|
}
|
|
|
|
// FromQuery is the inverse of ToQuery().
|
|
func (po *PinOptions) FromQuery(q url.Values) {
|
|
po.Name = q.Get("name")
|
|
rplStr := q.Get("replication")
|
|
rplStrMin := q.Get("replication-min")
|
|
rplStrMax := q.Get("replication-max")
|
|
if rplStr != "" { // override
|
|
rplStrMin = rplStr
|
|
rplStrMax = rplStr
|
|
}
|
|
if rpl, err := strconv.Atoi(rplStrMin); err == nil {
|
|
po.ReplicationFactorMin = rpl
|
|
}
|
|
if rpl, err := strconv.Atoi(rplStrMax); err == nil {
|
|
po.ReplicationFactorMax = rpl
|
|
}
|
|
|
|
if shsize, err := strconv.ParseUint(q.Get("shard-size"), 10, 64); err == nil {
|
|
po.ShardSize = shsize
|
|
}
|
|
|
|
if allocs := q.Get("user-allocations"); allocs != "" {
|
|
po.UserAllocations = strings.Split(allocs, ",")
|
|
}
|
|
|
|
po.Metadata = make(map[string]string)
|
|
for k := range q {
|
|
if !strings.HasPrefix(k, pinOptionsMetaPrefix) {
|
|
continue
|
|
}
|
|
metaKey := strings.TrimPrefix(k, pinOptionsMetaPrefix)
|
|
if metaKey == "" {
|
|
continue
|
|
}
|
|
po.Metadata[metaKey] = q.Get(k)
|
|
}
|
|
}
|
|
|
|
// Pin carries all the information associated to a CID that is pinned
|
|
// in IPFS Cluster.
|
|
type Pin struct {
|
|
PinOptions
|
|
|
|
Cid cid.Cid `json:"cid" codec:"c"`
|
|
|
|
// See PinType comments
|
|
Type PinType `json:"type" codec:"t,omitempty"`
|
|
|
|
// The peers to which this pin is allocated
|
|
Allocations []peer.ID `json:"allocations" codec:"a,omitempty"`
|
|
|
|
// MaxDepth associated to this pin. -1 means
|
|
// recursive.
|
|
MaxDepth int `json:"max_depth" codec:"d,omitempty"`
|
|
|
|
// We carry a reference CID to this pin. For
|
|
// ClusterDAGs, it is the MetaPin CID. For the
|
|
// MetaPin it is the ClusterDAG CID. For Shards,
|
|
// it is the previous shard CID.
|
|
// When not needed the pointer is nil
|
|
Reference *cid.Cid `json:"reference" codec:"r,omitempty"`
|
|
}
|
|
|
|
// PinPath is a wrapper for holding pin options and path of the content.
|
|
type PinPath struct {
|
|
PinOptions
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
// PinCid is a shortcut to create a Pin only with a Cid. Default is for pin to
|
|
// be recursive and the pin to be of DataType.
|
|
func PinCid(c cid.Cid) *Pin {
|
|
return &Pin{
|
|
Cid: c,
|
|
Type: DataType,
|
|
Allocations: []peer.ID{},
|
|
MaxDepth: -1,
|
|
}
|
|
}
|
|
|
|
// PinWithOpts creates a new Pin calling PinCid(c) and then sets
|
|
// its PinOptions fields with the given options.
|
|
func PinWithOpts(c cid.Cid, opts PinOptions) *Pin {
|
|
p := PinCid(c)
|
|
p.PinOptions = opts
|
|
return p
|
|
}
|
|
|
|
func convertPinType(t PinType) pb.Pin_PinType {
|
|
var i pb.Pin_PinType
|
|
for t != 1 {
|
|
if t == 0 {
|
|
return pb.Pin_BadType
|
|
}
|
|
t = t >> 1
|
|
i++
|
|
}
|
|
return i
|
|
}
|
|
|
|
// ProtoMarshal marshals this Pin using probobuf.
|
|
func (pin *Pin) ProtoMarshal() ([]byte, error) {
|
|
allocs := make([][]byte, len(pin.Allocations), len(pin.Allocations))
|
|
for i, pid := range pin.Allocations {
|
|
bs, err := pid.Marshal()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allocs[i] = bs
|
|
}
|
|
|
|
opts := &pb.PinOptions{
|
|
ReplicationFactorMin: int32(pin.ReplicationFactorMin),
|
|
ReplicationFactorMax: int32(pin.ReplicationFactorMax),
|
|
Name: pin.Name,
|
|
ShardSize: pin.ShardSize,
|
|
// UserAllocations: pin.UserAllocations,
|
|
Metadata: pin.Metadata,
|
|
}
|
|
|
|
pbPin := &pb.Pin{
|
|
Cid: pin.Cid.Bytes(),
|
|
Type: convertPinType(pin.Type),
|
|
Allocations: allocs,
|
|
MaxDepth: int32(pin.MaxDepth),
|
|
Options: opts,
|
|
}
|
|
if ref := pin.Reference; ref != nil {
|
|
pbPin.Reference = ref.Bytes()
|
|
}
|
|
return proto.Marshal(pbPin)
|
|
}
|
|
|
|
// ProtoUnmarshal unmarshals this fields from protobuf-encoded bytes.
|
|
func (pin *Pin) ProtoUnmarshal(data []byte) error {
|
|
pbPin := pb.Pin{}
|
|
err := proto.Unmarshal(data, &pbPin)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ci, err := cid.Cast(pbPin.GetCid())
|
|
if err != nil {
|
|
pin.Cid = cid.Undef
|
|
} else {
|
|
pin.Cid = ci
|
|
}
|
|
|
|
pin.Type = 1 << uint64(pbPin.GetType())
|
|
|
|
pbAllocs := pbPin.GetAllocations()
|
|
lenAllocs := len(pbAllocs)
|
|
allocs := make([]peer.ID, lenAllocs, lenAllocs)
|
|
for i, pidb := range pbAllocs {
|
|
pid, err := peer.IDFromBytes(pidb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
allocs[i] = pid
|
|
}
|
|
|
|
pin.Allocations = allocs
|
|
pin.MaxDepth = int(pbPin.GetMaxDepth())
|
|
ref, err := cid.Cast(pbPin.GetReference())
|
|
if err != nil {
|
|
pin.Reference = nil
|
|
|
|
} else {
|
|
pin.Reference = &ref
|
|
}
|
|
|
|
opts := pbPin.GetOptions()
|
|
pin.ReplicationFactorMin = int(opts.GetReplicationFactorMin())
|
|
pin.ReplicationFactorMax = int(opts.GetReplicationFactorMax())
|
|
pin.Name = opts.GetName()
|
|
pin.ShardSize = opts.GetShardSize()
|
|
// pin.UserAllocations = opts.GetUserAllocations()
|
|
pin.Metadata = opts.GetMetadata()
|
|
return nil
|
|
}
|
|
|
|
// Equals checks if two pins are the same (with the same allocations).
|
|
// If allocations are the same but in different order, they are still
|
|
// considered equivalent.
|
|
func (pin *Pin) Equals(pin2 *Pin) bool {
|
|
if pin == nil && pin2 != nil || pin2 == nil && pin != nil {
|
|
return false
|
|
}
|
|
|
|
if !pin.Cid.Equals(pin2.Cid) {
|
|
return false
|
|
}
|
|
|
|
if pin.Name != pin2.Name {
|
|
return false
|
|
}
|
|
|
|
if pin.Type != pin2.Type {
|
|
return false
|
|
}
|
|
|
|
if pin.MaxDepth != pin2.MaxDepth {
|
|
return false
|
|
}
|
|
|
|
if pin.Reference != nil && pin2.Reference == nil ||
|
|
pin.Reference == nil && pin2.Reference != nil {
|
|
return false
|
|
}
|
|
|
|
if pin.Reference != nil && pin2.Reference != nil &&
|
|
!pin.Reference.Equals(*pin2.Reference) {
|
|
return false
|
|
}
|
|
|
|
allocs1 := PeersToStrings(pin.Allocations)
|
|
sort.Strings(allocs1)
|
|
allocs2 := PeersToStrings(pin2.Allocations)
|
|
sort.Strings(allocs2)
|
|
|
|
if strings.Join(allocs1, ",") != strings.Join(allocs2, ",") {
|
|
return false
|
|
}
|
|
|
|
return pin.PinOptions.Equals(&pin2.PinOptions)
|
|
}
|
|
|
|
// IsRemotePin determines whether a Pin's ReplicationFactor has
|
|
// been met, so as to either pin or unpin it from the peer.
|
|
func (pin *Pin) IsRemotePin(pid peer.ID) bool {
|
|
if pin.ReplicationFactorMax < 0 || pin.ReplicationFactorMin < 0 {
|
|
return false
|
|
}
|
|
|
|
for _, p := range pin.Allocations {
|
|
if p == pid {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NodeWithMeta specifies a block of data and a set of optional metadata fields
|
|
// carrying information about the encoded ipld node
|
|
type NodeWithMeta struct {
|
|
Data []byte `codec:"d,omitempty"`
|
|
Cid cid.Cid `codec:"c, omitempty"`
|
|
CumSize uint64 `codec:"s,omitempty"` // Cumulative size
|
|
Format string `codec:"f,omitempty"`
|
|
}
|
|
|
|
// Size returns how big is the block. It is different from CumSize, which
|
|
// records the size of the underlying tree.
|
|
func (n *NodeWithMeta) Size() uint64 {
|
|
return uint64(len(n.Data))
|
|
}
|
|
|
|
// Metric transports information about a peer.ID. It is used to decide
|
|
// pin allocations by a PinAllocator. IPFS cluster is agnostic to
|
|
// the Value, which should be interpreted by the PinAllocator.
|
|
type Metric struct {
|
|
Name string `json:"name" codec:"n,omitempty"`
|
|
Peer peer.ID `json:"peer" codec:"p,omitempty"`
|
|
Value string `json:"value" codec:"v,omitempty"`
|
|
Expire int64 `json:"expire" codec:"e,omitempty"`
|
|
Valid bool `json:"valid" codec:"d,omitempty"`
|
|
}
|
|
|
|
// SetTTL sets Metric to expire after the given time.Duration
|
|
func (m *Metric) SetTTL(d time.Duration) {
|
|
exp := time.Now().Add(d)
|
|
m.Expire = exp.UnixNano()
|
|
}
|
|
|
|
// GetTTL returns the time left before the Metric expires
|
|
func (m *Metric) GetTTL() time.Duration {
|
|
expDate := time.Unix(0, m.Expire)
|
|
return expDate.Sub(time.Now())
|
|
}
|
|
|
|
// Expired returns if the Metric has expired
|
|
func (m *Metric) Expired() bool {
|
|
expDate := time.Unix(0, m.Expire)
|
|
return time.Now().After(expDate)
|
|
}
|
|
|
|
// Discard returns if the metric not valid or has expired
|
|
func (m *Metric) Discard() bool {
|
|
return !m.Valid || m.Expired()
|
|
}
|
|
|
|
// Alert carries alerting information about a peer. WIP.
|
|
type Alert struct {
|
|
Peer peer.ID
|
|
MetricName string
|
|
}
|
|
|
|
// Error can be used by APIs to return errors.
|
|
type Error struct {
|
|
Code int `json:"code" codec:"o,omitempty"`
|
|
Message string `json:"message" codec:"m,omitempty"`
|
|
}
|
|
|
|
// Error implements the error interface and returns the error's message.
|
|
func (e *Error) Error() string {
|
|
return fmt.Sprintf("%s (%d)", e.Message, e.Code)
|
|
}
|
|
|
|
// IPFSRepoStat wraps information about the IPFS repository.
|
|
type IPFSRepoStat struct {
|
|
RepoSize uint64 `codec:"r,omitempty"`
|
|
StorageMax uint64 `codec:"s, omitempty"`
|
|
}
|