2017-04-24 12:34:57 +02:00
|
|
|
/* jslint node:true */
|
|
|
|
|
/* global it:false */
|
|
|
|
|
/* global describe:false */
|
|
|
|
|
/* global before:false */
|
|
|
|
|
/* global after:false */
|
|
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
var async = require('async'),
|
|
|
|
|
backupdb = require('../backupdb.js'),
|
|
|
|
|
backups = require('../backups.js'),
|
2017-09-29 14:16:11 -07:00
|
|
|
createTree = require('./common.js').createTree,
|
2017-04-24 12:34:57 +02:00
|
|
|
database = require('../database'),
|
|
|
|
|
DatabaseError = require('../databaseerror.js'),
|
|
|
|
|
expect = require('expect.js'),
|
2017-09-29 14:16:11 -07:00
|
|
|
fs = require('fs'),
|
|
|
|
|
os = require('os'),
|
2017-10-02 20:08:00 -07:00
|
|
|
mkdirp = require('mkdirp'),
|
2017-09-29 14:16:11 -07:00
|
|
|
path = require('path'),
|
|
|
|
|
rimraf = require('rimraf'),
|
2017-10-02 20:08:00 -07:00
|
|
|
settings = require('../settings.js'),
|
2018-11-16 11:13:03 -08:00
|
|
|
SettingsError = require('../settings.js').SettingsError,
|
|
|
|
|
tasks = require('../tasks.js');
|
2017-10-02 20:08:00 -07:00
|
|
|
|
|
|
|
|
function createBackup(callback) {
|
2018-12-09 03:20:00 -08:00
|
|
|
backups.startBackupTask({ username: 'test' }, function (error, taskId) { // this call does not wait for the backup!
|
2017-10-02 20:08:00 -07:00
|
|
|
if (error) return callback(error);
|
|
|
|
|
|
|
|
|
|
function waitForBackup() {
|
2018-12-08 18:50:06 -08:00
|
|
|
tasks.get(taskId, function (error, p) {
|
2018-11-16 11:13:03 -08:00
|
|
|
if (error) return callback(error);
|
2017-10-02 20:08:00 -07:00
|
|
|
|
2018-11-16 11:13:03 -08:00
|
|
|
if (p.percent !== 100) return setTimeout(waitForBackup, 1000);
|
2017-10-02 20:08:00 -07:00
|
|
|
|
2018-11-29 15:16:31 -08:00
|
|
|
if (p.errorMessage) return callback(new Error('backup failed:' + p));
|
|
|
|
|
if (!p.result) return callback(new Error('backup has no result:' + p));
|
2017-10-02 20:08:00 -07:00
|
|
|
|
2018-11-16 11:13:03 -08:00
|
|
|
backups.getByStatePaged(backupdb.BACKUP_STATE_NORMAL, 1, 1, function (error, result) {
|
|
|
|
|
if (error) return callback(error);
|
|
|
|
|
if (result.length !== 1) return callback(new Error('result is not of length 1'));
|
|
|
|
|
|
|
|
|
|
callback(null, result[0]);
|
|
|
|
|
});
|
2017-10-02 20:08:00 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setTimeout(waitForBackup, 1000);
|
|
|
|
|
});
|
|
|
|
|
}
|
2017-04-24 12:34:57 +02:00
|
|
|
|
2017-04-24 13:41:23 +02:00
|
|
|
describe('backups', function () {
|
2017-04-24 12:34:57 +02:00
|
|
|
before(function (done) {
|
2017-11-15 02:29:58 +01:00
|
|
|
const BACKUP_DIR = path.join(os.tmpdir(), 'cloudron-backup-test');
|
|
|
|
|
|
2017-04-24 12:34:57 +02:00
|
|
|
async.series([
|
2017-11-15 02:29:58 +01:00
|
|
|
mkdirp.bind(null, BACKUP_DIR),
|
2017-04-24 12:34:57 +02:00
|
|
|
database.initialize,
|
|
|
|
|
database._clear,
|
|
|
|
|
settings.setBackupConfig.bind(null, {
|
|
|
|
|
provider: 'filesystem',
|
|
|
|
|
key: 'enckey',
|
2017-11-15 02:29:58 +01:00
|
|
|
backupFolder: BACKUP_DIR,
|
2017-09-27 10:25:36 -07:00
|
|
|
retentionSecs: 1,
|
|
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
})
|
|
|
|
|
], done);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
after(function (done) {
|
|
|
|
|
async.series([
|
|
|
|
|
database._clear
|
|
|
|
|
], done);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('cleanup', function () {
|
|
|
|
|
var BACKUP_0 = {
|
|
|
|
|
id: 'backup-box-0',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_BOX,
|
|
|
|
|
dependsOn: [ 'backup-app-00', 'backup-app-01' ],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var BACKUP_0_APP_0 = {
|
|
|
|
|
id: 'backup-app-00',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_APP,
|
|
|
|
|
dependsOn: [],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var BACKUP_0_APP_1 = {
|
|
|
|
|
id: 'backup-app-01',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_APP,
|
|
|
|
|
dependsOn: [],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var BACKUP_1 = {
|
|
|
|
|
id: 'backup-box-1',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_BOX,
|
|
|
|
|
dependsOn: [ 'backup-app-10', 'backup-app-11' ],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var BACKUP_1_APP_0 = {
|
|
|
|
|
id: 'backup-app-10',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_APP,
|
|
|
|
|
dependsOn: [],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var BACKUP_1_APP_1 = {
|
|
|
|
|
id: 'backup-app-11',
|
|
|
|
|
version: '1.0.0',
|
|
|
|
|
type: backupdb.BACKUP_TYPE_APP,
|
|
|
|
|
dependsOn: [],
|
2017-11-16 11:22:09 -08:00
|
|
|
manifest: null,
|
2017-09-27 17:34:49 -07:00
|
|
|
format: 'tgz'
|
2017-04-24 12:34:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
it('succeeds without backups', function (done) {
|
2017-10-02 18:29:16 -07:00
|
|
|
backups.cleanup({ username: 'test' }, done);
|
2017-04-24 12:34:57 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('succeeds with box backups, keeps latest', function (done) {
|
|
|
|
|
async.eachSeries([[ BACKUP_0, BACKUP_0_APP_0, BACKUP_0_APP_1 ], [ BACKUP_1, BACKUP_1_APP_0, BACKUP_1_APP_1 ]], function (backup, callback) {
|
|
|
|
|
// space out backups
|
|
|
|
|
setTimeout(function () {
|
|
|
|
|
async.each(backup, backupdb.add, callback);
|
|
|
|
|
}, 2000);
|
|
|
|
|
}, function (error) {
|
|
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
2017-10-02 18:29:16 -07:00
|
|
|
backups.cleanup({ username: 'test' }, function (error) {
|
2017-04-24 12:34:57 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
2017-05-30 14:09:55 -07:00
|
|
|
backupdb.getByTypePaged(backupdb.BACKUP_TYPE_BOX, 1, 1000, function (error, result) {
|
2017-04-24 12:34:57 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
expect(result.length).to.equal(1);
|
|
|
|
|
expect(result[0].id).to.equal(BACKUP_1.id);
|
|
|
|
|
|
|
|
|
|
// check that app backups are gone as well
|
|
|
|
|
backupdb.get(BACKUP_0_APP_0.id, function (error) {
|
|
|
|
|
expect(error).to.be.a(DatabaseError);
|
|
|
|
|
expect(error.reason).to.equal(DatabaseError.NOT_FOUND);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not remove expired backups if only one left', function (done) {
|
2017-10-02 18:29:16 -07:00
|
|
|
backups.cleanup({ username: 'test' }, function (error) {
|
2017-04-24 12:34:57 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
2017-05-30 14:09:55 -07:00
|
|
|
backupdb.getByTypePaged(backupdb.BACKUP_TYPE_BOX, 1, 1000, function (error, result) {
|
2017-04-24 12:34:57 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
expect(result.length).to.equal(1);
|
|
|
|
|
expect(result[0].id).to.equal(BACKUP_1.id);
|
|
|
|
|
|
2017-04-24 13:41:23 +02:00
|
|
|
// check that app backups are also still there
|
|
|
|
|
backupdb.get(BACKUP_1_APP_0.id, function (error, result) {
|
|
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
expect(result.id).to.equal(BACKUP_1_APP_0.id);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
2017-04-24 12:34:57 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-04-24 13:41:23 +02:00
|
|
|
|
|
|
|
|
it('succeeds for app backups not referenced by a box backup', function (done) {
|
|
|
|
|
async.eachSeries([BACKUP_0_APP_0, BACKUP_0_APP_1], backupdb.add, function (error) {
|
|
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
|
|
|
|
// wait for expiration
|
|
|
|
|
setTimeout(function () {
|
2017-10-02 18:29:16 -07:00
|
|
|
backups.cleanup({ username: 'test' }, function (error) {
|
2017-04-24 13:41:23 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
2017-05-30 14:09:55 -07:00
|
|
|
backupdb.getByTypePaged(backupdb.BACKUP_TYPE_APP, 1, 1000, function (error, result) {
|
2017-04-24 13:41:23 +02:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
expect(result.length).to.equal(2);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}, 2000);
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-04-24 12:34:57 +02:00
|
|
|
});
|
2017-09-29 14:16:11 -07:00
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
describe('fs meta data', function () {
|
2017-09-29 14:16:11 -07:00
|
|
|
var tmpdir;
|
|
|
|
|
before(function () {
|
|
|
|
|
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), 'backups-test'));
|
|
|
|
|
});
|
|
|
|
|
after(function () {
|
|
|
|
|
rimraf.sync(tmpdir);
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
it('saves special files', function (done) {
|
2017-09-29 14:16:11 -07:00
|
|
|
createTree(tmpdir, { 'data': { 'subdir': { 'emptydir': { } } }, 'dir2': { 'file': 'stuff' } });
|
2017-10-12 16:02:09 -07:00
|
|
|
fs.chmodSync(path.join(tmpdir, 'dir2/file'), parseInt('0755', 8));
|
2017-09-29 14:16:11 -07:00
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
backups._saveFsMetadata(tmpdir, function (error) {
|
2017-09-29 14:16:11 -07:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
var emptyDirs = JSON.parse(fs.readFileSync(path.join(tmpdir, 'fsmetadata.json'), 'utf8')).emptyDirs;
|
2017-09-29 14:16:11 -07:00
|
|
|
expect(emptyDirs).to.eql(['./data/subdir/emptydir']);
|
|
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
var execFiles = JSON.parse(fs.readFileSync(path.join(tmpdir, 'fsmetadata.json'), 'utf8')).execFiles;
|
|
|
|
|
expect(execFiles).to.eql(['./dir2/file']);
|
|
|
|
|
|
2017-09-29 14:16:11 -07:00
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
it('restores special files', function (done) {
|
2017-09-29 14:16:11 -07:00
|
|
|
rimraf.sync(path.join(tmpdir, 'data'));
|
|
|
|
|
|
|
|
|
|
expect(fs.existsSync(path.join(tmpdir, 'data/subdir/emptydir'))).to.be(false); // just make sure rimraf worked
|
|
|
|
|
|
2017-10-12 16:02:09 -07:00
|
|
|
backups._restoreFsMetadata(tmpdir, function (error) {
|
2017-09-29 14:16:11 -07:00
|
|
|
expect(error).to.not.be.ok();
|
|
|
|
|
|
|
|
|
|
expect(fs.existsSync(path.join(tmpdir, 'data/subdir/emptydir'))).to.be(true);
|
2017-10-12 16:02:09 -07:00
|
|
|
var mode = fs.statSync(path.join(tmpdir, 'dir2/file')).mode;
|
|
|
|
|
expect(mode & ~fs.constants.S_IFREG).to.be(parseInt('0755', 8));
|
2017-09-29 14:16:11 -07:00
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-10-02 20:08:00 -07:00
|
|
|
|
|
|
|
|
describe('filesystem', function () {
|
|
|
|
|
var backupInfo1;
|
|
|
|
|
|
|
|
|
|
var gBackupConfig = {
|
|
|
|
|
provider: 'filesystem',
|
|
|
|
|
backupFolder: path.join(os.tmpdir(), 'backups-test-filesystem'),
|
|
|
|
|
format: 'tgz'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
before(function (done) {
|
|
|
|
|
rimraf.sync(gBackupConfig.backupFolder);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
after(function (done) {
|
|
|
|
|
rimraf.sync(gBackupConfig.backupFolder);
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('fails to set backup config for non-existing folder', function (done) {
|
|
|
|
|
settings.setBackupConfig(gBackupConfig, function (error) {
|
|
|
|
|
expect(error).to.be.a(SettingsError);
|
|
|
|
|
expect(error.reason).to.equal(SettingsError.BAD_FIELD);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('succeeds to set backup config', function (done) {
|
|
|
|
|
mkdirp.sync(gBackupConfig.backupFolder);
|
|
|
|
|
|
|
|
|
|
settings.setBackupConfig(gBackupConfig, function (error) {
|
|
|
|
|
expect(error).to.be(null);
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('can backup', function (done) {
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
backupInfo1 = result;
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('can take another backup', function (done) {
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
done();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
2017-04-24 12:34:57 +02:00
|
|
|
});
|