]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/server/redundancy.ts
Move utils to /shared
[github/Chocobozzz/PeerTube.git] / server / tests / api / server / redundancy.ts
CommitLineData
c48e82b5
C
1/* tslint:disable:no-unused-expression */
2
3import * as chai from 'chai'
4import 'mocha'
5import { VideoDetails } from '../../../../shared/models/videos'
6import {
7 doubleFollow,
8 flushAndRunMultipleServers,
c48e82b5
C
9 getFollowingListPaginationAndSort,
10 getVideo,
993cef4b 11 immutableAssign,
ebdb6124 12 killallServers, makeGetRequest,
993cef4b 13 root,
c48e82b5 14 ServerInfo,
161b061d 15 setAccessTokensToServers, unfollow,
c48e82b5 16 uploadVideo,
993cef4b 17 viewVideo,
792e5b8e 18 wait,
25378bc8
C
19 waitUntilLog,
20 checkVideoFilesWereRemoved, removeVideo
9639bd17 21} from '../../../../shared/utils'
22import { waitJobs } from '../../../../shared/utils/server/jobs'
c48e82b5 23import * as magnetUtil from 'magnet-uri'
9639bd17 24import { updateRedundancy } from '../../../../shared/utils/server/redundancy'
c48e82b5
C
25import { ActorFollow } from '../../../../shared/models/actors'
26import { readdir } from 'fs-extra'
27import { join } from 'path'
b36f41ca 28import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy'
9639bd17 29import { getStats } from '../../../../shared/utils/server/stats'
4b5384f6 30import { ServerStats } from '../../../../shared/models/server/server-stats.model'
c48e82b5
C
31
32const expect = chai.expect
33
b36f41ca
C
34let servers: ServerInfo[] = []
35let video1Server2UUID: string
b36f41ca 36
e5565833 37function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) {
c48e82b5
C
38 const parsed = magnetUtil.decode(file.magnetUri)
39
40 for (const ws of baseWebseeds) {
41 const found = parsed.urlList.find(url => url === `${ws}-${file.resolution.id}.mp4`)
e5565833 42 expect(found, `Webseed ${ws} not found in ${file.magnetUri} on server ${server.url}`).to.not.be.undefined
c48e82b5 43 }
161b061d
C
44
45 expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length)
c48e82b5
C
46}
47
3f6b6a56 48async function runServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) {
b36f41ca
C
49 const config = {
50 redundancy: {
993cef4b
C
51 videos: {
52 check_interval: '5 seconds',
53 strategies: [
54 immutableAssign({
e5565833 55 min_lifetime: '1 hour',
993cef4b
C
56 strategy: strategy,
57 size: '100KB'
58 }, additionalParams)
59 ]
60 }
b36f41ca
C
61 }
62 }
63 servers = await flushAndRunMultipleServers(3, config)
c48e82b5 64
b36f41ca
C
65 // Get the access tokens
66 await setAccessTokensToServers(servers)
c48e82b5 67
b36f41ca
C
68 {
69 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' })
70 video1Server2UUID = res.body.video.uuid
c48e82b5 71
b36f41ca
C
72 await viewVideo(servers[ 1 ].url, video1Server2UUID)
73 }
c48e82b5 74
b36f41ca 75 await waitJobs(servers)
c48e82b5 76
b36f41ca
C
77 // Server 1 and server 2 follow each other
78 await doubleFollow(servers[ 0 ], servers[ 1 ])
79 // Server 1 and server 3 follow each other
80 await doubleFollow(servers[ 0 ], servers[ 2 ])
81 // Server 2 and server 3 follow each other
82 await doubleFollow(servers[ 1 ], servers[ 2 ])
83
84 await waitJobs(servers)
85}
c48e82b5 86
e5565833
C
87async function check1WebSeed (strategy: VideoRedundancyStrategy, videoUUID?: string) {
88 if (!videoUUID) videoUUID = video1Server2UUID
89
b36f41ca 90 const webseeds = [
e5565833 91 'http://localhost:9002/static/webseed/' + videoUUID
b36f41ca 92 ]
c48e82b5 93
b36f41ca 94 for (const server of servers) {
4b5384f6 95 {
e5565833 96 const res = await getVideo(server.url, videoUUID)
c48e82b5 97
4b5384f6 98 const video: VideoDetails = res.body
e5565833
C
99 for (const f of video.files) {
100 checkMagnetWebseeds(f, webseeds, server)
101 }
4b5384f6 102 }
e5565833
C
103 }
104}
4b5384f6 105
e5565833
C
106async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) {
107 const res = await getStats(servers[0].url)
108 const data: ServerStats = res.body
4b5384f6 109
e5565833
C
110 expect(data.videosRedundancy).to.have.lengthOf(1)
111 const stat = data.videosRedundancy[0]
4b5384f6 112
e5565833
C
113 expect(stat.strategy).to.equal(strategy)
114 expect(stat.totalSize).to.equal(102400)
115 expect(stat.totalUsed).to.be.at.least(1).and.below(102401)
116 expect(stat.totalVideoFiles).to.equal(4)
117 expect(stat.totalVideos).to.equal(1)
b36f41ca
C
118}
119
e5565833
C
120async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) {
121 const res = await getStats(servers[0].url)
122 const data: ServerStats = res.body
b36f41ca 123
e5565833 124 expect(data.videosRedundancy).to.have.lengthOf(1)
b36f41ca 125
e5565833
C
126 const stat = data.videosRedundancy[0]
127 expect(stat.strategy).to.equal(strategy)
128 expect(stat.totalSize).to.equal(102400)
129 expect(stat.totalUsed).to.equal(0)
130 expect(stat.totalVideoFiles).to.equal(0)
131 expect(stat.totalVideos).to.equal(0)
b36f41ca 132}
c48e82b5 133
e5565833
C
134async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: string) {
135 if (!videoUUID) videoUUID = video1Server2UUID
c48e82b5 136
b36f41ca 137 const webseeds = [
e5565833
C
138 'http://localhost:9001/static/webseed/' + videoUUID,
139 'http://localhost:9002/static/webseed/' + videoUUID
b36f41ca 140 ]
c48e82b5 141
b36f41ca 142 for (const server of servers) {
161b061d 143 const res = await getVideo(server.url, videoUUID)
b36f41ca 144
161b061d
C
145 const video: VideoDetails = res.body
146
147 for (const file of video.files) {
148 checkMagnetWebseeds(file, webseeds, server)
b36f41ca 149
161b061d
C
150 // Only servers 1 and 2 have the video
151 if (server.serverNumber !== 3) {
152 await makeGetRequest({
153 url: server.url,
154 statusCodeExpected: 200,
155 path: '/static/webseed/' + `${videoUUID}-${file.resolution.id}.mp4`,
156 contentType: null
157 })
4b5384f6 158 }
c48e82b5 159 }
b36f41ca 160 }
c48e82b5 161
ebdb6124
C
162 for (const directory of [ 'test1', 'test2' ]) {
163 const files = await readdir(join(root(), directory, 'videos'))
164 expect(files).to.have.length.at.least(4)
c48e82b5 165
ebdb6124
C
166 for (const resolution of [ 240, 360, 480, 720 ]) {
167 expect(files.find(f => f === `${videoUUID}-${resolution}.mp4`)).to.not.be.undefined
168 }
b36f41ca 169 }
e5565833 170}
4b5384f6 171
e5565833
C
172async function enableRedundancyOnServer1 () {
173 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true)
4b5384f6 174
e5565833
C
175 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
176 const follows: ActorFollow[] = res.body.data
177 const server2 = follows.find(f => f.following.host === 'localhost:9002')
178 const server3 = follows.find(f => f.following.host === 'localhost:9003')
4b5384f6 179
e5565833
C
180 expect(server3).to.not.be.undefined
181 expect(server3.following.hostRedundancyAllowed).to.be.false
182
183 expect(server2).to.not.be.undefined
184 expect(server2.following.hostRedundancyAllowed).to.be.true
b36f41ca 185}
c48e82b5 186
161b061d
C
187async function disableRedundancyOnServer1 () {
188 await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, false)
189
190 const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, '-createdAt')
191 const follows: ActorFollow[] = res.body.data
192 const server2 = follows.find(f => f.following.host === 'localhost:9002')
193 const server3 = follows.find(f => f.following.host === 'localhost:9003')
194
195 expect(server3).to.not.be.undefined
196 expect(server3.following.hostRedundancyAllowed).to.be.false
197
198 expect(server2).to.not.be.undefined
199 expect(server2.following.hostRedundancyAllowed).to.be.false
200}
201
b36f41ca
C
202async function cleanServers () {
203 killallServers(servers)
204}
c48e82b5 205
b36f41ca 206describe('Test videos redundancy', function () {
c48e82b5 207
b36f41ca 208 describe('With most-views strategy', function () {
4b5384f6 209 const strategy = 'most-views'
c48e82b5 210
b36f41ca
C
211 before(function () {
212 this.timeout(120000)
c48e82b5 213
4b5384f6 214 return runServers(strategy)
b36f41ca 215 })
c48e82b5 216
e5565833
C
217 it('Should have 1 webseed on the first video', async function () {
218 await check1WebSeed(strategy)
219 await checkStatsWith1Webseed(strategy)
b36f41ca 220 })
c48e82b5 221
3f6b6a56 222 it('Should enable redundancy on server 1', function () {
e5565833 223 return enableRedundancyOnServer1()
b36f41ca 224 })
c48e82b5 225
e5565833 226 it('Should have 2 webseed on the first video', async function () {
b36f41ca 227 this.timeout(40000)
c48e82b5 228
e5565833 229 await waitJobs(servers)
792e5b8e 230 await waitUntilLog(servers[0], 'Duplicated ', 4)
e5565833
C
231 await waitJobs(servers)
232
233 await check2Webseeds(strategy)
234 await checkStatsWith2Webseed(strategy)
b36f41ca 235 })
c48e82b5 236
161b061d
C
237 it('Should undo redundancy on server 1 and remove duplicated videos', async function () {
238 this.timeout(40000)
239
240 await disableRedundancyOnServer1()
241
242 await waitJobs(servers)
243 await wait(5000)
244
245 await check1WebSeed(strategy)
25378bc8
C
246
247 await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ])
161b061d
C
248 })
249
b36f41ca
C
250 after(function () {
251 return cleanServers()
252 })
c48e82b5
C
253 })
254
b36f41ca 255 describe('With trending strategy', function () {
4b5384f6 256 const strategy = 'trending'
c48e82b5 257
b36f41ca
C
258 before(function () {
259 this.timeout(120000)
260
4b5384f6 261 return runServers(strategy)
b36f41ca
C
262 })
263
e5565833
C
264 it('Should have 1 webseed on the first video', async function () {
265 await check1WebSeed(strategy)
266 await checkStatsWith1Webseed(strategy)
b36f41ca
C
267 })
268
3f6b6a56 269 it('Should enable redundancy on server 1', function () {
e5565833 270 return enableRedundancyOnServer1()
b36f41ca
C
271 })
272
e5565833 273 it('Should have 2 webseed on the first video', async function () {
3f6b6a56
C
274 this.timeout(40000)
275
e5565833 276 await waitJobs(servers)
792e5b8e 277 await waitUntilLog(servers[0], 'Duplicated ', 4)
e5565833
C
278 await waitJobs(servers)
279
280 await check2Webseeds(strategy)
281 await checkStatsWith2Webseed(strategy)
3f6b6a56
C
282 })
283
161b061d
C
284 it('Should unfollow on server 1 and remove duplicated videos', async function () {
285 this.timeout(40000)
286
287 await unfollow(servers[0].url, servers[0].accessToken, servers[1])
288
289 await waitJobs(servers)
290 await wait(5000)
291
292 await check1WebSeed(strategy)
25378bc8
C
293
294 await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ])
161b061d
C
295 })
296
3f6b6a56
C
297 after(function () {
298 return cleanServers()
299 })
300 })
301
302 describe('With recently added strategy', function () {
4b5384f6 303 const strategy = 'recently-added'
3f6b6a56
C
304
305 before(function () {
306 this.timeout(120000)
307
e5565833 308 return runServers(strategy, { min_views: 3 })
3f6b6a56
C
309 })
310
e5565833
C
311 it('Should have 1 webseed on the first video', async function () {
312 await check1WebSeed(strategy)
313 await checkStatsWith1Webseed(strategy)
3f6b6a56
C
314 })
315
316 it('Should enable redundancy on server 1', function () {
e5565833 317 return enableRedundancyOnServer1()
3f6b6a56
C
318 })
319
320 it('Should still have 1 webseed on the first video', async function () {
321 this.timeout(40000)
322
323 await waitJobs(servers)
324 await wait(15000)
325 await waitJobs(servers)
326
e5565833
C
327 await check1WebSeed(strategy)
328 await checkStatsWith1Webseed(strategy)
3f6b6a56
C
329 })
330
e5565833 331 it('Should view 2 times the first video to have > min_views config', async function () {
3f6b6a56
C
332 this.timeout(40000)
333
334 await viewVideo(servers[ 0 ].url, video1Server2UUID)
335 await viewVideo(servers[ 2 ].url, video1Server2UUID)
336
337 await wait(10000)
338 await waitJobs(servers)
339 })
340
e5565833 341 it('Should have 2 webseed on the first video', async function () {
b36f41ca
C
342 this.timeout(40000)
343
e5565833 344 await waitJobs(servers)
792e5b8e 345 await waitUntilLog(servers[0], 'Duplicated ', 4)
e5565833
C
346 await waitJobs(servers)
347
348 await check2Webseeds(strategy)
349 await checkStatsWith2Webseed(strategy)
350 })
351
25378bc8
C
352 it('Should remove the video and the redundancy files', async function () {
353 this.timeout(20000)
354
355 await removeVideo(servers[1].url, servers[1].accessToken, video1Server2UUID)
356
357 await waitJobs(servers)
358
359 for (const server of servers) {
360 await checkVideoFilesWereRemoved(video1Server2UUID, server.serverNumber)
361 }
362 })
363
e5565833
C
364 after(function () {
365 return cleanServers()
366 })
367 })
368
369 describe('Test expiration', function () {
370 const strategy = 'recently-added'
371
372 async function checkContains (servers: ServerInfo[], str: string) {
373 for (const server of servers) {
374 const res = await getVideo(server.url, video1Server2UUID)
375 const video: VideoDetails = res.body
376
377 for (const f of video.files) {
378 expect(f.magnetUri).to.contain(str)
379 }
380 }
381 }
382
383 async function checkNotContains (servers: ServerInfo[], str: string) {
384 for (const server of servers) {
385 const res = await getVideo(server.url, video1Server2UUID)
386 const video: VideoDetails = res.body
387
388 for (const f of video.files) {
389 expect(f.magnetUri).to.not.contain(str)
390 }
391 }
392 }
393
394 before(async function () {
395 this.timeout(120000)
396
397 await runServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
398
399 await enableRedundancyOnServer1()
400 })
401
402 it('Should still have 2 webseeds after 10 seconds', async function () {
403 this.timeout(40000)
404
405 await wait(10000)
406
407 try {
408 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001')
409 } catch {
410 // Maybe a server deleted a redundancy in the scheduler
411 await wait(2000)
412
413 await checkContains(servers, 'http%3A%2F%2Flocalhost%3A9001')
414 }
415 })
416
417 it('Should stop server 1 and expire video redundancy', async function () {
418 this.timeout(40000)
419
420 killallServers([ servers[0] ])
421
fd28a0fc 422 await wait(15000)
e5565833
C
423
424 await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A9001')
425 })
426
427 after(function () {
428 return killallServers([ servers[1], servers[2] ])
429 })
430 })
431
432 describe('Test file replacement', function () {
433 let video2Server2UUID: string
434 const strategy = 'recently-added'
435
436 before(async function () {
437 this.timeout(120000)
438
439 await runServers(strategy, { min_lifetime: '7 seconds', min_views: 0 })
440
441 await enableRedundancyOnServer1()
442
443 await waitJobs(servers)
792e5b8e 444 await waitUntilLog(servers[0], 'Duplicated ', 4)
e5565833
C
445 await waitJobs(servers)
446
447 await check2Webseeds(strategy)
448 await checkStatsWith2Webseed(strategy)
449
450 const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' })
451 video2Server2UUID = res.body.video.uuid
452 })
453
454 it('Should cache video 2 webseed on the first video', async function () {
fd28a0fc 455 this.timeout(120000)
e5565833
C
456
457 await waitJobs(servers)
458
fd28a0fc 459 let checked = false
e5565833 460
fd28a0fc
C
461 while (checked === false) {
462 await wait(1000)
792e5b8e 463
278711b5
C
464 try {
465 await check1WebSeed(strategy, video1Server2UUID)
466 await check2Webseeds(strategy, video2Server2UUID)
278711b5 467
fd28a0fc
C
468 checked = true
469 } catch {
470 checked = false
278711b5 471 }
792e5b8e 472 }
b36f41ca
C
473 })
474
475 after(function () {
476 return cleanServers()
477 })
c48e82b5
C
478 })
479})