diff --git a/src/backuptargets.js b/src/backuptargets.js index 8b2c3d462..1977f4dd3 100644 --- a/src/backuptargets.js +++ b/src/backuptargets.js @@ -23,7 +23,7 @@ exports = module.exports = { setSnapshotInfo, remount, - getMountStatus, + getStatus, ensureMounted, storageApi, @@ -44,7 +44,6 @@ const assert = require('assert'), eventlog = require('./eventlog.js'), hush = require('./hush.js'), locks = require('./locks.js'), - mounts = require('./mounts.js'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), @@ -389,31 +388,20 @@ async function remount(target) { await storageApi(target).setup(target.config); } -async function getMountStatus(target) { +async function getStatus(target) { assert.strictEqual(typeof target, 'object'); - let hostPath; - if (mounts.isManagedProvider(target.provider)) { - hostPath = paths.MANAGED_BACKUP_MOUNT_DIR; - } else if (target.provider === 'mountpoint') { - hostPath = target.config.mountPoint; - } else if (target.provider === 'filesystem') { - hostPath = target.config.backupDir; - } else { - return { state: 'active' }; - } - - return await mounts.getStatus(target.provider, hostPath); // { state, message } + return await storageApi(target).getStatus(target.config); } async function ensureMounted(target) { assert.strictEqual(typeof target, 'object'); - const status = await getMountStatus(target); + const status = await getStatus(target); if (status.state === 'active') return status; await remount(); - return await getMountStatus(target); + return await getStatus(target); } async function setConfig(backupTarget, newConfig, auditSource) { diff --git a/src/routes/backuptargets.js b/src/routes/backuptargets.js index 6d4b0fad0..677e23527 100644 --- a/src/routes/backuptargets.js +++ b/src/routes/backuptargets.js @@ -18,7 +18,7 @@ exports = module.exports = { createBackup, cleanup, remount, - getMountStatus, + getStatus, }; const assert = require('assert'), @@ -207,10 +207,10 @@ async function remount(req, res, next) { next(new HttpSuccess(202, {})); } -async function getMountStatus(req, res, next) { +async function getStatus(req, res, next) { assert.strictEqual(typeof req.resources.backupTarget, 'object'); - const [error, mountStatus] = await safe(backupTargets.getMountStatus(req.resources.backupTarget)); + const [error, mountStatus] = await safe(backupTargets.getStatus(req.resources.backupTarget)); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, mountStatus)); } diff --git a/src/routes/test/backuptargets-test.js b/src/routes/test/backuptargets-test.js index cf51aaba8..02cea88d3 100644 --- a/src/routes/test/backuptargets-test.js +++ b/src/routes/test/backuptargets-test.js @@ -293,7 +293,7 @@ describe('Backups API', function () { describe('mounting', function () { it('mount status', async function () { - const response = await superagent.get(`${serverUrl}/api/v1/backup_targets/${newTarget.id}/mount_status`) + const response = await superagent.get(`${serverUrl}/api/v1/backup_targets/${newTarget.id}/status`) .query({ access_token: owner.token }); expect(response.status).to.equal(200); expect(response.body.state).to.be('active'); diff --git a/src/server.js b/src/server.js index 4840ce2c8..71cead55e 100644 --- a/src/server.js +++ b/src/server.js @@ -161,7 +161,7 @@ async function initializeExpressSync() { router.get ('/api/v1/backup_targets/:id', token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.get); router.post('/api/v1/backup_targets', json, token, authorizeOwner, routes.backupTargets.add); router.del ('/api/v1/backup_targets/:id', token, authorizeOwner, routes.backupTargets.load, routes.backupTargets.del); - router.get ('/api/v1/backup_targets/:id/mount_status', token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.getMountStatus); + router.get ('/api/v1/backup_targets/:id/status', token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.getStatus); router.post('/api/v1/backup_targets/:id/create_backup', token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.createBackup); router.post('/api/v1/backup_targets/:id/cleanup', json, token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.cleanup); router.post('/api/v1/backup_targets/:id/remount', json, token, authorizeAdmin, routes.backupTargets.load, routes.backupTargets.remount); diff --git a/src/storage/filesystem.js b/src/storage/filesystem.js index faf8b982b..c2643cf9c 100644 --- a/src/storage/filesystem.js +++ b/src/storage/filesystem.js @@ -10,6 +10,7 @@ exports = module.exports = { injectPrivateFields, getAvailableSize, + getStatus, upload, download, @@ -63,6 +64,21 @@ async function getAvailableSize(config) { return dfResult.available; } +async function getStatus(config) { + assert.strictEqual(typeof config, 'object'); + + let hostPath; + if (mounts.isManagedProvider(config._provider)) { + hostPath = config._managedMountPath; + } else if (config._provider === 'mountpoint') { + hostPath = config.mountPoint; + } else if (config._provider === 'filesystem') { + hostPath = config.backupDir; + } + + return await mounts.getStatus(config._provider, hostPath); // { state, message } +} + function hasChownSupportSync(config) { switch (config._provider) { case mounts.MOUNT_TYPE_NFS: diff --git a/src/storage/gcs.js b/src/storage/gcs.js index 462be9b6c..8ee49ed1d 100644 --- a/src/storage/gcs.js +++ b/src/storage/gcs.js @@ -2,6 +2,7 @@ exports = module.exports = { getAvailableSize, + getStatus, upload, exists, @@ -54,6 +55,16 @@ async function getAvailableSize(apiConfig) { return Number.POSITIVE_INFINITY; } +async function getStatus(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + const bucket = getBucket(apiConfig); + const query = { prefix: path.join(apiConfig.prefix, 'snapshot'), autoPaginate: false, maxResults: 1 }; + const [listError] = await safe(bucket.getFiles(query)); + if (listError) return { state: 'inactive', message: `Failed to get files: ${listError.message}` }; + return { state: 'active' }; +} + async function upload(apiConfig, remotePath) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof remotePath, 'string'); @@ -242,6 +253,10 @@ async function verifyConfig({ id, provider, config }) { }); debug('testConfig: uploaded cloudron-testfile'); + const query = { prefix: path.join(config.prefix, 'snapshot'), autoPaginate: false, maxResults: 1 }; + const [listError] = await safe(bucket.getFiles(query)); + if (listError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Failed to get files: ${listError.message}`); + const [delError] = await safe(bucket.file(path.join(config.prefix, 'cloudron-testfile')).delete()); if (delError) throw new BoxError(BoxError.EXTERNAL_ERROR, delError.message); debug('testConfig: deleted cloudron-testfile'); diff --git a/src/storage/interface.js b/src/storage/interface.js index 83de18a2a..1c68d8309 100644 --- a/src/storage/interface.js +++ b/src/storage/interface.js @@ -12,6 +12,7 @@ // them to tune the concurrency based on failures/rate limits accordingly exports = module.exports = { getAvailableSize, + getStatus, upload, @@ -53,6 +54,13 @@ async function getAvailableSize(apiConfig) { return Number.POSITIVE_INFINITY; } +async function getStatus(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + // Result: { state, message } . state is 'active' or 'inactive' + throw new BoxError(BoxError.NOT_IMPLEMENTED, 'getStatus is not implemented'); +} + async function upload(apiConfig, backupFilePath) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof backupFilePath, 'string'); diff --git a/src/storage/noop.js b/src/storage/noop.js index b3f278075..1d9458711 100644 --- a/src/storage/noop.js +++ b/src/storage/noop.js @@ -2,6 +2,7 @@ exports = module.exports = { getAvailableSize, + getStatus, upload, exists, @@ -33,6 +34,12 @@ async function getAvailableSize(apiConfig) { return Number.POSITIVE_INFINITY; } +async function getStatus(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + return { state: 'active' }; +} + async function upload(apiConfig, remotePath) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof remotePath, 'string'); diff --git a/src/storage/s3.js b/src/storage/s3.js index f15bdb960..ceb40d95a 100644 --- a/src/storage/s3.js +++ b/src/storage/s3.js @@ -10,6 +10,7 @@ exports = module.exports = { injectPrivateFields, getAvailableSize, + getStatus, upload, exists, @@ -156,6 +157,23 @@ async function getAvailableSize(apiConfig) { return Number.POSITIVE_INFINITY; } +async function getStatus(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + const s3 = createS3Client(apiConfig, { retryStrategy: RETRY_STRATEGY }); + + const listParams = { + Bucket: apiConfig.bucket, + Prefix: path.join(apiConfig.prefix, 'snapshot'), + MaxKeys: 1 + }; + + const [listError] = await safe(s3.listObjectsV2(listParams)); + if (listError) return { status: 'inactive', message: `Error listing objects. ${formatError(listError)}` }; + + return { state: 'active' }; +} + async function upload(apiConfig, remotePath) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof remotePath, 'string');