diff --git a/cluster_test.go b/cluster_test.go index ecd1003c..e89a8f95 100644 --- a/cluster_test.go +++ b/cluster_test.go @@ -79,6 +79,7 @@ func (ipfs *mockConnector) PinLs(filter string) (map[string]api.IPFSPinStatus, e func (ipfs *mockConnector) ConnectSwarms() error { return nil } func (ipfs *mockConnector) ConfigKey(keypath string) (interface{}, error) { return nil, nil } +func (ipfs *mockConnector) FreeSpace() (int, error) { return 0, nil } func (ipfs *mockConnector) RepoSize() (int, error) { return 0, nil } func testingCluster(t *testing.T) (*Cluster, *mockAPI, *mockConnector, *mapstate.MapState, *maptracker.MapPinTracker) { diff --git a/informer/disk/disk.go b/informer/disk/disk.go index 70bdaa76..f0d1d7f1 100644 --- a/informer/disk/disk.go +++ b/informer/disk/disk.go @@ -12,13 +12,21 @@ import ( "github.com/ipfs/ipfs-cluster/api" ) +// TODO: switch default to disk-freespace +const DefaultMetric = "disk-reposize" + var logger = logging.Logger("diskinfo") // MetricTTL specifies how long our reported metric is valid in seconds. var MetricTTL = 30 // MetricName specifies the name of our metric -var MetricName = "disk" +var MetricName string + +var nameToRPC = map[string]string{ + "disk-freespace": "IPFSFreeSpace", + "disk-reposize": "IPFSRepoSize", +} // Informer is a simple object to implement the ipfscluster.Informer // and Component interfaces. @@ -28,6 +36,14 @@ type Informer struct { // NewInformer returns an initialized Informer. func NewInformer() *Informer { + MetricName = DefaultMetric + return &Informer{} +} + +// NewInformer returns an initialized Informer. +func NewInformerWithMetric(metric string) *Informer { + // assume `metric` has been checked already + MetricName = metric return &Informer{} } @@ -49,8 +65,6 @@ func (disk *Informer) Name() string { return MetricName } -// GetMetric uses the IPFSConnector the current -// repository size and returns it in a metric. func (disk *Informer) GetMetric() api.Metric { if disk.rpcClient == nil { return api.Metric{ @@ -58,13 +72,13 @@ func (disk *Informer) GetMetric() api.Metric { } } - var repoSize int + var metric int valid := true err := disk.rpcClient.Call("", "Cluster", - "IPFSRepoSize", + nameToRPC[MetricName], struct{}{}, - &repoSize) + &metric) if err != nil { logger.Error(err) valid = false @@ -72,7 +86,7 @@ func (disk *Informer) GetMetric() api.Metric { m := api.Metric{ Name: MetricName, - Value: fmt.Sprintf("%d", repoSize), + Value: fmt.Sprintf("%d", metric), Valid: valid, } diff --git a/informer/disk/disk_test.go b/informer/disk/disk_test.go index 352469ac..ad64f190 100644 --- a/informer/disk/disk_test.go +++ b/informer/disk/disk_test.go @@ -52,7 +52,7 @@ func (mock *badRPCService) IPFSRepoSize(in struct{}, out *int) error { func Test(t *testing.T) { inf := NewInformer() defer inf.Shutdown() - if inf.Name() != "disk" { + if inf.Name() != DefaultMetric { t.Error("careful when changing the name of an informer") } m := inf.GetMetric() diff --git a/ipfs-cluster-service/main.go b/ipfs-cluster-service/main.go index 7c0f52c6..a84cd60b 100644 --- a/ipfs-cluster-service/main.go +++ b/ipfs-cluster-service/main.go @@ -342,8 +342,12 @@ func setupLogging(lvl string) { func setupAllocation(strategy string) (ipfscluster.Informer, ipfscluster.PinAllocator) { switch strategy { - case "disk", "reposize": - return disk.NewInformer(), ascendalloc.NewAllocator() + case "disk": + informer := disk.NewInformer() + return informer, ascendalloc.NewAllocator() + case "disk-freespace", "disk-reposize": + informer := disk.NewInformerWithMetric(strategy) + return informer, ascendalloc.NewAllocator() case "numpin", "pincount": return numpin.NewInformer(), ascendalloc.NewAllocator() default: diff --git a/ipfscluster.go b/ipfscluster.go index b1ac8ed8..c92c5b7c 100644 --- a/ipfscluster.go +++ b/ipfscluster.go @@ -77,6 +77,9 @@ type IPFSConnector interface { // ConfigKey returns the value for a configuration key. // Subobjects are reached with keypaths as "Parent/Child/GrandChild...". ConfigKey(keypath string) (interface{}, error) + // FreeSpace returns the amount of remaining space on the repo, calculated from + //"repo stat" + FreeSpace() (int, error) // RepoSize returns the current repository size as expressed // by "repo stat". RepoSize() (int, error) diff --git a/ipfsconn/ipfshttp/ipfshttp.go b/ipfsconn/ipfshttp/ipfshttp.go index 36e0c079..4148ccae 100644 --- a/ipfsconn/ipfshttp/ipfshttp.go +++ b/ipfsconn/ipfshttp/ipfshttp.go @@ -101,6 +101,7 @@ type ipfsIDResp struct { type ipfsRepoStatResp struct { RepoSize int + StorageMax int NumObjects int } @@ -811,6 +812,25 @@ func getConfigValue(path []string, cfg map[string]interface{}) (interface{}, err } } +// FreeSpace returns the amount of unused space in the ipfs repository. This +// value is derived from the RepoSize and StorageMax values given by "repo +// stats". The value is in bytes. +func (ipfs *Connector) FreeSpace() (int, error) { + res, err := ipfs.get("repo/stat") + if err != nil { + logger.Error(err) + return 0, err + } + + var stats ipfsRepoStatResp + err = json.Unmarshal(res, &stats) + if err != nil { + logger.Error(err) + return 0, err + } + return stats.StorageMax - stats.RepoSize, nil +} + // RepoSize returns the current repository size of the ipfs daemon as // provided by "repo stats". The value is in bytes. func (ipfs *Connector) RepoSize() (int, error) { diff --git a/rpc_api.go b/rpc_api.go index 13ff3117..f5ab4512 100644 --- a/rpc_api.go +++ b/rpc_api.go @@ -242,6 +242,13 @@ func (rpcapi *RPCAPI) IPFSConfigKey(in string, out *interface{}) error { return err } +// IPFSFreeSpace runs IPFSConnector.FreeSpace(). +func (rpcapi *RPCAPI) IPFSFreeSpace(in struct{}, out *int) error { + res, err := rpcapi.c.ipfs.FreeSpace() + *out = res + return err +} + // IPFSRepoSize runs IPFSConnector.RepoSize(). func (rpcapi *RPCAPI) IPFSRepoSize(in struct{}, out *int) error { res, err := rpcapi.c.ipfs.RepoSize()