Skip to content

Commit

Permalink
VOLAPI-13: DeleteVolume endpoint should use waitlist tickets
Browse files Browse the repository at this point in the history
  • Loading branch information
Julien Gilli committed Jul 7, 2016
1 parent ca57940 commit 950f1ab
Showing 1 changed file with 117 additions and 43 deletions.
160 changes: 117 additions & 43 deletions lib/endpoints/volumes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

var assert = require('assert-plus');
var jsprim = require('jsprim');
var libuuid = require('libuuid');
var path = require('path');
var restify = require('restify');
Expand All @@ -29,7 +30,7 @@ var DEFAULT_NFS_SHARED_VOLUME_PACKAGE_SIZE_IN_MBS = 10 * units.MIBS_IN_GB;
assert.number(DEFAULT_NFS_SHARED_VOLUME_PACKAGE_SIZE_IN_MBS,
'DEFAULT_NFS_SHARED_VOLUME_PACKAGE_SIZE_IN_MBS');

var VOLUME_CREATION_TICKETS_SCOPE = 'nfs_volume_creation';
var VOLUME_TICKETS_SCOPE = 'nfs_volume';

function _selectBestPackage(requestedSize, packagesList, options, callback) {
assert.number(requestedSize, 'requestedSize');
Expand Down Expand Up @@ -169,13 +170,23 @@ function _buildStorageVMPayload(volumeParams, billingPackage, options, cb) {
});
}

