2512ecb701
New PeerManager, Allocator, Informer components have been added along with a new "replication_factor" configuration option. First, cluster peers collect and push metrics (Informer) to the Cluster leader regularly. The Informer is an interface that can be implemented in custom wayts to support custom metrics. Second, on a pin operation, using the information from the collected metrics, an Allocator can provide a list of preferences as to where the new pin should be assigned. The Allocator is an interface allowing to provide different allocation strategies. Both Allocator and Informer are Cluster Componenets, and have access to the RPC API. The allocations are kept in the shared state. Cluster peer failure detection is still missing and re-allocation is still missing, although re-pinning something when a node is down/metrics missing does re-allocate the pin somewhere else. License: MIT Signed-off-by: Hector Sanjuan <hector@protocol.ai>
285 lines
6.7 KiB
Go
285 lines
6.7 KiB
Go
package ipfscluster
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
|
"github.com/ipfs/ipfs-cluster/test"
|
|
)
|
|
|
|
var (
|
|
apiHost = "http://127.0.0.1:10002" // should match testingConfig()
|
|
)
|
|
|
|
func testRESTAPI(t *testing.T) *RESTAPI {
|
|
//logging.SetDebugLogging()
|
|
cfg := testingConfig()
|
|
rest, err := NewRESTAPI(cfg)
|
|
if err != nil {
|
|
t.Fatal("should be able to create a new Api: ", err)
|
|
}
|
|
|
|
// No keep alive! Otherwise tests hang with
|
|
// connections re-used from previous tests
|
|
rest.server.SetKeepAlivesEnabled(false)
|
|
rest.SetClient(test.NewMockRPCClient(t))
|
|
return rest
|
|
}
|
|
|
|
func processResp(t *testing.T, httpResp *http.Response, err error, resp interface{}) {
|
|
if err != nil {
|
|
t.Fatal("error making get request: ", err)
|
|
}
|
|
body, err := ioutil.ReadAll(httpResp.Body)
|
|
defer httpResp.Body.Close()
|
|
if err != nil {
|
|
t.Fatal("error reading body: ", err)
|
|
}
|
|
|
|
if len(body) != 0 {
|
|
err = json.Unmarshal(body, resp)
|
|
if err != nil {
|
|
t.Error(string(body))
|
|
t.Fatal("error parsing json: ", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeGet(t *testing.T, path string, resp interface{}) {
|
|
httpResp, err := http.Get(apiHost + path)
|
|
processResp(t, httpResp, err, resp)
|
|
}
|
|
|
|
func makePost(t *testing.T, path string, body []byte, resp interface{}) {
|
|
httpResp, err := http.Post(apiHost+path, "application/json", bytes.NewReader(body))
|
|
processResp(t, httpResp, err, resp)
|
|
}
|
|
|
|
func makeDelete(t *testing.T, path string, resp interface{}) {
|
|
req, _ := http.NewRequest("DELETE", apiHost+path, bytes.NewReader([]byte{}))
|
|
c := &http.Client{}
|
|
httpResp, err := c.Do(req)
|
|
processResp(t, httpResp, err, resp)
|
|
}
|
|
|
|
func TestRESTAPIShutdown(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
err := rest.Shutdown()
|
|
if err != nil {
|
|
t.Error("should shutdown cleanly: ", err)
|
|
}
|
|
// test shutting down twice
|
|
rest.Shutdown()
|
|
}
|
|
|
|
func TestRestAPIIDEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
id := api.IDSerial{}
|
|
makeGet(t, "/id", &id)
|
|
if id.ID != test.TestPeerID1.Pretty() {
|
|
t.Error("expected correct id")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIVersionEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
ver := api.Version{}
|
|
makeGet(t, "/version", &ver)
|
|
if ver.Version != "0.0.mock" {
|
|
t.Error("expected correct version")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIPeerstEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var list []api.IDSerial
|
|
makeGet(t, "/peers", &list)
|
|
if len(list) != 1 {
|
|
t.Fatal("expected 1 element")
|
|
}
|
|
if list[0].ID != test.TestPeerID1.Pretty() {
|
|
t.Error("expected a different peer id list: ", list)
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIPeerAddEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
id := api.IDSerial{}
|
|
// post with valid body
|
|
body := fmt.Sprintf("{\"peer_multiaddress\":\"/ip4/1.2.3.4/tcp/1234/ipfs/%s\"}", test.TestPeerID1.Pretty())
|
|
t.Log(body)
|
|
makePost(t, "/peers", []byte(body), &id)
|
|
|
|
if id.ID != test.TestPeerID1.Pretty() {
|
|
t.Error("expected correct ID")
|
|
}
|
|
if id.Error != "" {
|
|
t.Error("did not expect an error")
|
|
}
|
|
|
|
// Send invalid body
|
|
errResp := errorResp{}
|
|
makePost(t, "/peers", []byte("oeoeoeoe"), &errResp)
|
|
if errResp.Code != 400 {
|
|
t.Error("expected error with bad body")
|
|
}
|
|
// Send invalid multiaddr
|
|
makePost(t, "/peers", []byte("{\"peer_multiaddress\": \"ab\"}"), &errResp)
|
|
if errResp.Code != 400 {
|
|
t.Error("expected error with bad multiaddress")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIPeerRemoveEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
makeDelete(t, "/peers/"+test.TestPeerID1.Pretty(), &struct{}{})
|
|
}
|
|
|
|
func TestRESTAPIPinEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
// test regular post
|
|
makePost(t, "/pins/"+test.TestCid1, []byte{}, &struct{}{})
|
|
|
|
errResp := errorResp{}
|
|
makePost(t, "/pins/"+test.ErrorCid, []byte{}, &errResp)
|
|
if errResp.Message != test.ErrBadCid.Error() {
|
|
t.Error("expected different error: ", errResp.Message)
|
|
}
|
|
|
|
makePost(t, "/pins/abcd", []byte{}, &errResp)
|
|
if errResp.Code != 400 {
|
|
t.Error("should fail with bad Cid")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIUnpinEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
// test regular delete
|
|
makeDelete(t, "/pins/"+test.TestCid1, &struct{}{})
|
|
|
|
errResp := errorResp{}
|
|
makeDelete(t, "/pins/"+test.ErrorCid, &errResp)
|
|
if errResp.Message != test.ErrBadCid.Error() {
|
|
t.Error("expected different error: ", errResp.Message)
|
|
}
|
|
|
|
makeDelete(t, "/pins/abcd", &errResp)
|
|
if errResp.Code != 400 {
|
|
t.Error("should fail with bad Cid")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIPinListEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp []api.CidArgSerial
|
|
makeGet(t, "/pinlist", &resp)
|
|
if len(resp) != 3 ||
|
|
resp[0].Cid != test.TestCid1 || resp[1].Cid != test.TestCid2 ||
|
|
resp[2].Cid != test.TestCid3 {
|
|
t.Error("unexpected pin list: ", resp)
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIStatusAllEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp []api.GlobalPinInfoSerial
|
|
makeGet(t, "/pins", &resp)
|
|
if len(resp) != 3 ||
|
|
resp[0].Cid != test.TestCid1 ||
|
|
resp[1].PeerMap[test.TestPeerID1.Pretty()].Status != "pinning" {
|
|
t.Errorf("unexpected statusResp:\n %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIStatusEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp api.GlobalPinInfoSerial
|
|
makeGet(t, "/pins/"+test.TestCid1, &resp)
|
|
|
|
if resp.Cid != test.TestCid1 {
|
|
t.Error("expected the same cid")
|
|
}
|
|
info, ok := resp.PeerMap[test.TestPeerID1.Pretty()]
|
|
if !ok {
|
|
t.Fatal("expected info for test.TestPeerID1")
|
|
}
|
|
if info.Status != "pinned" {
|
|
t.Error("expected different status")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPISyncAllEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp []api.GlobalPinInfoSerial
|
|
makePost(t, "/pins/sync", []byte{}, &resp)
|
|
|
|
if len(resp) != 3 ||
|
|
resp[0].Cid != test.TestCid1 ||
|
|
resp[1].PeerMap[test.TestPeerID1.Pretty()].Status != "pinning" {
|
|
t.Errorf("unexpected statusResp:\n %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestRESTAPISyncEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp api.GlobalPinInfoSerial
|
|
makePost(t, "/pins/"+test.TestCid1+"/sync", []byte{}, &resp)
|
|
|
|
if resp.Cid != test.TestCid1 {
|
|
t.Error("expected the same cid")
|
|
}
|
|
info, ok := resp.PeerMap[test.TestPeerID1.Pretty()]
|
|
if !ok {
|
|
t.Fatal("expected info for test.TestPeerID1")
|
|
}
|
|
if info.Status != "pinned" {
|
|
t.Error("expected different status")
|
|
}
|
|
}
|
|
|
|
func TestRESTAPIRecoverEndpoint(t *testing.T) {
|
|
rest := testRESTAPI(t)
|
|
defer rest.Shutdown()
|
|
|
|
var resp api.GlobalPinInfoSerial
|
|
makePost(t, "/pins/"+test.TestCid1+"/recover", []byte{}, &resp)
|
|
|
|
if resp.Cid != test.TestCid1 {
|
|
t.Error("expected the same cid")
|
|
}
|
|
info, ok := resp.PeerMap[test.TestPeerID1.Pretty()]
|
|
if !ok {
|
|
t.Fatal("expected info for test.TestPeerID1")
|
|
}
|
|
if info.Status != "pinned" {
|
|
t.Error("expected different status")
|
|
}
|
|
}
|