diff --git a/src/bcc_client.js b/src/bcc_client.js new file mode 100644 index 0000000..9512d05 --- /dev/null +++ b/src/bcc_client.js @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * @file src/bcc_client.js + * @author leeight + */ + +/*eslint-env node*/ +/*eslint max-params:[0,10]*/ +/*eslint fecs-camelcase:[2,{"ignore":["/opt_/"]}]*/ + +var util = require('util'); + +var u = require('underscore'); +var Q = require('q'); +var debug = require('debug')('BccClient'); + +var Auth = require('./auth'); +var HttpClient = require('./http_client'); +var BceBaseClient = require('./bce_base_client'); + + +/** + * BCC service api + * + * 内网API地址:http://api.bcc.bce-sandbox.baidu.com + * 沙盒API地址:http://bcc.bce-api.baidu.com + * + * @see http://gollum.baidu.com/BceDocumentation/BccOpenAPI#简介 + * + * @constructor + * @param {Object} config The bcc client configuration. + * @extends {BceBaseClient} + */ +function BccClient(config) { + BceBaseClient.call(this, config, 'bcc', true); + + /** + * @type {HttpClient} + */ + this._httpAgent = null; +} +util.inherits(BccClient, BceBaseClient); + +// --- BEGIN --- + +BccClient.prototype.listInstances = function (opt_options) { + var options = opt_options || {}; + var params = u.extend( + {maxKeys: 1000}, + u.pick(options, 'maxKeys', 'marker') + ); + + return this._sendRequest('GET', '/instance', { + params: params, + config: options.config + }); +}; + +function abstractMethod() { + throw new Error('unimplemented method'); +} + +// GET /instance/price +BccClient.prototype.getPackages = function (opt_options) { + var options = opt_options || {}; + + return this._sendRequest('GET', '/instance/price', { + config: options.config + }); +}; + +// GET /image?marker={marker}&maxKeys={maxKeys}&imageType={imageType} +BccClient.prototype.getImages = function (opt_options) { + var options = opt_options || {}; + + // imageType => All, System, Custom, Integration + var params = u.extend( + {maxKeys: 1000, imageType: 'All'}, + u.pick(options, 'maxKeys', 'marker', 'imageType') + ); + + return this._sendRequest('GET', '/image', { + config: options.config, + params: params + }); +}; + +// POST /instance +BccClient.prototype.createInstance = function (body, opt_options) { + var me = this; + return this.getClientToken().then(function (response) { + var options = opt_options || {}; + + var clientToken = response.body.token; + var params = { + clientToken: clientToken + }; + + /* + var body = { + // MICRO,SMALL,MEDIUM,LARGE,XLARGE,XXLARGE + instanceType: string, + imageId: string, + ?localDiskSizeInGB: int, + ?createCdsList: List, + ?networkCapacityInMbps: int, + ?purchaseCount: int, + ?name: string, + ?adminPass: string, + ?networkType: string, + ?noahNode: string + }; + */ + + debug('createInstance, params = %j, body = %j', params, body); + + return me._sendRequest('POST', '/instance', { + config: options.config, + params: params, + body: JSON.stringify(body) + }); + }); +}; + +// GET /instance/{instanceId} +BccClient.prototype.getInstance = function (id, opt_options) { + var options = opt_options || {}; + + return this._sendRequest('GET', '/instance/' + id, { + config: options.config + }); +}; + +// PUT /instance/{instanceId}?action=start +BccClient.prototype.startInstance = function (id, opt_options) { + var options = opt_options || {}; + var params = { + start: '' + }; + + return this._sendRequest('PUT', '/instance/' + id, { + params: params, + config: options.config + }); +}; + +// PUT /instance/{instanceId}?action=stop +BccClient.prototype.stopInstance = function (id, opt_options) { + var options = opt_options || {}; + var params = { + stop: '' + }; + + return this._sendRequest('PUT', '/instance/' + id, { + params: params, + config: options.config + }); +}; + +// PUT /instance/{instanceId}?action=reboot +BccClient.prototype.restartInstance = function (id, opt_options) { + var options = opt_options || {}; + var params = { + reboot: '' + }; + + return this._sendRequest('PUT', '/instance/' + id, { + params: params, + config: options.config + }); +}; + +// PUT /instance/{instanceId}?action=changePass +BccClient.prototype.changeInstanceAdminPassword = abstractMethod; + +// PUT /instance/{instanceId}?action=rebuild +BccClient.prototype.rebuildInstance = abstractMethod; + +// DELETE /instance/{instanceId} +BccClient.prototype.deleteInstance = function (id, opt_options) { + var options = opt_options || {}; + + return this._sendRequest('DELETE', '/instance/' + id, { + config: options.config + }); +}; + +// PUT /instance/{instanceId}/securityGroup/{securityGroupId}?action=bind +BccClient.prototype.joinSecurityGroup = abstractMethod; + +// PUT /instance/{instanceId}/securityGroup/{securityGroupId}?action=unbind +BccClient.prototype.leaveSecurityGroup = abstractMethod; + +// GET /instance/{instanceId}/vnc +BccClient.prototype.getVNCUrl = function (id, opt_options) { + var options = opt_options || {}; + + return this._sendRequest('GET', '/instance/' + id + '/vnc', { + config: options.config + }); +}; + +BccClient.prototype.createSignature = function (credentials, httpMethod, path, params, headers) { + return Q.fcall(function () { + var auth = new Auth(credentials.ak, credentials.sk); + return auth.generateAuthorization(httpMethod, path, params, headers); + }); +}; + +BccClient.prototype.getClientToken = function (opt_options) { + return this._sendRequest('POST', '/token/create'); +}; + +// --- E N D --- + +BccClient.prototype._generateClientToken = function () { + var clientToken = Date.now().toString(16) + (Number.MAX_VALUE * Math.random()).toString(16).substr(0, 8); + return 'ClientToken:' + clientToken; +}; + +BccClient.prototype._sendRequest = function (httpMethod, pathname, varArgs) { + var defaultArgs = { + body: null, + headers: {}, + params: {}, + config: {}, + outputStream: null + }; + var args = u.extend(defaultArgs, varArgs); + + var config = u.extend({}, this.config, args.config); + var resource = '/v1' + pathname; + + var client = this; + var agent = this._httpAgent = new HttpClient(config); + u.each(['progress', 'error', 'abort'], function (eventName) { + agent.on(eventName, function (evt) { + client.emit(eventName, evt); + }); + }); + return this._httpAgent.sendRequest(httpMethod, resource, args.body, + args.headers, args.params, u.bind(this.createSignature, this), + args.outputStream + ); +}; + + + + + + + + + +module.exports = BccClient; + + +/* vim: set ts=4 sw=4 sts=4 tw=120: */ diff --git a/test/Makefile b/test/Makefile index 8961a70..8863380 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,2 +1,5 @@ all: ../node_modules/.bin/jasmine-node --verbose --growl --captureExceptions sdk + +bcc: + ../node_modules/.bin/jasmine-node --verbose --growl --captureExceptions sdk/bcc_client.spec.js diff --git a/test/config.js b/test/config.js index 8d5184c..97013d8 100644 --- a/test/config.js +++ b/test/config.js @@ -21,6 +21,13 @@ module.exports = { 'account': { 'id': '04e0d2c9e8ef478c951b97714c092f77', 'displayName': 'PASSPORT:105016607' + }, + 'bcc': { + 'endpoint': 'http://bcc.bce-api.baidu.com', + 'credentials': { + 'ak': '97559b0876464e6989e628edeb892e8f', + 'sk': '3e3b467ab329490a9cac428fe3f60b48' + } } // 'endpoint': 'http://localhost:8828', }; diff --git a/test/sdk/bcc_client.spec.js b/test/sdk/bcc_client.spec.js new file mode 100644 index 0000000..2b8563b --- /dev/null +++ b/test/sdk/bcc_client.spec.js @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * @file test/sdk/bcc_client.spec.js + * @author leeight + */ + +var Q = require('q'); +var u = require('underscore'); + +var config = require('../config'); +var BccClient = require('../../src/bcc_client'); + +describe('BccClient', function () { + var client; + var fail; + + beforeEach(function () { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 1000; + + fail = u.bind(function () { + return this.fail.call(this, JSON.stringify(arguments)); + }, this); + + client = new BccClient(config.bcc); + }); + + afterEach(function (done) { + client.listInstances() + .then(function (response) { + var defers = (response.body.instances || []) + .filter(function (item) { + // 包年包月的不支持释放 + return item.payment !== 'prepay'; + }) + .map(function (item) { + return client.deleteInstance(item.id); + }); + return Q.all(defers); + }) + .fin(done); + }); + + function delay(ms) { + var deferred = Q.defer(); + setTimeout(deferred.resolve, ms); + return deferred.promise; + } + + it('deleteInstance with invalid instance id', function (done) { + client.deleteInstance('no-such-instance-id') + .then(function (response) { + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + it('deleteInstance with valid instance id', function (done) { + client.listInstances() + .then(function (response) { + var instance = u.find(response.body.instances, function (item) { + return item.payment !== 'prepay'; + }); + return client.deleteInstance(instance.id); + }) + .then(function (response) { + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + it('listInstances', function (done) { + client.listInstances() + .then(function (response) { + var body = response.body; + expect(body).not.toBeUndefined(); + expect(body.maxKeys).toBe(1000); + expect(body.nextMarker).toBe(''); + expect(body.marker).toBe('null'); + expect(body.isTruncated).toBe(false); + expect(Array.isArray(body.instances)).toBe(true); + // console.log(body.instances); + // return client.getInstance(body.instances[0].id); + }) + // .then(function (response) { + // expect(response.body.server).not.toBeUndefined(); + // expect(response.body.server.id).not.toBe(''); + // }) + .catch(fail) + .fin(done); + }); + + it('getImages', function (done) { + client.getImages({maxKeys: 1}) + .then(function (response) { + var body = response.body; + expect(body.maxKeys).toBe(1); + expect(body.marker).toBe('null'); + expect(body.nextMarker).not.toBe(''); + expect(body.isTruncated).toBe(true); + expect(Array.isArray(body.images)).toBe(true); + }) + .catch(fail) + .fin(done); + }); + + it('getClientToken', function (done) { + client.getClientToken() + .then(function (response) { + expect(response.body.token).not.toBe(''); + }) + .catch(fail) + .fin(done); + }); + + it('createInstance', function (done) { + Q.all([client.getImages({maxKeys: 1}), client.getPackages()]) + .then(function (results) { + var images = results[0].body.images; + var packages = results[1].body.instanceTypes; + + var instanceType = packages[0].name; + var imageId = images[0].id; + var requestBody = { + instanceType: instanceType, + imageId: imageId + }; + + return client.createInstance(requestBody); + }) + .then(function (response) { + expect(Array.isArray(response.body.instanceIds)).toBe(true); + return delay(4000).then(function () { + return client.getInstance(response.body.instanceIds[0]); + }); + }) + .then(function (response) { + expect(response.body).not.toBe(null); + expect(response.body.server).not.toBeUndefined(); + }) + .catch(fail) + .fin(done); + }); + + it('getPackages', function (done) { + client.getPackages() + .then(function (response) { + expect(response.body.instanceTypes[0]).toEqual({ + name: 'bcc.t1.tiny', + type: 'Tiny', + cpuCount: 1, + memorySizeInGB: 1, + localDiskSizeInGB: -1 + }); + }) + .catch(fail) + .fin(done); + }); + + xit('startInstance', function (done) { + var id = '85f47301-fb64-4d6f-a761-870875537020'; + client.startInstance(id) + .then(function (response) { + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + xit('restartInstance', function (done) { + var id = '85f47301-fb64-4d6f-a761-870875537020'; + client.restartInstance(id) + .then(function (response) { + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + xit('stopInstance', function (done) { + var id = '85f47301-fb64-4d6f-a761-870875537020'; + client.stopInstance(id) + .then(function (response) { + expect(response.body).toEqual({}); + }) + .catch(fail) + .fin(done); + }); + + it('getVNCUrl', function (done) { + var id = '85f47301-fb64-4d6f-a761-870875537020'; + + client.getVNCUrl(id) + .then(function (response) { + // { vncUrl: 'http://10.105.97.40/vnc_auto.html?token=6996258f-a655-42b4-ba46-93e46eac0325' } + expect(response.body.vncUrl).not.toBe(''); + }) + .catch(fail) + .fin(done); + }); +}); + + + + + + + + + + +/* vim: set ts=4 sw=4 sts=4 tw=120: */