From b764380ac23f4e9d4677d08acdc3474c2931a16d Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 10 Jan 2020 10:11:28 +0100 Subject: Add ability to list redundancies --- server/tests/api/check-params/redundancy.ts | 141 ++++++++- server/tests/api/redundancy/index.ts | 1 + server/tests/api/redundancy/manage-redundancy.ts | 373 +++++++++++++++++++++++ server/tests/api/redundancy/redundancy.ts | 138 +++++++-- 4 files changed, 624 insertions(+), 29 deletions(-) create mode 100644 server/tests/api/redundancy/manage-redundancy.ts (limited to 'server/tests') diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts index 6471da840..7012a39ee 100644 --- a/server/tests/api/check-params/redundancy.ts +++ b/server/tests/api/check-params/redundancy.ts @@ -3,21 +3,25 @@ import 'mocha' import { + checkBadCountPagination, + checkBadSortPagination, + checkBadStartPagination, cleanupTests, createUser, doubleFollow, - flushAndRunMultipleServers, - flushTests, - killallServers, + flushAndRunMultipleServers, makeDeleteRequest, + makeGetRequest, makePostBodyRequest, makePutBodyRequest, ServerInfo, - setAccessTokensToServers, - userLogin + setAccessTokensToServers, uploadVideoAndGetId, + userLogin, waitJobs } from '../../../../shared/extra-utils' describe('Test server redundancy API validators', function () { let servers: ServerInfo[] let userAccessToken = null + let videoIdLocal: number + let videoIdRemote: number // --------------------------------------------------------------- @@ -36,9 +40,134 @@ describe('Test server redundancy API validators', function () { await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) userAccessToken = await userLogin(servers[0], user) + + videoIdLocal = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video' })).id + videoIdRemote = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video' })).id + + await waitJobs(servers) + }) + + describe('When listing redundancies', function () { + const path = '/api/v1/server/redundancy/videos' + + let url: string + let token: string + + before(function () { + url = servers[0].url + token = servers[0].accessToken + }) + + it('Should fail with an invalid token', async function () { + await makeGetRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 }) + }) + + it('Should fail if the user is not an administrator', async function () { + await makeGetRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 }) + }) + + it('Should fail with a bad start pagination', async function () { + await checkBadStartPagination(url, path, servers[0].accessToken) + }) + + it('Should fail with a bad count pagination', async function () { + await checkBadCountPagination(url, path, servers[0].accessToken) + }) + + it('Should fail with an incorrect sort', async function () { + await checkBadSortPagination(url, path, servers[0].accessToken) + }) + + it('Should fail with a bad target', async function () { + await makeGetRequest({ url, path, token, query: { target: 'bad target' } }) + }) + + it('Should fail without target', async function () { + await makeGetRequest({ url, path, token }) + }) + + it('Should succeed with the correct params', async function () { + await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, statusCodeExpected: 200 }) + }) + }) + + describe('When manually adding a redundancy', function () { + const path = '/api/v1/server/redundancy/videos' + + let url: string + let token: string + + before(function () { + url = servers[0].url + token = servers[0].accessToken + }) + + it('Should fail with an invalid token', async function () { + await makePostBodyRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 }) + }) + + it('Should fail if the user is not an administrator', async function () { + await makePostBodyRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 }) + }) + + it('Should fail without a video id', async function () { + await makePostBodyRequest({ url, path, token }) + }) + + it('Should fail with an incorrect video id', async function () { + await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } }) + }) + + it('Should fail with a not found video id', async function () { + await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, statusCodeExpected: 404 }) + }) + + it('Should fail with a local a video id', async function () { + await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } }) + }) + + it('Should succeed with the correct params', async function () { + await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 204 }) + }) + + it('Should fail if the video is already duplicated', async function () { + this.timeout(30000) + + await waitJobs(servers) + + await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 409 }) + }) + }) + + describe('When manually removing a redundancy', function () { + const path = '/api/v1/server/redundancy/videos/' + + let url: string + let token: string + + before(function () { + url = servers[0].url + token = servers[0].accessToken + }) + + it('Should fail with an invalid token', async function () { + await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', statusCodeExpected: 401 }) + }) + + it('Should fail if the user is not an administrator', async function () { + await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, statusCodeExpected: 403 }) + }) + + it('Should fail with an incorrect video id', async function () { + await makeDeleteRequest({ url, path: path + 'toto', token }) + }) + + it('Should fail with a not found video redundancy', async function () { + await makeDeleteRequest({ url, path: path + '454545', token, statusCodeExpected: 404 }) + }) }) - describe('When updating redundancy', function () { + describe('When updating server redundancy', function () { const path = '/api/v1/server/redundancy' it('Should fail with an invalid token', async function () { diff --git a/server/tests/api/redundancy/index.ts b/server/tests/api/redundancy/index.ts index 8e69b95a6..5359055b0 100644 --- a/server/tests/api/redundancy/index.ts +++ b/server/tests/api/redundancy/index.ts @@ -1 +1,2 @@ import './redundancy' +import './manage-redundancy' diff --git a/server/tests/api/redundancy/manage-redundancy.ts b/server/tests/api/redundancy/manage-redundancy.ts new file mode 100644 index 000000000..6a8937f24 --- /dev/null +++ b/server/tests/api/redundancy/manage-redundancy.ts @@ -0,0 +1,373 @@ +/* tslint:disable:no-unused-expression */ + +import * as chai from 'chai' +import 'mocha' +import { + cleanupTests, + doubleFollow, + flushAndRunMultipleServers, + getLocalIdByUUID, + ServerInfo, + setAccessTokensToServers, + uploadVideo, + uploadVideoAndGetId, + waitUntilLog +} from '../../../../shared/extra-utils' +import { waitJobs } from '../../../../shared/extra-utils/server/jobs' +import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy, updateRedundancy } from '@shared/extra-utils/server/redundancy' +import { VideoPrivacy, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' + +const expect = chai.expect + +describe('Test manage videos redundancy', function () { + const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ] + + let servers: ServerInfo[] + let video1Server2UUID: string + let video2Server2UUID: string + let redundanciesToRemove: number[] = [] + + before(async function () { + this.timeout(120000) + + const config = { + transcoding: { + hls: { + enabled: true + } + }, + redundancy: { + videos: { + check_interval: '1 second', + strategies: [ + { + strategy: 'recently-added', + min_lifetime: '1 hour', + size: '10MB', + min_views: 0 + } + ] + } + } + } + servers = await flushAndRunMultipleServers(3, config) + + // Get the access tokens + await setAccessTokensToServers(servers) + + { + const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' }) + video1Server2UUID = res.body.video.uuid + } + + { + const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) + video2Server2UUID = res.body.video.uuid + } + + await waitJobs(servers) + + // Server 1 and server 2 follow each other + await doubleFollow(servers[ 0 ], servers[ 1 ]) + await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) + + await waitJobs(servers) + }) + + it('Should not have redundancies on server 3', async function () { + for (const target of targets) { + const res = await listVideoRedundancies({ + url: servers[2].url, + accessToken: servers[2].accessToken, + target + }) + + expect(res.body.total).to.equal(0) + expect(res.body.data).to.have.lengthOf(0) + } + }) + + it('Should not have "remote-videos" redundancies on server 2', async function () { + this.timeout(120000) + + await waitJobs(servers) + await waitUntilLog(servers[0], 'Duplicated ', 10) + await waitJobs(servers) + + const res = await listVideoRedundancies({ + url: servers[1].url, + accessToken: servers[1].accessToken, + target: 'remote-videos' + }) + + expect(res.body.total).to.equal(0) + expect(res.body.data).to.have.lengthOf(0) + }) + + it('Should have "my-videos" redundancies on server 2', async function () { + this.timeout(120000) + + const res = await listVideoRedundancies({ + url: servers[1].url, + accessToken: servers[1].accessToken, + target: 'my-videos' + }) + + expect(res.body.total).to.equal(2) + + const videos = res.body.data as VideoRedundancy[] + expect(videos).to.have.lengthOf(2) + + const videos1 = videos.find(v => v.uuid === video1Server2UUID) + const videos2 = videos.find(v => v.uuid === video2Server2UUID) + + expect(videos1.name).to.equal('video 1 server 2') + expect(videos2.name).to.equal('video 2 server 2') + + expect(videos1.redundancies.files).to.have.lengthOf(4) + expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1) + + const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists) + + for (const r of redundancies) { + expect(r.strategy).to.be.null + expect(r.fileUrl).to.exist + expect(r.createdAt).to.exist + expect(r.updatedAt).to.exist + expect(r.expiresOn).to.exist + } + }) + + it('Should not have "my-videos" redundancies on server 1', async function () { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'my-videos' + }) + + expect(res.body.total).to.equal(0) + expect(res.body.data).to.have.lengthOf(0) + }) + + it('Should have "remote-videos" redundancies on server 1', async function () { + this.timeout(120000) + + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos' + }) + + expect(res.body.total).to.equal(2) + + const videos = res.body.data as VideoRedundancy[] + expect(videos).to.have.lengthOf(2) + + const videos1 = videos.find(v => v.uuid === video1Server2UUID) + const videos2 = videos.find(v => v.uuid === video2Server2UUID) + + expect(videos1.name).to.equal('video 1 server 2') + expect(videos2.name).to.equal('video 2 server 2') + + expect(videos1.redundancies.files).to.have.lengthOf(4) + expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1) + + const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists) + + for (const r of redundancies) { + expect(r.strategy).to.equal('recently-added') + expect(r.fileUrl).to.exist + expect(r.createdAt).to.exist + expect(r.updatedAt).to.exist + expect(r.expiresOn).to.exist + } + }) + + it('Should correctly paginate and sort results', async function () { + { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: 'name', + start: 0, + count: 2 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 1 server 2') + expect(videos[ 1 ].name).to.equal('video 2 server 2') + } + + { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: '-name', + start: 0, + count: 2 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 2 server 2') + expect(videos[ 1 ].name).to.equal('video 1 server 2') + } + + { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: '-name', + start: 1, + count: 1 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 1 server 2') + } + }) + + it('Should manually add a redundancy and list it', async function () { + this.timeout(120000) + + const uuid = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video 3 server 2', privacy: VideoPrivacy.UNLISTED })).uuid + await waitJobs(servers) + const videoId = await getLocalIdByUUID(servers[0].url, uuid) + + await addVideoRedundancy({ + url: servers[0].url, + accessToken: servers[0].accessToken, + videoId + }) + + await waitJobs(servers) + await waitUntilLog(servers[0], 'Duplicated ', 15) + await waitJobs(servers) + + { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: '-name', + start: 0, + count: 5 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 3 server 2') + + const video = videos[ 0 ] + expect(video.redundancies.files).to.have.lengthOf(4) + expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) + + const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) + + for (const r of redundancies) { + redundanciesToRemove.push(r.id) + + expect(r.strategy).to.equal('manual') + expect(r.fileUrl).to.exist + expect(r.createdAt).to.exist + expect(r.updatedAt).to.exist + expect(r.expiresOn).to.be.null + } + } + + const res = await listVideoRedundancies({ + url: servers[1].url, + accessToken: servers[1].accessToken, + target: 'my-videos', + sort: '-name', + start: 0, + count: 5 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 3 server 2') + + const video = videos[ 0 ] + expect(video.redundancies.files).to.have.lengthOf(4) + expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) + + const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) + + for (const r of redundancies) { + expect(r.strategy).to.be.null + expect(r.fileUrl).to.exist + expect(r.createdAt).to.exist + expect(r.updatedAt).to.exist + expect(r.expiresOn).to.be.null + } + }) + + it('Should manually remove a redundancy and remove it from the list', async function () { + this.timeout(120000) + + for (const redundancyId of redundanciesToRemove) { + await removeVideoRedundancy({ + url: servers[ 0 ].url, + accessToken: servers[ 0 ].accessToken, + redundancyId + }) + } + + { + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: '-name', + start: 0, + count: 5 + }) + + const videos = res.body.data + expect(videos).to.have.lengthOf(2) + + expect(videos[ 0 ].name).to.equal('video 2 server 2') + + redundanciesToRemove = [] + const video = videos[ 0 ] + expect(video.redundancies.files).to.have.lengthOf(4) + expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) + + const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) + + for (const r of redundancies) { + redundanciesToRemove.push(r.id) + } + } + }) + + it('Should remove another (auto) redundancy', async function () { + { + for (const redundancyId of redundanciesToRemove) { + await removeVideoRedundancy({ + url: servers[ 0 ].url, + accessToken: servers[ 0 ].accessToken, + redundancyId + }) + } + + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos', + sort: '-name', + start: 0, + count: 5 + }) + + const videos = res.body.data + expect(videos[ 0 ].name).to.equal('video 1 server 2') + expect(videos).to.have.lengthOf(1) + } + }) + + after(async function () { + await cleanupTests(servers) + }) +}) diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts index 1cdf93aa1..f5bf130d5 100644 --- a/server/tests/api/redundancy/redundancy.ts +++ b/server/tests/api/redundancy/redundancy.ts @@ -5,7 +5,8 @@ import 'mocha' import { VideoDetails } from '../../../../shared/models/videos' import { checkSegmentHash, - checkVideoFilesWereRemoved, cleanupTests, + checkVideoFilesWereRemoved, + cleanupTests, doubleFollow, flushAndRunMultipleServers, getFollowingListPaginationAndSort, @@ -28,11 +29,16 @@ import { import { waitJobs } from '../../../../shared/extra-utils/server/jobs' import * as magnetUtil from 'magnet-uri' -import { updateRedundancy } from '../../../../shared/extra-utils/server/redundancy' +import { + addVideoRedundancy, + listVideoRedundancies, + removeVideoRedundancy, + updateRedundancy +} from '../../../../shared/extra-utils/server/redundancy' import { ActorFollow } from '../../../../shared/models/actors' import { readdir } from 'fs-extra' import { join } from 'path' -import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' +import { VideoRedundancy, VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../../shared/models/redundancy' import { getStats } from '../../../../shared/extra-utils/server/stats' import { ServerStats } from '../../../../shared/models/server/server-stats.model' @@ -40,6 +46,7 @@ const expect = chai.expect let servers: ServerInfo[] = [] let video1Server2UUID: string +let video1Server2Id: number function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) { const parsed = magnetUtil.decode(file.magnetUri) @@ -52,7 +59,19 @@ function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: numbe expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) } -async function flushAndRunServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { +async function flushAndRunServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}) { + const strategies: any[] = [] + + if (strategy !== null) { + strategies.push( + immutableAssign({ + min_lifetime: '1 hour', + strategy: strategy, + size: '400KB' + }, additionalParams) + ) + } + const config = { transcoding: { hls: { @@ -62,16 +81,11 @@ async function flushAndRunServers (strategy: VideoRedundancyStrategy, additional redundancy: { videos: { check_interval: '5 seconds', - strategies: [ - immutableAssign({ - min_lifetime: '1 hour', - strategy: strategy, - size: '400KB' - }, additionalParams) - ] + strategies } } } + servers = await flushAndRunMultipleServers(3, config) // Get the access tokens @@ -80,6 +94,7 @@ async function flushAndRunServers (strategy: VideoRedundancyStrategy, additional { const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' }) video1Server2UUID = res.body.video.uuid + video1Server2Id = res.body.video.id await viewVideo(servers[ 1 ].url, video1Server2UUID) } @@ -216,29 +231,38 @@ async function check1PlaylistRedundancies (videoUUID?: string) { } } -async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { +async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) { + let totalSize: number = null + let statsLength = 1 + + if (strategy !== 'manual') { + totalSize = 409600 + statsLength = 2 + } + const res = await getStats(servers[0].url) const data: ServerStats = res.body - expect(data.videosRedundancy).to.have.lengthOf(1) - const stat = data.videosRedundancy[0] + expect(data.videosRedundancy).to.have.lengthOf(statsLength) + const stat = data.videosRedundancy[0] expect(stat.strategy).to.equal(strategy) - expect(stat.totalSize).to.equal(409600) + expect(stat.totalSize).to.equal(totalSize) + + return stat +} + +async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategyWithManual) { + const stat = await checkStatsGlobal(strategy) + expect(stat.totalUsed).to.be.at.least(1).and.below(409601) expect(stat.totalVideoFiles).to.equal(4) expect(stat.totalVideos).to.equal(1) } -async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { - const res = await getStats(servers[0].url) - const data: ServerStats = res.body - - expect(data.videosRedundancy).to.have.lengthOf(1) +async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategyWithManual) { + const stat = await checkStatsGlobal(strategy) - const stat = data.videosRedundancy[0] - expect(stat.strategy).to.equal(strategy) - expect(stat.totalSize).to.equal(409600) expect(stat.totalUsed).to.equal(0) expect(stat.totalVideoFiles).to.equal(0) expect(stat.totalVideos).to.equal(0) @@ -446,6 +470,74 @@ describe('Test videos redundancy', function () { }) }) + describe('With manual strategy', function () { + before(function () { + this.timeout(120000) + + return flushAndRunServers(null) + }) + + it('Should have 1 webseed on the first video', async function () { + await check1WebSeed() + await check0PlaylistRedundancies() + await checkStatsWith1Webseed('manual') + }) + + it('Should create a redundancy on first video', async function () { + await addVideoRedundancy({ + url: servers[0].url, + accessToken: servers[0].accessToken, + videoId: video1Server2Id + }) + }) + + it('Should have 2 webseeds on the first video', async function () { + this.timeout(80000) + + await waitJobs(servers) + await waitUntilLog(servers[0], 'Duplicated ', 5) + await waitJobs(servers) + + await check2Webseeds() + await check1PlaylistRedundancies() + await checkStatsWith2Webseed('manual') + }) + + it('Should manually remove redundancies on server 1 and remove duplicated videos', async function () { + this.timeout(80000) + + const res = await listVideoRedundancies({ + url: servers[0].url, + accessToken: servers[0].accessToken, + target: 'remote-videos' + }) + + const videos = res.body.data as VideoRedundancy[] + expect(videos).to.have.lengthOf(1) + + const video = videos[0] + for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) { + await removeVideoRedundancy({ + url: servers[0].url, + accessToken: servers[0].accessToken, + redundancyId: r.id + }) + } + + await waitJobs(servers) + await wait(5000) + + await check1WebSeed() + await check0PlaylistRedundancies() + + await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ]) + }) + + after(async function () { + await cleanupTests(servers) + }) + }) + describe('Test expiration', function () { const strategy = 'recently-added' -- cgit v1.2.3