Skip to content

Commit

Permalink
VOLAPI-15: Support "predicate" querystring parameter for ListVolumes …
Browse files Browse the repository at this point in the history
…endpoint
  • Loading branch information
Julien Gilli committed Jul 18, 2016
1 parent 950f1ab commit 3211c88
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 13 deletions.
39 changes: 36 additions & 3 deletions lib/endpoints/volumes.js
Expand Up @@ -10,12 +10,14 @@

var assert = require('assert-plus');
var jsprim = require('jsprim');
var krill = require('krill');
var libuuid = require('libuuid');
var path = require('path');
var restify = require('restify');
var vasync = require('vasync');

var errors = require('../errors');
var predicateValidation = require('../validation/predicate');
var units = require('../units');
var volumesModel = require('../models/volumes');
var volumeUtils = require('../volumes');
Expand Down Expand Up @@ -486,22 +488,53 @@ function createVolume(req, res, next) {
});
}

function validateListVolumes(req, res, next) {
assert.object(req, 'req');
assert.object(res, 'res');
assert.func(next, 'next');

var errs = [];
var validationErr;
var predicateValidationErr;

if (req.query.predicate) {
predicateValidationErr =
predicateValidation.validatePredicate(req.query.predicate);
}

assert.optionalObject(predicateValidationErr, predicateValidationErr);
if (predicateValidationErr !== undefined) {
errs.push(predicateValidationErr);
}

if (errs.length > 0) {
validationErr = next(new Error('Invalid list parameters: '
+ errs));
}

next(validationErr);
}

function listVolumes(req, res, next) {
assert.object(req, 'req');
assert.object(req.query, 'req.query');
assert.object(res, 'res');
assert.func(next, 'next');

var predicate;

if (req.query.predicate) {
predicate = krill.createPredicate(JSON.parse(req.query.predicate));
}

volumesModel.listVolumes({
owner_uuid: req.query.owner_uuid,
name: req.query.name,
filter: req.query.filter
predicate: predicate
}, function onListVolumes(err, volumes) {
req.volumes = volumes;
next(err);
});

}

