2017-02-09 15:29:17 +00:00
|
|
|
package test
|
2016-12-16 16:22:37 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2016-12-16 18:14:45 +00:00
|
|
|
"net/url"
|
|
|
|
"strconv"
|
2016-12-16 16:22:37 +00:00
|
|
|
"strings"
|
|
|
|
|
2017-02-13 15:46:53 +00:00
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
2017-02-09 15:29:17 +00:00
|
|
|
"github.com/ipfs/ipfs-cluster/state/mapstate"
|
|
|
|
|
2016-12-16 16:22:37 +00:00
|
|
|
cid "github.com/ipfs/go-cid"
|
|
|
|
)
|
|
|
|
|
2017-02-09 15:29:17 +00:00
|
|
|
// IpfsMock is an ipfs daemon mock which should sustain the functionality used by ipfscluster.
|
|
|
|
type IpfsMock struct {
|
2016-12-16 16:22:37 +00:00
|
|
|
server *httptest.Server
|
2017-02-09 15:29:17 +00:00
|
|
|
Addr string
|
|
|
|
Port int
|
|
|
|
pinMap *mapstate.MapState
|
2016-12-16 16:22:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type mockPinResp struct {
|
|
|
|
Pins []string
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockPinType struct {
|
|
|
|
Type string
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockPinLsResp struct {
|
|
|
|
Keys map[string]mockPinType
|
|
|
|
}
|
|
|
|
|
|
|
|
type ipfsErr struct {
|
|
|
|
Code int
|
|
|
|
Message string
|
|
|
|
}
|
|
|
|
|
2017-04-06 02:27:02 +00:00
|
|
|
type mockIDResp struct {
|
2017-02-09 15:29:17 +00:00
|
|
|
ID string
|
|
|
|
Addresses []string
|
|
|
|
}
|
|
|
|
|
2017-03-29 20:52:13 +00:00
|
|
|
type mockRepoStatResp struct {
|
2017-10-26 13:06:00 +00:00
|
|
|
RepoSize uint64
|
|
|
|
NumObjects uint64
|
|
|
|
StorageMax uint64
|
2017-03-27 13:07:12 +00:00
|
|
|
}
|
|
|
|
|
2017-03-29 20:52:13 +00:00
|
|
|
type mockConfigResp struct {
|
2017-03-27 13:07:12 +00:00
|
|
|
Datastore struct {
|
|
|
|
StorageMax string
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-29 20:52:13 +00:00
|
|
|
type mockAddResp struct {
|
2017-11-13 12:52:33 +00:00
|
|
|
Name string
|
|
|
|
Hash string
|
|
|
|
Bytes uint64
|
2017-03-29 20:52:13 +00:00
|
|
|
}
|
|
|
|
|
2018-03-07 13:51:37 +00:00
|
|
|
type mockRefsResp struct {
|
|
|
|
Ref string
|
|
|
|
Err string
|
|
|
|
}
|
|
|
|
|
2018-01-18 02:49:35 +00:00
|
|
|
type mockSwarmPeersResp struct {
|
|
|
|
Peers []mockIpfsPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockIpfsPeer struct {
|
|
|
|
Peer string
|
|
|
|
}
|
|
|
|
|
2017-02-09 15:29:17 +00:00
|
|
|
// NewIpfsMock returns a new mock.
|
|
|
|
func NewIpfsMock() *IpfsMock {
|
|
|
|
st := mapstate.NewMapState()
|
|
|
|
m := &IpfsMock{
|
2016-12-16 16:22:37 +00:00
|
|
|
pinMap: st,
|
|
|
|
}
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(m.handler))
|
|
|
|
m.server = ts
|
2016-12-16 18:14:45 +00:00
|
|
|
|
2016-12-16 21:00:08 +00:00
|
|
|
url, _ := url.Parse(ts.URL)
|
2016-12-16 18:14:45 +00:00
|
|
|
h := strings.Split(url.Host, ":")
|
|
|
|
i, _ := strconv.Atoi(h[1])
|
|
|
|
|
2017-02-09 15:29:17 +00:00
|
|
|
m.Port = i
|
|
|
|
m.Addr = h[0]
|
2016-12-16 16:22:37 +00:00
|
|
|
return m
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: what if IPFS API changes?
|
2017-02-09 15:29:17 +00:00
|
|
|
func (m *IpfsMock) handler(w http.ResponseWriter, r *http.Request) {
|
2016-12-16 16:22:37 +00:00
|
|
|
p := r.URL.Path
|
|
|
|
endp := strings.TrimPrefix(p, "/api/v0/")
|
|
|
|
switch endp {
|
2017-01-26 18:59:31 +00:00
|
|
|
case "id":
|
2017-04-06 02:27:02 +00:00
|
|
|
resp := mockIDResp{
|
2017-02-09 15:29:17 +00:00
|
|
|
ID: TestPeerID1.Pretty(),
|
2017-01-26 18:59:31 +00:00
|
|
|
Addresses: []string{
|
|
|
|
"/ip4/0.0.0.0/tcp/1234",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2017-03-29 20:52:13 +00:00
|
|
|
case "add":
|
|
|
|
c, _ := cid.Decode(TestCid3)
|
|
|
|
// add also pins
|
|
|
|
m.pinMap.Add(api.PinCid(c))
|
|
|
|
_, fheader, err := r.FormFile("file")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "no file in /add", 500)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-11-13 12:52:33 +00:00
|
|
|
query := r.URL.Query()
|
|
|
|
progress, ok := query["progress"]
|
|
|
|
if ok && len(progress) > 0 && progress[0] != "false" {
|
|
|
|
progressResp := mockAddResp{
|
|
|
|
Name: fheader.Filename,
|
|
|
|
Bytes: 4,
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(progressResp)
|
|
|
|
w.Write(j)
|
|
|
|
}
|
|
|
|
|
2017-03-29 20:52:13 +00:00
|
|
|
resp := mockAddResp{
|
|
|
|
Name: fheader.Filename,
|
|
|
|
Hash: TestCid3,
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2016-12-16 16:22:37 +00:00
|
|
|
case "pin/add":
|
2018-04-20 04:09:07 +00:00
|
|
|
arg, ok := extractCid(r.URL)
|
|
|
|
if !ok {
|
2016-12-16 16:22:37 +00:00
|
|
|
goto ERROR
|
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
if arg == ErrorCid {
|
2016-12-16 16:22:37 +00:00
|
|
|
goto ERROR
|
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
c, err := cid.Decode(arg)
|
2016-12-16 16:22:37 +00:00
|
|
|
if err != nil {
|
|
|
|
goto ERROR
|
|
|
|
}
|
2017-03-08 15:57:27 +00:00
|
|
|
m.pinMap.Add(api.PinCid(c))
|
2016-12-16 16:22:37 +00:00
|
|
|
resp := mockPinResp{
|
2018-04-20 04:09:07 +00:00
|
|
|
Pins: []string{arg},
|
2016-12-16 16:22:37 +00:00
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
|
|
|
case "pin/rm":
|
2018-04-20 04:09:07 +00:00
|
|
|
arg, ok := extractCid(r.URL)
|
|
|
|
if !ok {
|
2016-12-16 16:22:37 +00:00
|
|
|
goto ERROR
|
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
c, err := cid.Decode(arg)
|
2016-12-16 16:22:37 +00:00
|
|
|
if err != nil {
|
|
|
|
goto ERROR
|
|
|
|
}
|
2017-02-13 15:46:53 +00:00
|
|
|
m.pinMap.Rm(c)
|
2016-12-16 16:22:37 +00:00
|
|
|
resp := mockPinResp{
|
2018-04-20 04:09:07 +00:00
|
|
|
Pins: []string{arg},
|
2016-12-16 16:22:37 +00:00
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
|
|
|
case "pin/ls":
|
2018-04-20 04:09:07 +00:00
|
|
|
arg, ok := extractCid(r.URL)
|
2016-12-16 16:22:37 +00:00
|
|
|
if !ok {
|
|
|
|
rMap := make(map[string]mockPinType)
|
2017-02-13 15:46:53 +00:00
|
|
|
pins := m.pinMap.List()
|
2016-12-16 16:22:37 +00:00
|
|
|
for _, p := range pins {
|
2017-02-13 15:46:53 +00:00
|
|
|
rMap[p.Cid.String()] = mockPinType{"recursive"}
|
2016-12-16 16:22:37 +00:00
|
|
|
}
|
|
|
|
j, _ := json.Marshal(mockPinLsResp{rMap})
|
|
|
|
w.Write(j)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2018-04-20 04:09:07 +00:00
|
|
|
cidStr := arg
|
2016-12-16 16:22:37 +00:00
|
|
|
c, err := cid.Decode(cidStr)
|
|
|
|
if err != nil {
|
|
|
|
goto ERROR
|
|
|
|
}
|
2017-02-13 15:46:53 +00:00
|
|
|
ok = m.pinMap.Has(c)
|
2016-12-16 16:22:37 +00:00
|
|
|
if ok {
|
|
|
|
rMap := make(map[string]mockPinType)
|
|
|
|
rMap[cidStr] = mockPinType{"recursive"}
|
|
|
|
j, _ := json.Marshal(mockPinLsResp{rMap})
|
|
|
|
w.Write(j)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
resp := ipfsErr{0, fmt.Sprintf("Path '%s' is not pinned", cidStr)}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
|
|
|
}
|
2017-03-23 18:34:33 +00:00
|
|
|
case "swarm/connect":
|
2018-04-20 04:09:07 +00:00
|
|
|
arg, ok := extractCid(r.URL)
|
2017-03-23 18:34:33 +00:00
|
|
|
if !ok {
|
|
|
|
goto ERROR
|
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
addr := arg
|
2017-03-23 18:34:33 +00:00
|
|
|
splits := strings.Split(addr, "/")
|
|
|
|
pid := splits[len(splits)-1]
|
|
|
|
resp := struct {
|
|
|
|
Strings []string
|
|
|
|
}{
|
|
|
|
Strings: []string{fmt.Sprintf("connect %s success", pid)},
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2018-01-18 02:49:35 +00:00
|
|
|
case "swarm/peers":
|
|
|
|
peer1 := mockIpfsPeer{
|
|
|
|
Peer: TestPeerID4.Pretty(),
|
|
|
|
}
|
|
|
|
peer2 := mockIpfsPeer{
|
|
|
|
Peer: TestPeerID5.Pretty(),
|
|
|
|
}
|
|
|
|
resp := mockSwarmPeersResp{
|
|
|
|
Peers: []mockIpfsPeer{peer1, peer2},
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2017-03-27 13:07:12 +00:00
|
|
|
case "repo/stat":
|
|
|
|
len := len(m.pinMap.List())
|
2017-03-29 20:52:13 +00:00
|
|
|
resp := mockRepoStatResp{
|
2017-10-26 13:06:00 +00:00
|
|
|
RepoSize: uint64(len) * 1000,
|
|
|
|
NumObjects: uint64(len),
|
|
|
|
StorageMax: 10000000000, //10 GB
|
2017-03-27 13:07:12 +00:00
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
|
|
|
case "config/show":
|
2017-03-29 20:52:13 +00:00
|
|
|
resp := mockConfigResp{
|
2017-03-27 13:07:12 +00:00
|
|
|
Datastore: struct {
|
|
|
|
StorageMax string
|
|
|
|
}{
|
|
|
|
StorageMax: "10G",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2018-03-07 13:51:37 +00:00
|
|
|
case "refs":
|
2018-04-20 04:09:07 +00:00
|
|
|
arg, ok := extractCid(r.URL)
|
2018-03-07 13:51:37 +00:00
|
|
|
if !ok {
|
|
|
|
goto ERROR
|
|
|
|
}
|
|
|
|
resp := mockRefsResp{
|
2018-04-20 04:09:07 +00:00
|
|
|
Ref: arg,
|
2018-03-07 13:51:37 +00:00
|
|
|
}
|
|
|
|
j, _ := json.Marshal(resp)
|
|
|
|
w.Write(j)
|
2017-01-26 21:49:53 +00:00
|
|
|
case "version":
|
|
|
|
w.Write([]byte("{\"Version\":\"m.o.c.k\"}"))
|
2016-12-16 16:22:37 +00:00
|
|
|
default:
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
ERROR:
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
|
2017-02-15 14:16:16 +00:00
|
|
|
// Close closes the mock server. It's important to call after each test or
|
|
|
|
// the listeners are left hanging around.
|
2017-02-09 15:29:17 +00:00
|
|
|
func (m *IpfsMock) Close() {
|
2016-12-16 16:22:37 +00:00
|
|
|
m.server.Close()
|
|
|
|
}
|
2018-04-20 04:09:07 +00:00
|
|
|
|
|
|
|
// extractCid extracts the cid argument from a url.URL, either via
|
|
|
|
// the query string parameters or from the url path itself.
|
|
|
|
func extractCid(u *url.URL) (string, bool) {
|
|
|
|
arg := u.Query().Get("arg")
|
|
|
|
if arg != "" {
|
|
|
|
return arg, true
|
|
|
|
}
|
|
|
|
|
|
|
|
p := strings.TrimPrefix(u.Path, "/api/v0/")
|
|
|
|
segs := strings.Split(p, "/")
|
|
|
|
|
|
|
|
if len(segs) > 2 {
|
|
|
|
return segs[len(segs)-1], true
|
|
|
|
}
|
|
|
|
return "", false
|
|
|
|
}
|