Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit b8171b1

Browse files
authored
feat: ipfs.ping cli, http-api and core (#1342)
1 parent 2e40fb8 commit b8171b1

File tree

18 files changed

+726
-12
lines changed

18 files changed

+726
-12
lines changed

circle.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
# Warning: This file is automatically synced from https://github.com/ipfs/ci-sync so if you want to change it, please change it there and ask someone to sync all repositories.
22
machine:
33
node:
4-
version: 8
4+
version: 8.11.1
55

66
test:
7-
pre:
8-
- npm run lint
97
post:
10-
- make test
118
- npm run coverage -- --upload --providers coveralls
129

1310
dependencies:

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@
7272
"execa": "~0.10.0",
7373
"expose-loader": "~0.7.5",
7474
"form-data": "^2.3.2",
75-
"hat": "~0.0.3",
76-
"interface-ipfs-core": "~0.65.7",
77-
"ipfsd-ctl": "~0.34.0",
75+
"hat": "0.0.3",
76+
"interface-ipfs-core": "~0.66.2",
77+
"ipfsd-ctl": "~0.37.0",
7878
"lodash": "^4.17.10",
7979
"mocha": "^5.1.1",
8080
"ncp": "^2.0.0",
@@ -105,7 +105,7 @@
105105
"hapi-set-header": "^1.0.2",
106106
"hoek": "^5.0.3",
107107
"human-to-milliseconds": "^1.0.0",
108-
"ipfs-api": "^21.0.0",
108+
"ipfs-api": "^22.0.0",
109109
"ipfs-bitswap": "~0.20.0",
110110
"ipfs-block": "~0.7.1",
111111
"ipfs-block-service": "~0.14.0",
@@ -148,7 +148,7 @@
148148
"multihashes": "~0.4.13",
149149
"once": "^1.4.0",
150150
"path-exists": "^3.0.0",
151-
"peer-book": "~0.7.0",
151+
"peer-book": "~0.8.0",
152152
"peer-id": "~0.10.7",
153153
"peer-info": "~0.14.1",
154154
"progress": "^2.0.0",

src/cli/commands/ping.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict'
2+
3+
const pull = require('pull-stream')
4+
const print = require('../utils').print
5+
6+
module.exports = {
7+
command: 'ping <peerId>',
8+
9+
description: 'Measure the latency of a connection',
10+
11+
builder: {
12+
count: {
13+
alias: 'n',
14+
type: 'integer',
15+
default: 10
16+
}
17+
},
18+
19+
handler (argv) {
20+
const peerId = argv.peerId
21+
const count = argv.count || 10
22+
pull(
23+
argv.ipfs.pingPullStream(peerId, { count }),
24+
pull.drain(({ success, time, text }) => {
25+
// Check if it's a pong
26+
if (success && !text) {
27+
print(`Pong received: time=${time} ms`)
28+
// Status response
29+
} else {
30+
print(text)
31+
}
32+
})
33+
)
34+
}
35+
}

src/core/components/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ exports.dag = require('./dag')
1616
exports.libp2p = require('./libp2p')
1717
exports.swarm = require('./swarm')
1818
exports.ping = require('./ping')
19+
exports.pingPullStream = require('./ping-pull-stream')
20+
exports.pingReadableStream = require('./ping-readable-stream')
1921
exports.files = require('./files')
2022
exports.bitswap = require('./bitswap')
2123
exports.pubsub = require('./pubsub')
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
'use strict'
2+
3+
const debug = require('debug')
4+
const OFFLINE_ERROR = require('../utils').OFFLINE_ERROR
5+
const PeerId = require('peer-id')
6+
const pull = require('pull-stream')
7+
const Pushable = require('pull-pushable')
8+
const waterfall = require('async/waterfall')
9+
10+
const log = debug('jsipfs:pingPullStream')
11+
log.error = debug('jsipfs:pingPullStream:error')
12+
13+
module.exports = function pingPullStream (self) {
14+
return (peerId, opts) => {
15+
if (!self.isOnline()) {
16+
return pull.error(new Error(OFFLINE_ERROR))
17+
}
18+
19+
opts = Object.assign({ count: 10 }, opts)
20+
21+
const source = Pushable()
22+
23+
waterfall([
24+
(cb) => getPeer(self._libp2pNode, source, peerId, cb),
25+
(peer, cb) => runPing(self._libp2pNode, source, opts.count, peer, cb)
26+
], (err) => {
27+
if (err) {
28+
log.error(err)
29+
source.push(getPacket({ success: false, text: err.toString() }))
30+
source.end(err)
31+
}
32+
})
33+
34+
return source
35+
}
36+
}
37+
38+
function getPacket (msg) {
39+
// Default msg
40+
const basePacket = { success: true, time: 0, text: '' }
41+
return Object.assign(basePacket, msg)
42+
}
43+
44+
function getPeer (libp2pNode, statusStream, peerId, cb) {
45+
let peer
46+
47+
try {
48+
peer = libp2pNode.peerBook.get(peerId)
49+
} catch (err) {
50+
log('Peer not found in peer book, trying peer routing')
51+
// Share lookup status just as in the go implemmentation
52+
statusStream.push(getPacket({ text: `Looking up peer ${peerId}` }))
53+
54+
// Try to use peerRouting
55+
try {
56+
peerId = PeerId.createFromB58String(peerId)
57+
} catch (err) {
58+
return cb(Object.assign(err, {
59+
message: `failed to parse peer address '${peerId}': input isn't valid multihash`
60+
}))
61+
}
62+
63+
return libp2pNode.peerRouting.findPeer(peerId, cb)
64+
}
65+
66+
cb(null, peer)
67+
}
68+
69+
function runPing (libp2pNode, statusStream, count, peer, cb) {
70+
libp2pNode.ping(peer, (err, p) => {
71+
if (err) {
72+
return cb(err)
73+
}
74+
75+
log('Got peer', peer)
76+
77+
let packetCount = 0
78+
let totalTime = 0
79+
statusStream.push(getPacket({ text: `PING ${peer.id.toB58String()}` }))
80+
81+
p.on('ping', (time) => {
82+
statusStream.push(getPacket({ time: time }))
83+
totalTime += time
84+
packetCount++
85+
if (packetCount >= count) {
86+
const average = totalTime / count
87+
p.stop()
88+
statusStream.push(getPacket({ text: `Average latency: ${average}ms` }))
89+
statusStream.end()
90+
}
91+
})
92+
93+
p.on('error', (err) => {
94+
log.error(err)
95+
p.stop()
96+
statusStream.push(getPacket({ success: false, text: err.toString() }))
97+
statusStream.end(err)
98+
})
99+
100+
p.start()
101+
102+
return cb()
103+
})
104+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict'
2+
3+
const toStream = require('pull-stream-to-stream')
4+
5+
module.exports = function pingReadableStream (self) {
6+
return (peerId, opts) => toStream.source(self.pingPullStream(peerId, opts))
7+
}

src/core/components/ping.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
'use strict'
22

33
const promisify = require('promisify-es6')
4+
const pull = require('pull-stream/pull')
45

56
module.exports = function ping (self) {
6-
return promisify((callback) => {
7-
callback(new Error('Not implemented'))
7+
return promisify((peerId, opts, cb) => {
8+
pull(
9+
self.pingPullStream(peerId, opts),
10+
pull.collect(cb)
11+
)
812
})
913
}

src/core/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ class IPFS extends EventEmitter {
102102
this.files = components.files(this)
103103
this.bitswap = components.bitswap(this)
104104
this.ping = components.ping(this)
105+
this.pingPullStream = components.pingPullStream(this)
106+
this.pingReadableStream = components.pingReadableStream(this)
105107
this.pubsub = components.pubsub(this)
106108
this.dht = components.dht(this)
107109
this.dns = components.dns(this)

src/http/api/resources/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
exports.version = require('./version')
44
exports.shutdown = require('./shutdown')
55
exports.id = require('./id')
6+
exports.ping = require('./ping')
67
exports.bootstrap = require('./bootstrap')
78
exports.repo = require('./repo')
89
exports.object = require('./object')

src/http/api/resources/ping.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
'use strict'
2+
3+
const Joi = require('joi')
4+
const pull = require('pull-stream')
5+
const toStream = require('pull-stream-to-stream')
6+
const ndjson = require('pull-ndjson')
7+
const PassThrough = require('readable-stream').PassThrough
8+
const pump = require('pump')
9+
10+
exports.get = {
11+
validate: {
12+
query: Joi.object().keys({
13+
n: Joi.alternatives()
14+
.when('count', {
15+
is: Joi.any().exist(),
16+
then: Joi.any().forbidden(),
17+
otherwise: Joi.number().integer().greater(0)
18+
}),
19+
count: Joi.number().integer().greater(0),
20+
arg: Joi.string().required()
21+
}).unknown()
22+
},
23+
handler: (request, reply) => {
24+
const ipfs = request.server.app.ipfs
25+
const peerId = request.query.arg
26+
// Default count to 10
27+
const count = request.query.n || request.query.count || 10
28+
29+
const source = pull(
30+
ipfs.pingPullStream(peerId, { count: count }),
31+
pull.map((chunk) => ({
32+
Success: chunk.success,
33+
Time: chunk.time,
34+
Text: chunk.text
35+
})),
36+
ndjson.serialize()
37+
)
38+
39+
// Streams from pull-stream-to-stream don't seem to be compatible
40+
// with the stream2 readable interface
41+
// see: https://github.com/hapijs/hapi/blob/c23070a3de1b328876d5e64e679a147fafb04b38/lib/response.js#L533
42+
// and: https://github.com/pull-stream/pull-stream-to-stream/blob/e436acee18b71af8e71d1b5d32eee642351517c7/index.js#L28
43+
const responseStream = toStream.source(source)
44+
const stream2 = new PassThrough()
45+
pump(responseStream, stream2)
46+
return reply(stream2).type('application/json').header('X-Chunked-Output', '1')
47+
}
48+
}

src/http/api/routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = (server) => {
99
require('./object')(server)
1010
require('./repo')(server)
1111
require('./config')(server)
12+
require('./ping')(server)
1213
require('./swarm')(server)
1314
require('./bitswap')(server)
1415
require('./file')(server)

src/http/api/routes/ping.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
'use strict'
2+
3+
const resources = require('./../resources')
4+
5+
module.exports = (server) => {
6+
const api = server.select('API')
7+
8+
api.route({
9+
method: '*',
10+
path: '/api/v0/ping',
11+
config: {
12+
handler: resources.ping.get.handler,
13+
validate: resources.ping.get.validate
14+
}
15+
})
16+
}

test/cli/commands.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
const expect = require('chai').expect
55
const runOnAndOff = require('../utils/on-and-off')
66

7-
const commandCount = 73
7+
const commandCount = 74
8+
89
describe('commands', () => runOnAndOff((thing) => {
910
let ipfs
1011

0 commit comments

Comments
 (0)