Correctly cleanup files from object storage

This commit is contained in:
Chocobozzz 2022-10-25 11:51:20 +02:00
parent 849f0fd3b2
commit 508c1b1e9f
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
4 changed files with 42 additions and 19 deletions

View File

@ -1,3 +1,4 @@
import { map } from 'bluebird'
import { createReadStream, createWriteStream, ensureDir, ReadStream } from 'fs-extra' import { createReadStream, createWriteStream, ensureDir, ReadStream } from 'fs-extra'
import { dirname } from 'path' import { dirname } from 'path'
import { Readable } from 'stream' import { Readable } from 'stream'
@ -93,6 +94,8 @@ function updatePrefixACL (options: {
prefix, prefix,
bucketInfo, bucketInfo,
commandBuilder: obj => { commandBuilder: obj => {
logger.debug('Updating ACL of %s inside prefix %s in bucket %s', obj.Key, prefix, bucketInfo.BUCKET_NAME, lTags())
return new PutObjectAclCommand({ return new PutObjectAclCommand({
Bucket: bucketInfo.BUCKET_NAME, Bucket: bucketInfo.BUCKET_NAME,
Key: obj.Key, Key: obj.Key,
@ -117,7 +120,7 @@ function removeObject (objectStorageKey: string, bucketInfo: BucketInfo) {
return getClient().send(command) return getClient().send(command)
} }
function removePrefix (prefix: string, bucketInfo: BucketInfo) { async function removePrefix (prefix: string, bucketInfo: BucketInfo) {
// FIXME: use bulk delete when s3ninja will support this operation // FIXME: use bulk delete when s3ninja will support this operation
logger.debug('Removing prefix %s in bucket %s', prefix, bucketInfo.BUCKET_NAME, lTags()) logger.debug('Removing prefix %s in bucket %s', prefix, bucketInfo.BUCKET_NAME, lTags())
@ -126,6 +129,8 @@ function removePrefix (prefix: string, bucketInfo: BucketInfo) {
prefix, prefix,
bucketInfo, bucketInfo,
commandBuilder: obj => { commandBuilder: obj => {
logger.debug('Removing %s inside prefix %s in bucket %s', obj.Key, prefix, bucketInfo.BUCKET_NAME, lTags())
return new DeleteObjectCommand({ return new DeleteObjectCommand({
Bucket: bucketInfo.BUCKET_NAME, Bucket: bucketInfo.BUCKET_NAME,
Key: obj.Key Key: obj.Key
@ -259,7 +264,7 @@ async function applyOnPrefix (options: {
const s3Client = getClient() const s3Client = getClient()
const commandPrefix = bucketInfo.PREFIX + prefix const commandPrefix = buildKey(prefix, bucketInfo)
const listCommand = new ListObjectsV2Command({ const listCommand = new ListObjectsV2Command({
Bucket: bucketInfo.BUCKET_NAME, Bucket: bucketInfo.BUCKET_NAME,
Prefix: commandPrefix, Prefix: commandPrefix,
@ -275,11 +280,11 @@ async function applyOnPrefix (options: {
throw new Error(message) throw new Error(message)
} }
for (const object of listedObjects.Contents) { await map(listedObjects.Contents, object => {
const command = commandBuilder(object) const command = commandBuilder(object)
await s3Client.send(command) return s3Client.send(command)
} }, { concurrency: 10 })
// Repeat if not all objects could be listed at once (limit of 1000?) // Repeat if not all objects could be listed at once (limit of 1000?)
if (listedObjects.IsTruncated) { if (listedObjects.IsTruncated) {

View File

@ -785,9 +785,8 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
// Do not wait video deletion because we could be in a transaction // Do not wait video deletion because we could be in a transaction
Promise.all(tasks) Promise.all(tasks)
.catch(err => { .then(() => logger.info('Removed files of video %s.', instance.url))
logger.error('Some errors when removing files of video %s in before destroy hook.', instance.uuid, { err }) .catch(err => logger.error('Some errors when removing files of video %s in before destroy hook.', instance.uuid, { err }))
})
return undefined return undefined
} }

View File

@ -192,16 +192,6 @@ describe('Object storage for video static file privacy', function () {
await checkPublicFiles(publicVideoUUID) await checkPublicFiles(publicVideoUUID)
}) })
after(async function () {
this.timeout(30000)
if (privateVideoUUID) await server.videos.remove({ id: privateVideoUUID })
if (publicVideoUUID) await server.videos.remove({ id: publicVideoUUID })
if (userPrivateVideoUUID) await server.videos.remove({ id: userPrivateVideoUUID })
await waitJobs([ server ])
})
}) })
describe('Live', function () { describe('Live', function () {
@ -331,6 +321,18 @@ describe('Object storage for video static file privacy', function () {
}) })
after(async function () { after(async function () {
this.timeout(60000)
const { data } = await server.videos.listAllForAdmin()
for (const v of data) {
await server.videos.remove({ id: v.uuid })
}
for (const v of data) {
await server.servers.waitUntilLog('Removed files of video ' + v.url, 1, true)
}
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })
}) })

View File

@ -4,7 +4,7 @@ import { expect } from 'chai'
import { createReadStream, stat } from 'fs-extra' import { createReadStream, stat } from 'fs-extra'
import got, { Response as GotResponse } from 'got' import got, { Response as GotResponse } from 'got'
import validator from 'validator' import validator from 'validator'
import { buildAbsoluteFixturePath, omit, pick, wait } from '@shared/core-utils' import { buildAbsoluteFixturePath, getAllPrivacies, omit, pick, wait } from '@shared/core-utils'
import { buildUUID } from '@shared/extra-utils' import { buildUUID } from '@shared/extra-utils'
import { import {
HttpStatusCode, HttpStatusCode,
@ -15,6 +15,7 @@ import {
VideoCreateResult, VideoCreateResult,
VideoDetails, VideoDetails,
VideoFileMetadata, VideoFileMetadata,
VideoInclude,
VideoPrivacy, VideoPrivacy,
VideosCommonQuery, VideosCommonQuery,
VideoTranscodingCreate VideoTranscodingCreate
@ -234,6 +235,22 @@ export class VideosCommand extends AbstractCommand {
}) })
} }
listAllForAdmin (options: OverrideCommandOptions & VideosCommonQuery = {}) {
const include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER
const nsfw = 'both'
const privacyOneOf = getAllPrivacies()
return this.list({
...options,
include,
nsfw,
privacyOneOf,
token: this.buildCommonRequestToken({ ...options, implicitToken: true })
})
}
listByAccount (options: OverrideCommandOptions & VideosCommonQuery & { listByAccount (options: OverrideCommandOptions & VideosCommonQuery & {
handle: string handle: string
}) { }) {