Skip to content

Commit

Permalink
Allow for asset uploads to be suspended.
Browse files Browse the repository at this point in the history
In some cases we need to be able to stop new assets from being
uploaded but would like to allow assets to continue to be
downloaded. This is useful for doing server maintenance. This
allows a flag to be set in the config or set via a post to a
special URL. When the "paused" flag is set, the server will
return a 503 resource unavailable response for POSTs to the asset
upload endpoint. Tests are provided for all cases.
  • Loading branch information
Laurence Liss committed Jun 3, 2016
1 parent 5c64b7a commit c27f669
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 6 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,13 @@ Example:
```
./bin/probo-asset-receiver -c path/to/config-file/awsS3Storage.config.yaml
```

### 6. Pausing and unpausing file uploads
If doing server maintenance, it is often nice to be able to prevent new files
from coming in but still allowing existing files to be served. This allows
builds to continue to function even though new assets will not be allowed. This
is helpful when migrating the files to a new server or system.

```
curl -X POST -H "Authorization: Bearer" -H content-type:application/json --data-binary '{"uploadsPaused": true}' http://localhost:3000/service/upload-status
```
2 changes: 2 additions & 0 deletions defaults.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ recipheredOutputDir: null
# API tokens for creating all routes except asset upload (disabled by default)
tokens: null

uploadsPaused: false

## If using S3 use this template for config settings.
#
# fileStoragePlugin: AwsS3Storage
Expand Down
38 changes: 37 additions & 1 deletion lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ var Server = function(options) {
this.options = options || {
encryptionCipher: 'aes-256-cbc',
encryptionPassword: 'SECRET',
uploadsPaused: false,
};
this.options.databasePlugin = this.options.databasePlugin || 'LevelDB';
this.options.fileStoragePlugin = this.options.fileStoragePlugin || 'LocalFiles';
this.database = new plugins.database[this.options.databasePlugin](this.options.databaseConfig);
this.logger = this.options.logger || bunyan.createLogger({name: 'probo-asset-receiver'});
this.fileStorage = new plugins.fileStorage[this.options.fileStoragePlugin](this.options.fileStorageConfig, this.logger);
this.uploadsPaused = this.options.uploadsPaused;

// Used to generate IDs for asset uploads.
this.flakeIdGen = new FlakeId();
Expand Down Expand Up @@ -74,6 +76,10 @@ Server.prototype.setup = function() {
app.get('/buckets/:bucket/assets', auth, this.routes.assetMetadataByBucket);
app.delete('/buckets/:bucket/assets/:assetName', auth, this.routes.deleteAssetFromBucket);

// Maintenance endpoints.
app.post('/service/upload-status', auth, bodyParser.json(), this.routes.uploadStatus);
app.get('/service/upload-status', auth, this.routes.getUploadStatus);

// routes not requiring authorization token
app.post('/asset/:token/:assetName', this.routes.receiveFileAsset);
app.get('/', this.routes.index);
Expand Down Expand Up @@ -141,6 +147,29 @@ Server.prototype.routes.index = function(req, res, done) {
res.end('Probo asset receiver');
};

Server.prototype.routes.uploadStatus = function(req, res, done) {
var uploadsPaused = req.body.uploadsPaused;
if (uploadsPaused === true || uploadsPaused === false) {
this.uploadsPaused = uploadsPaused;
var pausedStatus = uploadsPaused ? 'paused' : 'unpaused';
return res
.status(201)
.send(`Uploads are now ${pausedStatus}.`);
}
else {
return res
.status(400)
.send('Unknown options.');
}
};

Server.prototype.routes.getUploadStatus = function(req, res, done) {
var pausedStatus = this.uploadsPaused ? 'paused' : 'unpaused';
return res
.status(200)
.send(`Uploads are ${pausedStatus}.`);
};

Server.prototype.routes.listBuckets = function(req, res, done) {
var readStream = this.database.listBuckets();
res.writeHead(200, {'Content-Type': 'application/JSON'});
Expand Down Expand Up @@ -284,7 +313,14 @@ Server.prototype.routes.receiveFileAsset = function(req, res, done) {
var self = this;
var token = req.params.token;
var assetName = req.params.assetName;
self.database.getBucketFromToken(token, function(error, bucket) {

if (this.uploadsPaused) {
return res
.status(503)
.send('The system is undergoing maintenance and cannot accept file uploads at the moment.');
}

this.database.getBucketFromToken(token, function(error, bucket) {
if (error) {
self.logger.warn('Bucket not found ' + bucket + ' when attempting to use token ' + token, {bucket: bucket, token: token});
return res
Expand Down
67 changes: 62 additions & 5 deletions test/upload-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,11 @@ describe('http-api', function() {
done();
});
});

beforeEach(function() {
server.uploadsPaused = false;
});

after(function(done) {
server.stop(done);
});
Expand Down Expand Up @@ -296,6 +301,17 @@ describe('http-api', function() {
done();
});
});
it('should receive a 500 error if uploads are paused', function(done) {
server.uploadsPaused = true;
var options = getOptions('/asset/baz/package.json');
var errorMessage = 'The system is undergoing maintenance and cannot accept file uploads at the moment.';
request.post(options, function(error, res, body) {
res.statusCode.should.equal(503);
res.statusMessage.should.equal('Service Unavailable');
body.should.equal(errorMessage);
done();
});
});
});

describe('Asset Data', function() {
Expand Down Expand Up @@ -359,27 +375,68 @@ describe('http-api', function() {
});
});
it('should no longer contain bucket asset database data.', function(done) {
server.database.getAssetId('foo', 'package.json', function(err, assetId) {
server.database.getAssetId('foo', 'package.json', function(error, assetId) {
should.not.exist(assetId);
done();
});
});
it('should no longer contain asset database data.', function(done) {
server.database.getAssetMetadata(foundAssetId, function(err, data) {
server.database.getAssetMetadata(foundAssetId, function(error, data) {
should.not.exist(data);
done();
});
});
it('should no longer contain bucket-asset-version database data.', function(done) {
server.database.getBucketAssetVersion('foo', 'package.json', foundAssetId, function(err, data) {
server.database.getBucketAssetVersion('foo', 'package.json', foundAssetId, function(error, data) {
should.not.exist(data);
done();
});
});
it('should delete a file.', function(done) {
var file = server.fileStorage.createReadStream(foundAssetId);
file.on('error', function(err) {
err.message.should.startWith('ENOENT: no such file or directory');
file.on('error', function(error) {
error.message.should.startWith('ENOENT: no such file or directory');
done();
});
});
});

describe('Maintenance endpoints', function() {
it('should give an appropriate message if uploads are not paused.', function(done) {
var options = getOptions('/service/upload-status');
request(options, function(error, response, body) {
console.log(body);
body.should.equal('Uploads are unpaused.');
done();
});
});
it('should give an appropriate message if uploads are paused.', function(done) {
var options = getOptions('/service/upload-status');
server.uploadsPaused = true;
request(options, function(error, response, body) {
console.log(body);
body.should.equal('Uploads are paused.');
done();
});
});
it('should pause the server upload status via post .', function(done) {
var options = getOptions('/service/upload-status');
options.body = {
uploadsPaused: true,
};
request.post(options, function(error, response, body) {
body.should.equal('Uploads are now paused.');
done();
});
});
it('should unpause the server upload status via post .', function(done) {
server.uploadsPaused = true;
var options = getOptions('/service/upload-status');
options.body = {
uploadsPaused: false,
};
request.post(options, function(error, response, body) {
body.should.equal('Uploads are now unpaused.');
done();
});
});
Expand Down

0 comments on commit c27f669

Please sign in to comment.