diff --git a/package-lock.json b/package-lock.json index 9d4ebd411..b54863400 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,7 +338,7 @@ }, "amdefine": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, @@ -411,7 +411,7 @@ }, "assert-plus": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { @@ -483,7 +483,7 @@ }, "backoff": { "version": "2.5.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", "requires": { "precond": "0.2" @@ -621,7 +621,7 @@ }, "buffer-equal-constant-time": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-fill": { @@ -636,7 +636,7 @@ }, "bunyan": { "version": "1.8.12", - "resolved": false, + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", "requires": { "dtrace-provider": "~0.8", @@ -762,7 +762,7 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, @@ -807,7 +807,7 @@ }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { @@ -992,7 +992,7 @@ }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cron": { @@ -1034,7 +1034,7 @@ }, "dashdash": { "version": "1.14.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" @@ -1109,7 +1109,7 @@ }, "decamelize": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "deep-eql": { @@ -1376,7 +1376,7 @@ }, "ent": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, "error-ex": { @@ -1480,7 +1480,7 @@ }, "expect.js": { "version": "0.3.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/expect.js/-/expect.js-0.3.1.tgz", "integrity": "sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s=", "dev": true }, @@ -2267,7 +2267,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", @@ -2303,7 +2303,7 @@ }, "is-arrayish": { "version": "0.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, @@ -2385,12 +2385,12 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isstream": { @@ -2526,7 +2526,7 @@ }, "ldap-filter": { "version": "0.2.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/ldap-filter/-/ldap-filter-0.2.2.tgz", "integrity": "sha1-8rhCvguG2jNSeYUFsx68rlkNd9A=", "requires": { "assert-plus": "0.1.5" @@ -2534,7 +2534,7 @@ "dependencies": { "assert-plus": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=" } } @@ -2733,19 +2733,19 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -2876,9 +2876,9 @@ } }, "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz", + "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==" }, "moment-timezone": { "version": "0.5.27", @@ -2943,7 +2943,7 @@ }, "mv": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", "optional": true, "requires": { @@ -2954,7 +2954,7 @@ "dependencies": { "glob": { "version": "6.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", "optional": true, "requires": { @@ -2967,13 +2967,13 @@ }, "ncp": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "optional": true }, "rimraf": { "version": "2.4.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", "optional": true, "requires": { @@ -3261,7 +3261,7 @@ }, "nopt": { "version": "3.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { @@ -3310,7 +3310,7 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, @@ -3470,7 +3470,7 @@ }, "parse-json": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { @@ -3494,7 +3494,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { @@ -3584,7 +3584,7 @@ }, "precond": { "version": "0.2.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" }, "pretty-bytes": { @@ -3652,7 +3652,7 @@ }, "pseudomap": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, @@ -3920,7 +3920,7 @@ }, "require-directory": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { @@ -4194,7 +4194,7 @@ }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "setprototypeof": { @@ -4254,7 +4254,7 @@ }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "smtp-connection": { @@ -4348,7 +4348,7 @@ }, "sprintf-js": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sqlstring": { @@ -4515,7 +4515,7 @@ }, "stubs": { "version": "3.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" }, "superagent": { @@ -4812,7 +4812,7 @@ }, "typedarray": { "version": "0.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "uid-safe": { @@ -4884,7 +4884,7 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utile": { @@ -4939,7 +4939,7 @@ }, "vasync": { "version": "1.6.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz", "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=", "requires": { "verror": "1.6.0" @@ -4947,7 +4947,7 @@ "dependencies": { "verror": { "version": "1.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz", "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=", "requires": { "extsprintf": "1.2.0" @@ -4957,7 +4957,7 @@ }, "verror": { "version": "1.10.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { "assert-plus": "^1.0.0", @@ -4980,7 +4980,7 @@ }, "which-module": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, "wide-align": { @@ -5067,7 +5067,7 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { diff --git a/package.json b/package.json index ce6b90463..f2adea818 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "lodash": "^4.17.15", "lodash.chunk": "^4.2.0", "mime": "^2.4.4", + "moment": "^2.25.3", "moment-timezone": "^0.5.27", "morgan": "^1.9.1", "multiparty": "^4.2.1", diff --git a/src/backups.js b/src/backups.js index 21b70869c..35088c940 100644 --- a/src/backups.js +++ b/src/backups.js @@ -57,6 +57,7 @@ var addons = require('./addons.js'), eventlog = require('./eventlog.js'), fs = require('fs'), locker = require('./locker.js'), + moment = require('moment'), mkdirp = require('mkdirp'), once = require('once'), path = require('path'), @@ -140,7 +141,12 @@ function testConfig(backupConfig, callback) { } const policy = backupConfig.retentionPolicy; + if (!policy) return callback(new BoxError(BoxError.BAD_FIELD, 'retentionPolicy is required', { field: 'retentionPolicy' })); if ('keepWithinSecs' in policy && typeof policy.keepWithinSecs !== 'number') return callback(new BoxError(BoxError.BAD_FIELD, 'keepWithinSecs must be a number', { field: 'retentionPolicy' })); + if ('keepDaily' in policy && typeof policy.keepDaily !== 'number') return callback(new BoxError(BoxError.BAD_FIELD, 'keepDaily must be a number', { field: 'retentionPolicy' })); + if ('keepWeekly' in policy && typeof policy.keepWeekly !== 'number') return callback(new BoxError(BoxError.BAD_FIELD, 'keepWeekly must be a number', { field: 'retentionPolicy' })); + if ('keepMonthly' in policy && typeof policy.keepMonthly !== 'number') return callback(new BoxError(BoxError.BAD_FIELD, 'keepMonthly must be a number', { field: 'retentionPolicy' })); + if ('keepYearly' in policy && typeof policy.keepYearly !== 'number') return callback(new BoxError(BoxError.BAD_FIELD, 'keepYearly must be a number', { field: 'retentionPolicy' })); api(backupConfig.provider).testConfig(backupConfig, callback); } @@ -1225,6 +1231,52 @@ function ensureBackup(auditSource, callback) { }); } +function applyBackupRetentionPolicy(backups, policy) { + assert(Array.isArray(backups)); + assert.strictEqual(typeof policy, 'object'); + + const now = new Date(); + + for (const backup of backups) { + if (backup.keepReason) continue; // already kept for some other reason + + if ((now - backup.creationTime) < (backup.preserveSecs * 1000)) { + backup.keepReason = 'preserveSecs'; + } else if ((now - backup.creationTime) < (policy.keepWithinSecs * 1000)) { + backup.keepReason = 'withinSecs'; + } + } + + const KEEP_FORMATS = { + keepDaily: 'Y-M-D', + keepWeekly: 'Y-W', + keepMonthly: 'Y-M', + keepYearly: 'Y' + }; + + for (const format of [ 'keepDaily', 'keepWeekly', 'keepMonthly', 'keepYearly' ]) { + if (!(format in policy)) continue; + + const n = policy[format]; // we want to keep "n" backups of format + if (!n) continue; // disabled rule + + let lastPeriod = null, keptSoFar = 0; + for (const backup of backups) { + if (backup.keepReason) continue; // already kept for some other reason + const period = moment(backup.creationTime).format(KEEP_FORMATS[format]); + if (period === lastPeriod) continue; // already kept for this period + + lastPeriod = period; + backup.keepReason = format; + if (++keptSoFar === n) break; + } + } + + for (const backup of backups) { + if (backup.keepReason) debug(`applyBackupRetentionPolicy: ${backup.id} ${backup.type} ${backup.keepReason}`); + } +} + function cleanupBackup(backupConfig, backup, callback) { assert.strictEqual(typeof backupConfig, 'object'); assert.strictEqual(typeof backup, 'object'); @@ -1260,31 +1312,34 @@ function cleanupBackup(backupConfig, backup, callback) { } } -function cleanupAppBackups(backupConfig, referencedAppBackups, callback) { +function cleanupAppBackups(backupConfig, referencedAppBackupIds, callback) { assert.strictEqual(typeof backupConfig, 'object'); - assert(Array.isArray(referencedAppBackups)); + assert(Array.isArray(referencedAppBackupIds)); assert.strictEqual(typeof callback, 'function'); - const now = new Date(); - let removedAppBackups = []; + let removedAppBackupIds = []; // we clean app backups of any state because the ones to keep are determined by the box cleanup code backupdb.getByTypePaged(backupdb.BACKUP_TYPE_APP, 1, 1000, function (error, appBackups) { if (error) return callback(error); + for (const appBackup of appBackups) { // set the reason so that policy filter can skip it + if (referencedAppBackupIds.includes(appBackup.id)) appBackup.keepReason = 'reference'; + } + + applyBackupRetentionPolicy(appBackups, backupConfig.retentionPolicy); + async.eachSeries(appBackups, function iterator(appBackup, iteratorDone) { - if (referencedAppBackups.indexOf(appBackup.id) !== -1) return iteratorDone(); - if ((now - appBackup.creationTime) < (appBackup.preserveSecs * 1000)) return iteratorDone(); - if ((now - appBackup.creationTime) < (backupConfig.retentionPolicy.keepWithinSecs * 1000)) return iteratorDone(); + if (appBackup.keepReason) return iteratorDone(); debug('cleanupAppBackups: removing %s', appBackup.id); - removedAppBackups.push(appBackup.id); + removedAppBackupIds.push(appBackup.id); cleanupBackup(backupConfig, appBackup, iteratorDone); }, function () { debug('cleanupAppBackups: done'); - callback(null, removedAppBackups); + callback(null, removedAppBackupIds); }); }); } @@ -1294,13 +1349,12 @@ function cleanupBoxBackups(backupConfig, auditSource, callback) { assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - const now = new Date(); - let referencedAppBackups = [], removedBoxBackups = []; + let referencedAppBackupIds = [], removedBoxBackupIds = []; backupdb.getByTypePaged(backupdb.BACKUP_TYPE_BOX, 1, 1000, function (error, boxBackups) { if (error) return callback(error); - if (boxBackups.length === 0) return callback(null, { removedBoxBackups, referencedAppBackups }); + if (boxBackups.length === 0) return callback(null, { removedBoxBackupIds, referencedAppBackupIds }); // search for the first valid backup var i; @@ -1311,28 +1365,28 @@ function cleanupBoxBackups(backupConfig, auditSource, callback) { // keep the first valid backup if (i !== boxBackups.length) { debug('cleanupBoxBackups: preserving box backup %s (%j)', boxBackups[i].id, boxBackups[i].dependsOn); - referencedAppBackups = boxBackups[i].dependsOn; + referencedAppBackupIds = boxBackups[i].dependsOn; boxBackups.splice(i, 1); } else { debug('cleanupBoxBackups: no box backup to preserve'); } + applyBackupRetentionPolicy(boxBackups, backupConfig.retentionPolicy); + async.eachSeries(boxBackups, function iterator(boxBackup, iteratorNext) { - // TODO: errored backups should probably be cleaned up before retention time, but we will - // have to be careful not to remove any backup currently being created - if ((now - boxBackup.creationTime) < (backupConfig.retentionPolicy.keepWithinSecs * 1000)) { - referencedAppBackups = referencedAppBackups.concat(boxBackup.dependsOn); + if (boxBackup.keepReason) { + referencedAppBackupIds = referencedAppBackupIds.concat(boxBackup.dependsOn); return iteratorNext(); } debug('cleanupBoxBackups: removing %s', boxBackup.id); - removedBoxBackups.push(boxBackup.id); + removedBoxBackupIds.push(boxBackup.id); cleanupBackup(backupConfig, boxBackup, iteratorNext); }, function () { debug('cleanupBoxBackups: done'); - callback(null, { removedBoxBackups, referencedAppBackups }); + callback(null, { removedBoxBackupIds, referencedAppBackupIds }); }); }); } @@ -1401,12 +1455,12 @@ function cleanup(auditSource, progressCallback, callback) { progressCallback({ percent: 10, message: 'Cleaning box backups' }); - cleanupBoxBackups(backupConfig, auditSource, function (error, result) { + cleanupBoxBackups(backupConfig, auditSource, function (error, { removedBoxBackupIds, referencedAppBackupIds }) { if (error) return callback(error); progressCallback({ percent: 40, message: 'Cleaning app backups' }); - cleanupAppBackups(backupConfig, result.referencedAppBackups, function (error, removedAppBackups) { + cleanupAppBackups(backupConfig, referencedAppBackupIds, function (error, removedAppBackupIds) { if (error) return callback(error); progressCallback({ percent: 90, message: 'Cleaning snapshots' }); @@ -1414,7 +1468,7 @@ function cleanup(auditSource, progressCallback, callback) { cleanupSnapshots(backupConfig, function (error) { if (error) return callback(error); - callback(null, { removedBoxBackups: result.removedBoxBackups, removedAppBackups: removedAppBackups }); + callback(null, { removedBoxBackupIds, removedAppBackupIds }); }); }); }); diff --git a/src/routes/settings.js b/src/routes/settings.js index 30ddbd1b1..64369fd79 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -107,7 +107,7 @@ function setBackupConfig(req, res, next) { if ('acceptSelfSignedCerts' in req.body && typeof req.body.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean')); if (!req.body.retentionPolicy || typeof req.body.retentionPolicy !== 'object') return next(new HttpError(400, 'retentionPolicy is required')); - if (typeof req.body.retentionPolicy.keepWithin !== 'number') return next(400, 'keepWithin is required'); + if (typeof req.body.retentionPolicy.keepWithinSecs !== 'number') return next(400, 'keepWithinSecs is required'); // testing the backup using put/del takes a bit of time at times req.clearTimeout(); diff --git a/src/routes/test/backups-test.js b/src/routes/test/backups-test.js index d0eb45a4a..4856aef3b 100644 --- a/src/routes/test/backups-test.js +++ b/src/routes/test/backups-test.js @@ -62,7 +62,7 @@ function setup(done) { }, function createSettings(callback) { - settings.setBackupConfig({ provider: 'filesystem', backupFolder: '/tmp', format: 'tgz' }, callback); + settings.setBackupConfig({ provider: 'filesystem', backupFolder: '/tmp', format: 'tgz', retentionPolicy: {} }, callback); } ], done); } diff --git a/src/settings.js b/src/settings.js index 1b034a613..08a6ae67e 100644 --- a/src/settings.js +++ b/src/settings.js @@ -145,7 +145,7 @@ let gDefaults = (function () { backupFolder: '/var/backups', format: 'tgz', encryption: null, - retentionPolicy: { keepWithin: 2 * 24 * 60 * 60 }, // 2 days + retentionPolicy: { keepWithinSecs: 2 * 24 * 60 * 60 }, // 2 days intervalSecs: 24 * 60 * 60 // ~1 day }; result[exports.PLATFORM_CONFIG_KEY] = {}; diff --git a/src/test/backups-test.js b/src/test/backups-test.js index e11566e87..c4fae1658 100644 --- a/src/test/backups-test.js +++ b/src/test/backups-test.js @@ -80,7 +80,7 @@ describe('backups', function () { provider: 'filesystem', password: 'supersecret', backupFolder: BACKUP_DIR, - retentionPolicy: { keepWithin: 1 }, + retentionPolicy: { keepWithinSecs: 1 }, format: 'tgz' }) ], done); @@ -277,25 +277,26 @@ describe('backups', function () { describe('filesystem', function () { var backupInfo1; - var gBackupConfig = { + var backupConfig = { provider: 'filesystem', backupFolder: path.join(os.tmpdir(), 'backups-test-filesystem'), - format: 'tgz' + format: 'tgz', + retentionPolicy: { keepWithinSecs: 10000 } }; before(function (done) { - rimraf.sync(gBackupConfig.backupFolder); + rimraf.sync(backupConfig.backupFolder); done(); }); after(function (done) { - rimraf.sync(gBackupConfig.backupFolder); + rimraf.sync(backupConfig.backupFolder); done(); }); it('fails to set backup config for non-existing folder', function (done) { - settings.setBackupConfig(gBackupConfig, function (error) { + settings.setBackupConfig(backupConfig, function (error) { expect(error).to.be.a(BoxError); expect(error.reason).to.equal(BoxError.BAD_FIELD); @@ -304,9 +305,9 @@ describe('backups', function () { }); it('succeeds to set backup config', function (done) { - mkdirp.sync(gBackupConfig.backupFolder); + mkdirp.sync(backupConfig.backupFolder); - settings.setBackupConfig(gBackupConfig, function (error) { + settings.setBackupConfig(backupConfig, function (error) { expect(error).to.be(null); done(); @@ -318,10 +319,9 @@ describe('backups', function () { if (require('child_process').execSync('/usr/bin/mysqldump --version').toString().indexOf('MariaDB') !== -1) return done(); createBackup(function (error, result) { -console.dir(error); expect(error).to.be(null); - expect(fs.statSync(path.join(gBackupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup - expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); + expect(fs.statSync(path.join(backupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup + expect(fs.statSync(path.join(backupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); backupInfo1 = result; @@ -335,9 +335,9 @@ console.dir(error); createBackup(function (error, result) { expect(error).to.be(null); - expect(fs.statSync(path.join(gBackupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup - expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); // hard linked to new backup - expect(fs.statSync(path.join(gBackupConfig.backupFolder, `${backupInfo1.id}.tar.gz`)).nlink).to.be(1); // not hard linked anymore + expect(fs.statSync(path.join(backupConfig.backupFolder, 'snapshot/box.tar.gz')).nlink).to.be(2); // hard linked to a rotated backup + expect(fs.statSync(path.join(backupConfig.backupFolder, `${result.id}.tar.gz`)).nlink).to.be(2); // hard linked to new backup + expect(fs.statSync(path.join(backupConfig.backupFolder, `${backupInfo1.id}.tar.gz`)).nlink).to.be(1); // not hard linked anymore done(); });