function _acquireTicket(ticketParams, options, callback) {
assert.object(ticketParams, 'ticketParams');
function _acquireVolumeTicket(ticketId, options, callback) {
assert.string(ticketId, 'ticketId');
assert.object(options, 'options');
assert.object(options.cnapiClient, 'options.cnapiClient');
assert.object(options.log, 'options.log');
assert.func(callback, 'callback');

var cnapiClient = options.cnapiClient;
var ticketParams = {
scope: VOLUME_TICKETS_SCOPE,
id: ticketId,
// 10 minutes
expires_at: (new Date(Date.now() + 600 * 1000).toString())
};
var log = options.log;

log.debug({ticketParams: ticketParams}, 'Acquiring volume ticket');

cnapiClient.listServers({
headnode: true
Expand Down Expand Up @@ -210,13 +221,18 @@ function _acquireTicket(ticketParams, options, callback) {
});
}

function _releaseTicket(ticket, options, callback) {
function _releaseVolumeTicket(ticket, options, callback) {
assert.object(ticket, 'ticket');
assert.object(options, 'options');
assert.object(options.cnapiClient, 'options.cnapiClient');
assert.object(options.log, 'options.log');
assert.func(callback, 'callback');

var cnapiClient = options.cnapiClient;
var log = options.log;

log.debug({ticket: ticket}, 'Releasing volume ticket');

cnapiClient.waitlistTicketRelease(ticket.uuid, callback);
}

Expand Down Expand Up @@ -294,20 +310,12 @@ function createVolume(req, res, next) {
var context = {};

vasync.pipeline({funcs: [
function acquireVolumeCreationTicket(ctx, done) {
function acquireVolumeTicket(ctx, done) {
var ticketId = ownerUuid + '-' + volumeName;
var ticketParams = {
scope: VOLUME_CREATION_TICKETS_SCOPE,
id: ticketId,
// 10 minutes
expires_at: (new Date(Date.now() + 600 * 1000).toString())
};

req.log.debug({ticketParams: ticketParams},
'Acquiring volume creation ticket');

_acquireTicket(ticketParams, {
cnapiClient: req._cnapiClient
_acquireVolumeTicket(ticketId, {
cnapiClient: req._cnapiClient,
log: req.log
}, function onTicketAcquired(err, ticket) {
if (err) {
done(new Error('Error when acquiring ticket: ' + err));
Expand Down Expand Up @@ -462,15 +470,14 @@ function createVolume(req, res, next) {
req.volume = context.volume;

if (context.ticket !== undefined) {
req.log.debug({ticket: context.ticket},
'Releasing volume creation ticket');
_releaseTicket(context.ticket, {
cnapiClient: req._cnapiClient
_releaseVolumeTicket(context.ticket, {
cnapiClient: req._cnapiClient,
log: req.log
}, function onTicketReleased() {
// We explicitly ignore errors when releasing volume creation
// tickets, because there's not much we can do in that case.
// Instead, we propagate the error that happened in the vasync
// pipeline if there's one.
// We explicitly ignore errors when releasing volume tickets,
// because there's not much we can do in that case. Instead, we
// propagate the error that happened in the vasync pipeline if
// there's one.
next(err);
});
} else {
Expand Down Expand Up @@ -520,7 +527,9 @@ function _deleteVolume(volume, options, callback) {
}, next);
},
function deleteVolumeModel(args, next) {
volumesModel.deleteVolume(volume.uuid, next);
var deletedVolume = jsprim.deepCopy(volume);
deletedVolume.state = 'deleted';
volumesModel.updateVolume(volume.uuid, deletedVolume, next);
}
]
}, callback);
Expand Down Expand Up @@ -552,6 +561,40 @@ function deleteVolume(req, res, next) {
done(err);
});
},
function acquireVolumeTicket(ctx, done) {
assert.object(ctx.volume, 'ctx.volume');

var volume = ctx.volume;
var volumeName = volume.name;
var ticketId = ownerUuid + '-' + volumeName;

_acquireVolumeTicket(ticketId, {
cnapiClient: req._cnapiClient,
log: req.log
}, function onTicketAcquired(err, ticket) {
if (err) {
done(new Error('Error when acquiring ticket: ' + err));
return;
}

if (!ticket) {
done(new Error('Error when acquiring ticket'));
return;
}

if (ticket.status !== 'active') {
done(new Error('Could not acquire ticket, ticket is ' +
'not active and instead is: ' + ticket.status));
return;
}

req.log.debug({ticket: ticket},
'Volume deletion ticket acquired');

ctx.ticket = ticket;
done();
});
},
function checkVolumeUnused(ctx, done) {
assert.object(ctx.volume, 'ctx.volume');

Expand Down Expand Up @@ -595,11 +638,20 @@ function deleteVolume(req, res, next) {
],
arg: context
}, function allDone(err) {
if (!err) {
res.send(204);
if (context.ticket !== undefined) {
_releaseVolumeTicket(context.ticket, {
cnapiClient: req._cnapiClient,
log: req.log
}, function onTicketReleased() {
// We explicitly ignore errors when releasing volume tickets,
// because there's not much we can do in that case. Instead, we
// propagate the error that happened in the vasync pipeline if
// there's one.
next(err);
});
} else {
next(err);
}

next(err);
});
}

Expand Down Expand Up @@ -637,21 +689,35 @@ function getVolume(req, res, next) {
});
},
function _getStorageVmStatus(ctx, done) {
req._vmapiClient.getVm({
uuid: ctx.volume.vm_uuid
}, function onGetVm(err, storageVm) {
if (err || (storageVm && storageVm.state !== 'running')) {
// If the storage VM could not be found or is not in a
// 'running' state, then the volume is in a unusable
// state.
ctx.volume.state = 'failed';
}
assert.object(ctx.volume, 'ctx.volume');

// Errors in getting the storage VM object should not be
// propagated to clients, because the existence of a storage
// VM is an implementation detail.
var volume = ctx.volume;
var volumeReady = volume && volume.state === 'ready';

if (!volumeReady) {
// If the volume is not in state 'ready', then its storage VM
// state is irrelevant, so there's no need to retrieve it.
done();
});
} else {
req._vmapiClient.getVm({
uuid: ctx.volume.vm_uuid
}, function onGetVm(err, storageVm) {
var storageVmRunning = storageVm &&
storageVm.state === 'running';
if (err || !storageVmRunning) {
// If there was an error in retrieving the storage VM,
// or if that storage is not running, the volume cannot
// be accessed by other VMs, so its state is considered
// to be 'failed'.
ctx.volume.state = 'failed';
}

// Errors in getting the storage VM object should not be
// propagated to clients, because the existence of a storage
// VM is an implementation detail.
done();
});
}
}
],
arg: context
Expand Down Expand Up @@ -727,7 +793,15 @@ function mount(config, server) {
path: '/volumes/:uuid',
name: 'DeleteVolume',
version: '1.0.0'
}, restify.queryParser(), deleteVolume);
}, restify.queryParser(), deleteVolume,
function renderDeletedVolume(req, res, next) {
// Explicitly send an empty response
req.renderedResponse = {};
next();
},
makeSendResponseHandler({
statusCode: 204
}));
}

module.exports = {
Expand Down

0 comments on commit 950f1ab

Please sign in to comment.