Skip to content

Commit 7717cd1

Browse files
committed
Major update with Hyperdrive API.
1 parent baca504 commit 7717cd1

File tree

6 files changed

+286
-57
lines changed

6 files changed

+286
-57
lines changed

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class HyperdriveDaemon extends EventEmitter {
8585
}
8686

8787
module.exports = async function start (opts = {}) {
88-
const metadata = await new Promise((resolve, reject) => {
88+
const metadata = opts.metadata || await new Promise((resolve, reject) => {
8989
loadMetadata((err, metadata) => {
9090
if (err) return reject(err)
9191
return resolve(metadata)
@@ -96,6 +96,7 @@ module.exports = async function start (opts = {}) {
9696

9797
const daemonOpts = {}
9898
const bootstrapOpts = opts.bootstrap || argv.bootstrap
99+
console.log('BOOTSTRAP OPTS:', opts.bootstrap)
99100
if (bootstrapOpts.length) {
100101
if (bootstrapOpts === false && bootstrapOpts[0] === 'false') {
101102
daemonOpts.network = { bootstrap: false }

lib/drives/index.js

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ const {
1111
fromHyperdriveOptions,
1212
toHyperdriveOptions,
1313
fromStat,
14-
toStat
14+
toStat,
15+
toMount,
16+
fromMount
1517
} = require('hyperdrive-daemon-client/lib/common')
1618
const { rpc } = require('hyperdrive-daemon-client')
1719

@@ -151,6 +153,10 @@ class DriveManager extends EventEmitter {
151153

152154
if (drive.writable) {
153155
await this._configureDrive(drive, opts && opts.configure)
156+
} else {
157+
// All read-only drives are currently published by default.
158+
console.log('PUBLISHING READ-ONLY DRIVE')
159+
await this.publish(drive)
154160
}
155161
if (this.opts.stats) {
156162
this._collectStats(drive)
@@ -228,6 +234,32 @@ function createDriveHandlers (driveManager) {
228234
return rsp
229235
},
230236

237+
publish: async (call) => {
238+
console.error('IN SERVER PUBLISH')
239+
const id = call.request.getId()
240+
241+
if (!id) throw new Error('A publish request must specify a session ID.')
242+
const drive = driveManager.driveForSession(id)
243+
244+
console.log('PUBLISHING DRIVE WITH KEY:', drive.key)
245+
await driveManager.publish(drive)
246+
247+
const rsp = new rpc.drive.messages.PublishDriveResponse()
248+
return rsp
249+
},
250+
251+
unpublish: async (call) => {
252+
const id = call.request.getId()
253+
254+
if (!id) throw new Error('An unpublish request must specify a session ID.')
255+
const drive = driveManager.driveForSession(id)
256+
257+
await driveManager.unpublish(drive)
258+
259+
const rsp = new rpc.drive.messages.UnpublishDriveResponse()
260+
return rsp
261+
},
262+
231263
readFile: async (call) => {
232264
const id = call.request.getId()
233265
const path = call.request.getPath()
@@ -294,22 +326,41 @@ function createDriveHandlers (driveManager) {
294326
const recursive = call.request.getRecursive()
295327

296328
if (!id) throw new Error('A readdir request must specify a session ID.')
297-
if (!path) throw new Error('A readdir request must specify a path. ')
329+
if (!path) throw new Error('A readdir request must specify a path.')
298330
const drive = driveManager.driveForSession(id)
299331

300332
return new Promise((resolve, reject) => {
301333
drive.readdir(path, { recursive }, (err, files) => {
302334
if (err) return reject(err)
303335

304336
const rsp = new rpc.drive.messages.ReadDirectoryResponse()
305-
console.log('FILES:', files)
306337
rsp.setFilesList(files)
307338

308339
return resolve(rsp)
309340
})
310341
})
311342
},
312343

344+
mount: async (call) => {
345+
const id = call.request.getId()
346+
const path = call.request.getPath()
347+
const opts = fromMount(call.request.getOpts())
348+
349+
if (!id) throw new Error('A mount request must specify a session ID.')
350+
if (!path) throw new Error('A mount request must specify a path.')
351+
if (!opts) throw new Error('A mount request must specify mount options.')
352+
const drive = driveManager.driveForSession(id)
353+
354+
return new Promise((resolve, reject) => {
355+
drive.mount(path, opts.key, opts, err => {
356+
console.error('after server mount, err:', err)
357+
if (err) return reject(err)
358+
const rsp = new rpc.drive.messages.MountDriveResponse()
359+
return resolve(rsp)
360+
})
361+
})
362+
},
363+
313364
watch: async (call) => {
314365

315366
},
@@ -322,7 +373,7 @@ function createDriveHandlers (driveManager) {
322373

323374
},
324375

325-
closeSession: async (call) => {
376+
close: async (call) => {
326377
const id = call.request.getId()
327378

328379
const drive = driveManager.driveForSession(id)

lib/fuse/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ class FuseManager extends EventEmitter {
291291
await this.unmount()
292292
return mountRoot(drive)
293293
}
294-
console.error('MOUNTING SUBDRIVE HERE')
295294
return mountSubdrive(relativePath, drive)
296295

297296
async function mountSubdrive (relativePath, drive) {
Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
const { spawn } = require('child_process')
2-
3-
const pify = require('pify')
41
const test = require('tape')
5-
const tmp = require('tmp-promise')
6-
7-
const PORT = 3101
8-
process.env['HYPERDRIVE_TOKEN'] = 'test-token'
9-
process.env['HYPERDRIVE_ENDPOINT'] = `localhost:${PORT}`
102

11-
const loadClient = require('hyperdrive-daemon-client/lib/loader')
12-
const start = require('..')
3+
const { createOne } = require('./util/create')
134

145
test('can write/read a file from a remote hyperdrive', async t => {
15-
const { client, cleanup } = await create()
6+
const { client, cleanup } = await createOne()
167

178
try {
189
const { opts, id } = await client.drive.get()
@@ -24,7 +15,7 @@ test('can write/read a file from a remote hyperdrive', async t => {
2415
const contents = await client.drive.readFile(id, 'hello')
2516
t.same(contents, Buffer.from('world'))
2617

27-
await client.drive.closeSession(id)
18+
await client.drive.close(id)
2819
} catch (err) {
2920
t.fail(err)
3021
}
@@ -34,7 +25,7 @@ test('can write/read a file from a remote hyperdrive', async t => {
3425
})
3526

3627
test('can stat a file from a remote hyperdrive', async t => {
37-
const { client, cleanup } = await create()
28+
const { client, cleanup } = await createOne()
3829

3930
try {
4031
const { opts, id } = await client.drive.get()
@@ -48,7 +39,7 @@ test('can stat a file from a remote hyperdrive', async t => {
4839
t.same(stat.uid, 0)
4940
t.same(stat.gid, 0)
5041

51-
await client.drive.closeSession(id)
42+
await client.drive.close(id)
5243
} catch (err) {
5344
t.fail(err)
5445
}
@@ -58,7 +49,7 @@ test('can stat a file from a remote hyperdrive', async t => {
5849
})
5950

6051
test('can list a directory from a remote hyperdrive', async t => {
61-
const { client, cleanup } = await create()
52+
const { client, cleanup } = await createOne()
6253

6354
try {
6455
const { opts, id } = await client.drive.get()
@@ -67,7 +58,7 @@ test('can list a directory from a remote hyperdrive', async t => {
6758

6859
await client.drive.writeFile(id, 'hello', 'world')
6960
await client.drive.writeFile(id, 'goodbye', 'dog')
70-
await client.drive.writeFile(id, 'adios', 'friend')
61+
await client.drive.writeFile(id, 'adios', 'amigo')
7162

7263
const files = await client.drive.readdir(id, '')
7364
t.same(files.length, 4)
@@ -76,7 +67,7 @@ test('can list a directory from a remote hyperdrive', async t => {
7667
t.notEqual(files.indexOf('adios'), -1)
7768
t.notEqual(files.indexOf('.key'), -1)
7869

79-
await client.drive.closeSession(id)
70+
await client.drive.close(id)
8071
} catch (err) {
8172
t.fail(err)
8273
}
@@ -86,7 +77,7 @@ test('can list a directory from a remote hyperdrive', async t => {
8677
})
8778

8879
test('can read/write multiple remote hyperdrives on one server', async t => {
89-
const { client, cleanup } = await create()
80+
const { client, cleanup } = await createOne()
9081
var startingId = 1
9182

9283
const files = [
@@ -116,37 +107,39 @@ test('can read/write multiple remote hyperdrives on one server', async t => {
116107
t.end()
117108
})
118109

119-
async function create () {
120-
const { path, cleanup: dirCleanup } = await tmp.dir({ unsafeCleanup: true })
121-
const stop = await start({
122-
storage: path,
123-
bootstrap: false,
124-
port: 3101
125-
})
126-
127-
return new Promise((resolve, reject) => {
128-
return loadClient((err, client) => {
129-
if (err) return reject(err)
130-
return resolve({
131-
client: {
132-
drive: promisifyClass(client.drive),
133-
fuse: promisifyClass(client.fuse)
134-
},
135-
cleanup
136-
})
137-
})
138-
})
139-
140-
async function cleanup () {
141-
await stop()
142-
await dirCleanup()
110+
test('can mount a drive within a remote hyperdrive', async t => {
111+
const { client, cleanup } = await createOne()
112+
113+
try {
114+
const { opts: opts1, id: id1 } = await client.drive.get()
115+
t.true(opts1.key)
116+
t.same(id1, 1)
117+
118+
const { opts: opts2, id: id2 } = await client.drive.get()
119+
t.true(opts2.key)
120+
t.same(id2, 2)
121+
t.notEqual(opts1.key, opts2.key)
122+
123+
const noVersion = { ...opts2, version: null }
124+
125+
await client.drive.mount(id1, 'a', noVersion)
126+
127+
await client.drive.writeFile(id1, 'a/hello', 'world')
128+
await client.drive.writeFile(id1, 'a/goodbye', 'dog')
129+
await client.drive.writeFile(id1, 'adios', 'amigo')
130+
await client.drive.writeFile(id2, 'hamster', 'wheel')
131+
132+
t.same(await client.drive.readFile(id1, 'adios'), Buffer.from('amigo'))
133+
t.same(await client.drive.readFile(id1, 'a/hello'), Buffer.from('world'))
134+
t.same(await client.drive.readFile(id2, 'hello'), Buffer.from('world'))
135+
t.same(await client.drive.readFile(id2, 'hamster'), Buffer.from('wheel'))
136+
137+
await client.drive.close(id1)
138+
await client.drive.close(id2)
139+
} catch (err) {
140+
t.fail(err)
143141
}
144-
}
145-
146-
function promisifyClass (clazz) {
147-
const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(clazz)).filter(name => name !== 'constructor')
148-
methods.forEach(name => {
149-
clazz[name] = pify(clazz[name])
150-
})
151-
return clazz
152-
}
142+
143+
await cleanup()
144+
t.end()
145+
})

test/replication.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const test = require('tape')
2+
3+
const { create } = require('./util/create')
4+
5+
test('can replicate a single drive between daemons', async t => {
6+
const { clients, cleanup } = await create(2)
7+
const firstClient = clients[0]
8+
const secondClient = clients[1]
9+
10+
try {
11+
const { opts, id: id1 } = await firstClient.drive.get()
12+
await firstClient.drive.publish(id1)
13+
14+
const { id: id2 } = await secondClient.drive.get({ key: opts.key })
15+
16+
await firstClient.drive.writeFile(id1, 'hello', 'world')
17+
18+
// 100 ms delay for replication.
19+
await delay(100)
20+
21+
const replicatedContent = await secondClient.drive.readFile(id2, 'hello')
22+
t.same(replicatedContent, Buffer.from('world'))
23+
} catch (err) {
24+
t.fail(err)
25+
}
26+
27+
await cleanup()
28+
t.end()
29+
})
30+
31+
test('can replicate many mounted drives between daemons', async t => {
32+
const { clients, cleanup } = await create(2)
33+
const firstClient = clients[0]
34+
const secondClient = clients[1]
35+
36+
const NUM_MOUNTS = 100
37+
38+
try {
39+
const mounts = await createFirst()
40+
const second = await createSecond(mounts)
41+
42+
// 100 ms delay for replication.
43+
console.log('VALIDATING IN 10s')
44+
await delay(10000)
45+
46+
await validate(mounts, second)
47+
} catch (err) {
48+
t.fail(err)
49+
}
50+
51+
await cleanup()
52+
t.end()
53+
54+
async function createFirst () {
55+
const { opts: rootOpts, id: rootId } = await firstClient.drive.get()
56+
const mounts = []
57+
for (let i = 0; i < NUM_MOUNTS; i++) {
58+
const key = '' + i
59+
const { opts: mountOpts, id: mountId } = await firstClient.drive.get()
60+
await firstClient.drive.mount(rootId, key, { ...mountOpts, version: null })
61+
await firstClient.drive.writeFile(mountId, key, key)
62+
await firstClient.drive.publish(mountId)
63+
mounts.push({ key: mountOpts.key, path: key + '/' + key, content: key })
64+
}
65+
return mounts
66+
}
67+
68+
async function createSecond (mounts) {
69+
const { opts: rootOpts, id: rootId } = await secondClient.drive.get()
70+
for (const { key, path, content } of mounts) {
71+
const { id } = await secondClient.drive.get({ key })
72+
await secondClient.drive.mount(rootId, content, { key })
73+
}
74+
return rootId
75+
}
76+
77+
async function validate (mounts, id) {
78+
for (const { key, path, content } of mounts) {
79+
const readContent = await secondClient.drive.readFile(id, path)
80+
t.same(readContent, Buffer.from(content))
81+
}
82+
}
83+
})
84+
85+
test('can replicate recursive mounts between daemons', async t => {
86+
t.end()
87+
})
88+
89+
function delay (ms) {
90+
return new Promise(resolve => setTimeout(resolve, ms))
91+
}

0 commit comments

Comments
 (0)