cloud-blender
Version:
A high level library for cloud compute operations
582 lines (500 loc) • 20.1 kB
JavaScript
var request = require('request'),
underscore = require('underscore'),
async = require('async'),
interval = 10000,
proxyUrl, token;
function createSimpleNodeData(rawNode) {
var node = {
id: rawNode.id,
status: rawNode.status,
addresses: [null, null],
tags: {}
};
if (rawNode.addresses.private) {
underscore.each(rawNode.addresses.private, function (item) {
if (item.version === 4) {
node.addresses[0] = item.addr;
}
});
}
if (rawNode.addresses.public) {
underscore.each(rawNode.addresses.public, function (item) {
if (item.version === 4) {
node.addresses[1] = item.addr;
}
});
}
if (rawNode.metadata) {
underscore.each(rawNode.metadata, function (item, key) {
node.tags[key] = item;
});
}
return node;
}
function setProxy(proxy) {
proxyUrl = proxy;
}
function getProxy() {
return proxyUrl;
}
function connect(identitySettings, callback) {
// the threshold is 1 hour to be on the safe side
// (the token expires every 12hours)
var DIFF_THRESH_HOURS = 1,
expires = '',
hourDiff = 0,
requestSettings;
if (token) {
identitySettings.identityToken = token;
}
if (identitySettings.identityToken && identitySettings.identityToken.access && identitySettings.identityToken.access.token &&
'id' in identitySettings.identityToken.access.token &&
'expires' in identitySettings.identityToken.access.token) {
expires = identitySettings.identityToken.access.token.expires;
hourDiff = (new Date(expires).getTime() - new Date().getTime()) / 1000 / 60 / 60;
if (hourDiff > DIFF_THRESH_HOURS) {
return callback(null, identitySettings.identityToken);
}
}
var auth = {
"RAX-KSKEY:apiKeyCredentials": {
"username": identitySettings.regionContext.identitySettings.credentials.username,
"apiKey": identitySettings.regionContext.identitySettings.credentials.apiKey
}
};
requestSettings = {
method: 'POST',
url: 'https://identity.api.rackspacecloud.com/v2.0/tokens',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({auth: auth}),
proxy: getProxy()
};
request(requestSettings, function (error, response, bodyString) {
var identityToken;
if ((error) || ((response) && (response.statusCode !== 200))) {
return callback(new Error('cannot retrieve token from rackspace. reason: ' +
(response ? response.statusCode : ' empty response - probably bad tunneling proxy')));
}
identityToken = JSON.parse(bodyString); //.access.token;
identitySettings.identityToken = identityToken;
setToken(identityToken);
callback(null, identityToken);
});
}
function setToken(identityToken) {
token = identityToken;
}
function getToken() {
return token;
}
function getEndpoint(success, region, type) {
var endPointType = type;
if (!type) {
endPointType = 'compute';
}
var catalogItem = underscore.find(success.access.serviceCatalog, function (item) {
if (item.type === endPointType) {
return true;
}
else {
return false;
}
});
var endpoint;
if (catalogItem && catalogItem.endpoints) {
endpoint = underscore.find(catalogItem.endpoints, function (item) {
if (item.region === region) {
return true;
}
else {
return false;
}
});
}
return endpoint;
}
function listPortsForSecurityGroupsRetryRequest(settings, pollingCount, interval, callback) {
request(settings, function (error, response, bodyString) {
if ((error) || (response && (response.statusCode !== 200))) {
if (pollingCount > 0) {
setTimeout(listPortsForSecurityGroupsRetryRequest, interval, settings, pollingCount - 1, interval, callback);
} else {
var errorCreate = new Error('can not listPorts with parameters: ' + JSON.stringify(settings) +
'. statusCode: ' + (response ? response.statusCode : 'undefined') +
' ,body string: ' + JSON.stringify(bodyString) + ' ,error: ' + error);
callback(errorCreate);
}
} else {
callback(null, JSON.parse(bodyString).ports);
}
});
}
function updateSecurityGroupOfPortRetryRequest(settings, pollingCount, interval, callbackRetry){
request(settings, function (error, response, bodyString) {
if ((error) || (response && (response.statusCode !== 200))) {
if (pollingCount > 0){
setTimeout(updateSecurityGroupOfPortRetryRequest, interval, settings, pollingCount - 1, interval, callbackRetry);
}else{
var errorCreate = new Error('can not update security groups with parameters: ' + JSON.stringify(settings) +
'. statusCode: ' + (response ? response.statusCode : 'undefined') +
' ,body string: ' + JSON.stringify(bodyString) + ' ,error: ' + error);
callbackRetry(errorCreate);
}
}else{
callbackRetry();
}
});
}
module.exports = {
setProxy: function (proxyUrl) {
setProxy(proxyUrl);
},
createPreparation: function (settings, callback) {
callback(null, null);
},
createRegionContext: function (regionSettings, limits) {
return {
identitySettings: {
credentials: {
username: regionSettings.accessKey,
apiKey: regionSettings.secretKey
}
},
computeSettings: {
region: regionSettings.region
},
limits: limits,
providerName: 'rackspace',
pollingCount: 180
};
},
listNodes: function (settings, callback) {
var region = settings.regionContext.computeSettings.region;
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
var listNodesRequestSettings = {
method: 'GET',
url: endpoint.publicURL + '/servers/detail',
headers: {
'X-Auth-Token': success.access.token.id,
'Accept': 'application/json'
},
proxy: getProxy()
};
request(listNodesRequestSettings, function (error, response, bodyString) {
var finalResults = {
nodes: [],
rawResult: bodyString
};
if ((error) || (response && (response.statusCode !== 200) && (response.statusCode !== 203) && (response.statusCode !== 300))) {
var errorCreate = new Error('can not listNodes with parameters: ' + JSON.stringify(settings) +
'. statusCode: ' + (response ? response.statusCode : 'undefined') +
' ,body string' + bodyString,
'error: ' + error);
return callback(errorCreate, finalResults);
}
var servers = JSON.parse(bodyString).servers;
underscore.each(servers, function (server) {
var node = createSimpleNodeData(server);
finalResults.nodes.push(node);
});
return callback(null, finalResults);
});
});
},
createNode: function (settings, cloudServicesTestSettings, nodeIndex, callback) {
function isJson(obj) {
if (typeof obj === 'object') {
return true;
} else {
return false;
}
}
var securityGroups = settings.nodeParams.securityGroups,
region = settings.regionContext.computeSettings.region,
nodeParams = {
name: new Date().valueOf() + '-createdByStorm',
imageRef: settings.nodeParams.imageId,
flavorRef: settings.nodeParams.instanceType,
metadata: settings.nodeParams.tags,
key_name: settings.nodeParams.keyName
},
userData = settings.nodeParams.userData,
adminPass = settings.nodeParams.adminPass;
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
// adding user data
if (userData) {
if (isJson(userData)) {
nodeParams.user_data = new Buffer(JSON.stringify(userData)).toString('base64');
} else {
nodeParams.user_data = new Buffer(userData).toString('base64');
}
nodeParams.config_drive = true;
}
if (adminPass) {
nodeParams.adminPass = adminPass;
}
// vendor specific extension must be last
underscore.extend(nodeParams, settings.nodeParams.vendorSpecificParams);
var createserverRequest = {
method: 'POST',
url: endpoint.publicURL + '/servers',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Auth-Token': success.access.token.id
},
proxy: getProxy(),
body: JSON.stringify({server: nodeParams})
};
request(createserverRequest, function (error, response, bodyString) {
function updateSecurityGroups() {
listPortsForSecurityGroupsRetryRequest(listPorts, 20, interval, function (error, ports){
if (error) {
callback(error);
}
async.each(ports, function (port, callbackAsync) {
if (machineId === port.device_id) {
var updateSecurityGroupForPort = {
method: 'PUT',
url: networkEndpoint.publicURL + '/ports/' + port.id,
headers: {
'X-Auth-Token': success.access.token.id,
'Accept': 'application/json'
},
proxy: getProxy(),
json: {
'port': {
'security_groups': securityGroups
}
}
};
updateSecurityGroupOfPortRetryRequest(updateSecurityGroupForPort, 20, interval, callbackAsync);
} else {
callbackAsync();
}
},
function (err) {
return callback(err, finalResults);
});
});
}
var finalResults = {
rawResult: {},
node: {
tags: settings.nodeParams.tags
}
};
if ((error) || (response.statusCode !== 202)) {
finalResults.node.status = 'ERROR_ALLOCATION';
var errorCreate = new Error('can not createNode with parameters: ' + JSON.stringify(settings.nodeParams) +
'. statusCode: ' + (response ? response.statusCode : 'undefined') +
' ,body string' + bodyString,
'error: ' + error);
return callback(errorCreate, finalResults);
}
finalResults.node = underscore.extend(finalResults.node, JSON.parse(bodyString).server);
finalResults.rawResult = JSON.parse(bodyString);
if (securityGroups) {
var networkEndpoint = getEndpoint(success, region, 'network'),
machineId = JSON.parse(bodyString).server.id;
if (!networkEndpoint) {
return callback(new Error('didn\'t find network endpoint for region: ' + region));
}
var listPorts = {
method: 'GET',
url: networkEndpoint.publicURL + '/ports',
headers: {
'X-Auth-Token': success.access.token.id,
'Accept': 'application/json'
},
proxy: getProxy()
};
updateSecurityGroups();
} else {
return callback(null, finalResults);
}
});
});
},
deleteNode: function (settings, callback) {
var region = settings.regionContext.computeSettings.region;
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
var deleteServerRequest = {
method: 'DELETE',
url: endpoint.publicURL + '/servers/' + settings.node.id,
headers: {
'Accept': 'application/json',
'X-Auth-Token': success.access.token.id
},
proxy: getProxy()
};
request(deleteServerRequest, function (error, response, bodyString) {
var finalResults = {
rawResult: bodyString
};
if ((error) || (response.statusCode !== 204)) {
finalResults.result = 'ERROR';
var errorCreate = new Error('deleteNode failed for id: ' + JSON.stringify(settings.node.id) +
'. statusCode: ' + (response ? response.statusCode : 'undefined') +
'. response: ' + (response ? JSON.stringify(response) : 'undefined') +
', request settings: ' + JSON.stringify(deleteServerRequest));
return callback(errorCreate, finalResults);
}
finalResults.result = 'SUCCESS';
return callback(null, finalResults);
});
});
},
createImage: function (settings, callback) {
var imageParams = {
name: new Date().valueOf() + '-createdByStorm',
serverId: settings.imageParams.nodeId,
metadata: settings.imageParams.tags
}, region = settings.regionContext.computeSettings.region;
underscore.extend(imageParams, settings.imageParams.vendorSpecificParams);
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
var createImageRequest = {
method: 'POST',
url: endpoint.publicURL + '/servers/' + settings.imageParams.nodeId + '/action',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Auth-Token': success.access.token.id
},
proxy: getProxy(),
body: JSON.stringify({createImage: imageParams})
};
request(createImageRequest, function (error, response, bodyString) {
var location = '',
imageId = '',
finalResult = {};
if (error || (response && (response.statusCode !== 202))) {
var errorCreate = new Error('cannot createImage with params: ' +
JSON.stringify(settings.imageParams) +
', error: ' + error +
'. statusCode: ' + (response ? response.statusCode : 'undefined'));
return callback(errorCreate);
}
location = response.headers.location;
imageId = location.slice(location.lastIndexOf('/') + 1);
finalResult.rawResult = location;
finalResult.imageId = imageId;
return callback(null, finalResult);
});
});
},
listImages: function (settings, imageIdsArr, callback) {
var region = settings.regionContext.computeSettings.region;
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
var listImagesRequest = {
method: 'GET',
url: endpoint.publicURL + '/images/detail',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Auth-Token': success.access.token.id
},
proxy: getProxy()
};
request(listImagesRequest, function (error, response, bodyString) {
var finalResult = {};
if (error || (response && (response.statusCode !== 200))) {
var errorCreate = new Error('cannot retrieve images list from rackspace. ' +
'. statusCode: ' + (response ? response.statusCode : 'undefined'));
return callback(errorCreate);
}
finalResult.rawResult = JSON.parse(bodyString);
finalResult.images = [];
underscore.each(finalResult.rawResult.images, function (rawImage) {
var image = underscore.pick(rawImage, 'id', 'status', 'name');
image.tags = rawImage.metadata;
finalResult.images.push(image);
});
return callback(null, finalResult);
});
});
},
deleteImage: function (settings, callback) {
var region = settings.regionContext.computeSettings.region;
connect(settings, function (error, success) {
if (error) {
return callback(new Error('couldn\'t connect: ' + error));
}
var endpoint = getEndpoint(success, region);
if (!endpoint) {
return callback(new Error('didn\'t find an endpoint for region: ' + region));
}
var deleteImageRequest = {
method: 'DELETE',
url: endpoint.publicURL + '/images/' + settings.imageParams.imageId,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Auth-Token': success.access.token.id
},
proxy: getProxy()
};
request(deleteImageRequest, function (error, response, bodyString) {
var finalResult = {rawResult: undefined};
if (error || (response && (response.statusCode !== 204))) {
var errorCreate = new Error('cannot deleteImage, error: ' + error + ', code: ' +
(response ? response.statusCode : 'undefined'));
finalResult.result = 'ERROR';
return callback(errorCreate, finalResult);
}
finalResult.result = 'SUCCESS';
return callback(null, finalResult);
});
});
},
associateAddress: function (settings, callback) {
var error = new Error('no implementation');
callback(error, null);
},
disassociateAddress: function (settings, callback) {
var error = new Error('no implementation');
callback(error, null);
},
validateCredentials: function (settings, callback) {
var error = new Error('no implementation');
callback(error, null);
}
};