function _deleteVolume(volume, options, callback) {
Expand Down Expand Up @@ -775,7 +808,7 @@ function mount(config, server) {
path: '/volumes',
name: 'ListVolumes',
version: '1.0.0'
}, restify.queryParser(), listVolumes, renderVolumes,
}, restify.queryParser(), validateListVolumes, listVolumes, renderVolumes,
makeSendResponseHandler({
statusCode: 200
}));
Expand Down
34 changes: 26 additions & 8 deletions lib/models/volumes.js
Expand Up @@ -40,14 +40,14 @@ function createVolume(volumeParams, callback) {
assert.object(volumeParams, 'volumeParams');
assert.func(callback, 'callback');

log.debug({volumeParams: volumeParams}, 'Creating volume in moray');
log.debug({volumeParams: volumeParams}, 'Create volume');

var volumeUuid = volumeParams.uuid;
var name = volumeParams.name;
var ownerUuid = volumeParams.owner_uuid;
var size = volumeParams.size;
var type = volumeParams.type;
var state = 'creating';
var state = volumeParams.state || 'creating';

var volumeObject = {
uuid: volumeUuid,
Expand Down Expand Up @@ -87,11 +87,22 @@ function _buildSearchFilter(params) {
var paramName;
var filters = [];
var searchFilter;
var predicateLdapFilter;

if (params.predicate && !params.predicate.trivial()) {
predicateLdapFilter =
ldapFilter.parse(params.predicate.toLDAPFilterString());
}

if (predicateLdapFilter) {
filters.push(predicateLdapFilter);
}

for (paramName in params) {
// 'filter' is a special parameter that cannot be directly translated
// into an LDAP filter.
if (paramName === 'filter') {
// 'predicate' is a special parameter that cannot be directly translated
// into an LDAP filter, and is instead parsed separately into a LDAP
// filter.
if (paramName === 'predicate') {
continue;
}

Expand All @@ -105,17 +116,22 @@ function _buildSearchFilter(params) {
}));
}

if (filters.length > 0) {
searchFilter = new ldapFilter.AndFilter({filters: filters});
} else {
if (filters.length === 0) {
searchFilter = ldapFilter.parse(SELECT_ALL_FILTER);
} else if (filters.length === 1) {
searchFilter = filters[0];
} else if (filters.length > 1) {
searchFilter = new ldapFilter.AndFilter({filters: filters});
}

return searchFilter.toString();
}

function listVolumes(params, callback) {
assert.object(params, 'params');
assert.optionalString(params.name, 'params.name');
assert.optionalString(params.owner_uuid, 'params.owner_uuid');
assert.optionalObject(params.predicate, 'params.predicate');
assert.func(callback, 'callback');

var volumesFound = [];
Expand Down Expand Up @@ -157,6 +173,8 @@ function deleteVolume(volumeUuid, callback) {
assert.string(volumeUuid, 'volumeUuid');
assert.func(callback, 'callback');

log.debug({volumeUuid: volumeUuid}, 'Delete volume');

morayClient.deleteObject(VOLUMES_BUCKET_NAME, volumeUuid, callback);
}

Expand Down
4 changes: 4 additions & 0 deletions lib/moray.js
Expand Up @@ -155,4 +155,8 @@ Moray.prototype.deleteObject =
this.connection.deleteObject(bucketName, key, callback);
};

Moray.prototype.close = function close() {
this.connection.close();
};

module.exports = Moray;
80 changes: 80 additions & 0 deletions lib/validation/predicate.js
@@ -0,0 +1,80 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*
* Copyright (c) 2016, Joyent, Inc.
*/

var assert = require('assert-plus');
var krill = require('krill');

var volumes = require('../volumes');
var volumesValidation = require('./volumes');

var VOLUME_PREDICATE_TYPES = {
name: 'string',
type: 'string',
state: 'string'
};

function validatePredicate(predicateString) {
assert.string(predicateString, 'predicateString');

var predicateObject;
var predicate;
var validationErrs = [];
var error;
var predicateFieldsAndValues;
var predicateField;
var VALIDATION_FUNCS = {
state: volumesValidation.validateVolumeState,
name: volumesValidation.validateVolumeName,
type: volumesValidation.validateVolumeType
};

try {
predicateObject = JSON.parse(predicateString);
} catch (parseErr) {
error = parseErr;
}

if (!error) {
try {
predicate = krill.createPredicate(predicateObject,
VOLUME_PREDICATE_TYPES);
} catch (predicateValidationErr) {
error = predicateValidationErr;
}
}

if (!error) {
predicateFieldsAndValues = predicate.fieldsAndValues();

for (predicateField in predicateFieldsAndValues) {
var validationFunc = VALIDATION_FUNCS[predicateField];
var predicateValues = predicateFieldsAndValues[predicateField];

assert.func(validationFunc, 'validationFunc');

predicateValues.forEach(function validatePredicateValue(value) {
var validationError = validationFunc(value);
if (validationError) {
validationErrs.push(validationError);
}
});
}
}

if (validationErrs.length > 0) {
error = new Error('Invalid values in predicate: ' + validationErrs);
}

return error;
}

module.exports = {
validatePredicate: validatePredicate
};
28 changes: 27 additions & 1 deletion lib/validation/volumes.js
@@ -1,3 +1,13 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*
* Copyright (c) 2016, Joyent, Inc.
*/

var volumes = require('../volumes');

var VALID_VOLUME_NAME_REGEXP = /^[a-zA-Z0-9][a-zA-Z0-9_\.\-]+$/;
Expand Down Expand Up @@ -37,8 +47,24 @@ function validateVolumeSize(size) {
return err;
}

function validateVolumeState(state) {
console.log('state: ', state);

var VALID_STATES = [
'creating', 'ready', 'deleted', 'failed', 'rolling_back'
];
var err;

if (VALID_STATES.indexOf(state) === -1) {
err = new Error('Volume state: ' + state + ' is invalid');
}

return err;
}

module.exports = {
validateVolumeName: validateVolumeName,
validateVolumeType: validateVolumeType,
validateVolumeSize: validateVolumeSize
validateVolumeSize: validateVolumeSize,
validateVolumeState: validateVolumeState
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -12,6 +12,7 @@
"assert-plus": "^1.0.0",
"bunyan": "^1.8.0",
"jsprim": "^1.2.2",
"krill": "git+ssh://git@github.com:misterdjules/node-krill.git#fix-empty-predicate-ldap-conversion",
"ldap-filter": "^0.3.1",
"ldapjs": "^1.0.0",
"libuuid": "^0.1.4",
Expand Down
2 changes: 1 addition & 1 deletion test/integration/lib/clients-setup.js
Expand Up @@ -34,7 +34,7 @@ function getApiClients(callback) {
}
});

var volapiClient = new VOLAPI({
var volapiClient = new restify.createJsonClient({
url: VOLAPI_URL,
version: '*',
log: logger,
Expand Down

0 comments on commit 3211c88

Please sign in to comment.