diff --git a/.gitignore b/.gitignore
index a307754e3..6b67ea7b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
.DS_Store
+.history
node_modules
*.log
test/fixtures/test-download*
diff --git a/docs/providers/compute-commonality.md b/docs/providers/compute-commonality.md
index 08820ff98..7e6ec7842 100644
--- a/docs/providers/compute-commonality.md
+++ b/docs/providers/compute-commonality.md
@@ -34,6 +34,9 @@ The following table outlines the methods that are available on the different com
| getLimits | N | N | N | Y | Y | N |
| getDetails | Y | N | N | N | N | N |
| stopServer | N | Y | N | N | N | N |
+| shelveServer | N | N | N | Y | N | N |
+| offloadServer | N | N | N | Y | N | N |
+| unshelveServer | N | N | N | Y | N | N |
| createHostedService | N | Y | N | N | N | N |
| resizeServer | N | N | N | Y | Y | N |
| rebuildServer | N | N | N | Y | Y | N |
diff --git a/docs/providers/openstack/compute.md b/docs/providers/openstack/compute.md
index d687f9530..d2d4113ad 100644
--- a/docs/providers/openstack/compute.md
+++ b/docs/providers/openstack/compute.md
@@ -1,4 +1,4 @@
-##Using the Openstack Compute provider
+## Using the Openstack Compute provider
Creating a client is straight-forward:
@@ -51,7 +51,7 @@ Takes server or serverId as an argument and returns the server in the callback
`f(err, server)`
#### client.rebootServer(server, options, callback)
-Reboots the specifed server with options
+Reboots the specified server with options
Options include:
@@ -63,7 +63,7 @@ Options include:
Returns callback with a confirmation
#### client.rebuildServer(server, options, callback)
-Rebuilds the specifed server with options
+Rebuilds the specified server with options
Options include:
@@ -83,6 +83,21 @@ Returns callback with a confirmation
**Note about backwards compatiblity:**
For backwards compatibility, it is also possible to pass an image ID or instance of `pkgcloud.core.compute.Image` as the value of the `options` argument.
+#### client.shelveServer(server, callback)
+[Shelves](https://developer.openstack.org/api-ref/compute/#shelve-server-shelve-action) the specified server
+
+Takes server or serverId as an argument and returns a confirmation in the callback `f(err, confirmation)`
+
+#### client.offloadServer(server, callback)
+[Offloads](https://developer.openstack.org/api-ref/compute/#shelf-offload-remove-server-shelveoffload-action) the specified server
+
+Takes server or serverId as an argument and returns a confirmation in the callback `f(err, confirmation)`
+
+#### client.unshelveServer(server, callback)
+[Unshelves](https://developer.openstack.org/api-ref/compute/#unshelve-restore-shelved-server-unshelve-action) the specified server
+
+Takes server or serverId as an argument and returns a confirmation in the callback `f(err, confirmation)`
+
#### client.getVersion(callback)
Get the current version of the api returned in a callback `f(err, version)`
diff --git a/lib/pkgcloud/common/status.js b/lib/pkgcloud/common/status.js
index b4cab0dc8..47f9eca93 100644
--- a/lib/pkgcloud/common/status.js
+++ b/lib/pkgcloud/common/status.js
@@ -13,5 +13,7 @@ exports.compute = {
stopped: 'STOPPED',
terminated: 'TERMINATED',
unknown: 'UNKNOWN',
- updating: 'UPDATING'
+ updating: 'UPDATING',
+ shelved: 'SHELVED',
+ offloaded: 'OFFLOADED'
};
\ No newline at end of file
diff --git a/lib/pkgcloud/openstack/compute/client/extensions/servers.js b/lib/pkgcloud/openstack/compute/client/extensions/servers.js
index b0fc93c01..bb9a0dedd 100644
--- a/lib/pkgcloud/openstack/compute/client/extensions/servers.js
+++ b/lib/pkgcloud/openstack/compute/client/extensions/servers.js
@@ -41,3 +41,47 @@ exports.stopServer = function (server, callback) {
});
};
+/**
+ * client.shelveServer
+ *
+ * @description Shelves a server and changes its status to SHELVED then, eventually, to OFFLOADED.
+ *
+ * @see https://developer.openstack.org/api-ref/compute/#shelve-server-shelve-action
+ *
+ * @param {String|Object} server The server ID or server to shelve
+ * @param {function} callback
+ * @returns {*}
+ */
+exports.shelveServer = function (server, callback) {
+ return this._doServerAction(server, {'shelve': null}, callback);
+};
+
+/**
+ * client.offloadServer
+ *
+ * @description Offload a shelved server and changes its status to OFFLOADED.
+ *
+ * @see https://developer.openstack.org/api-ref/compute/#shelf-offload-remove-server-shelveoffload-action
+ *
+ * @param {String|Object} server The server ID or server to offload
+ * @param {function} callback
+ * @returns {*}
+ */
+exports.offloadServer = function (server, callback) {
+ return this._doServerAction(server, {'shelveOffload': null}, callback);
+};
+
+/**
+ * client.unshelveServer
+ *
+ * @description Unshelve a shelved or offloaded server and changes its status to RUNNING.
+ *
+ * @see https://developer.openstack.org/api-ref/compute/#unshelve-restore-shelved-server-unshelve-action
+ *
+ * @param {String|Object} server The server ID or server to unshelve
+ * @param {function} callback
+ * @returns {*}
+ */
+exports.unshelveServer = function (server, callback) {
+ return this._doServerAction(server, {'unshelve': null}, callback);
+};
diff --git a/lib/pkgcloud/openstack/compute/server.js b/lib/pkgcloud/openstack/compute/server.js
index 9c240186b..fa074e8d9 100644
--- a/lib/pkgcloud/openstack/compute/server.js
+++ b/lib/pkgcloud/openstack/compute/server.js
@@ -53,6 +53,12 @@ Server.prototype._setProperties = function (details) {
case 'ERROR':
this.status = this.STATUS.error;
break;
+ case 'SHELVED':
+ this.status = this.STATUS.shelved;
+ break;
+ case 'SHELVED_OFFLOADED':
+ this.status = this.STATUS.offloaded;
+ break;
default:
this.status = this.STATUS.unknown;
break;
diff --git a/test/openstack/compute/client/offloadServer-test.js b/test/openstack/compute/client/offloadServer-test.js
new file mode 100644
index 000000000..e50b9844c
--- /dev/null
+++ b/test/openstack/compute/client/offloadServer-test.js
@@ -0,0 +1,106 @@
+/*
+*
+* (C) 2014 Alvaro M. Reol
+*
+*/
+
+var helpers = require('../../../helpers');
+
+var should = require('should'),
+ async = require('async'),
+ hock = require('hock'),
+ http = require('http'),
+ mock = !!process.env.MOCK;
+
+var client = helpers.createClient('openstack', 'compute');
+
+describe('pkgcloud/common/compute/server[openstack]', function () {
+
+ var authHockInstance, hockInstance, authServer, server;
+
+ before(function (done) {
+
+ if (!mock) {
+ return done();
+ }
+
+ hockInstance = hock.createHock({ throwOnUnmatched: false });
+ authHockInstance = hock.createHock();
+
+ server = http.createServer(hockInstance.handler);
+ authServer = http.createServer(authHockInstance.handler);
+
+ async.parallel([
+ function (next) {
+ server.listen(12345, next);
+ },
+ function (next) {
+ authServer.listen(12346, next);
+ }
+ ], done);
+ });
+
+ it('the server.offloadServer() method should offload a server instance', function (done) {
+ if (mock) {
+ authHockInstance
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ }
+ }
+ })
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/initialToken.json')
+ .get('/v2.0/tenants')
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/tenantId.json')
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ },
+ tenantId: '72e90ecb69c44d0296072ea39e537041'
+ }
+ })
+ .reply(200, helpers.getOpenstackAuthResponse());
+
+ hockInstance
+ .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action',
+ { 'shelveOffload': null })
+ .reply(200, '');
+ }
+
+ client.offloadServer('a2e90ecb69c44d0296072ea39e53704a', function (err) {
+ should.not.exist(err);
+
+ authHockInstance && authHockInstance.done();
+ hockInstance && hockInstance.done();
+
+ done();
+ });
+
+
+ });
+
+ after(function (done) {
+ if (!mock) {
+ return done();
+ }
+
+ async.parallel([
+ function (next) {
+ server.close(next);
+ },
+ function (next) {
+ authServer.close(next);
+ }
+ ], done);
+ });
+
+});
+
+
+
+
+
diff --git a/test/openstack/compute/client/shelveServer-test.js b/test/openstack/compute/client/shelveServer-test.js
new file mode 100644
index 000000000..406769700
--- /dev/null
+++ b/test/openstack/compute/client/shelveServer-test.js
@@ -0,0 +1,106 @@
+/*
+*
+* (C) 2014 Alvaro M. Reol
+*
+*/
+
+var helpers = require('../../../helpers');
+
+var should = require('should'),
+ async = require('async'),
+ hock = require('hock'),
+ http = require('http'),
+ mock = !!process.env.MOCK;
+
+var client = helpers.createClient('openstack', 'compute');
+
+describe('pkgcloud/common/compute/server[openstack]', function () {
+
+ var authHockInstance, hockInstance, authServer, server;
+
+ before(function (done) {
+
+ if (!mock) {
+ return done();
+ }
+
+ hockInstance = hock.createHock({ throwOnUnmatched: false });
+ authHockInstance = hock.createHock();
+
+ server = http.createServer(hockInstance.handler);
+ authServer = http.createServer(authHockInstance.handler);
+
+ async.parallel([
+ function (next) {
+ server.listen(12345, next);
+ },
+ function (next) {
+ authServer.listen(12346, next);
+ }
+ ], done);
+ });
+
+ it('the server.shelveServer() method should shelve a server instance', function (done) {
+ if (mock) {
+ authHockInstance
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ }
+ }
+ })
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/initialToken.json')
+ .get('/v2.0/tenants')
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/tenantId.json')
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ },
+ tenantId: '72e90ecb69c44d0296072ea39e537041'
+ }
+ })
+ .reply(200, helpers.getOpenstackAuthResponse());
+
+ hockInstance
+ .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action',
+ { 'shelve': null })
+ .reply(200, '');
+ }
+
+ client.shelveServer('a2e90ecb69c44d0296072ea39e53704a', function (err) {
+ should.not.exist(err);
+
+ authHockInstance && authHockInstance.done();
+ hockInstance && hockInstance.done();
+
+ done();
+ });
+
+
+ });
+
+ after(function (done) {
+ if (!mock) {
+ return done();
+ }
+
+ async.parallel([
+ function (next) {
+ server.close(next);
+ },
+ function (next) {
+ authServer.close(next);
+ }
+ ], done);
+ });
+
+});
+
+
+
+
+
diff --git a/test/openstack/compute/client/unshelveServer-test.js b/test/openstack/compute/client/unshelveServer-test.js
new file mode 100644
index 000000000..8324dc0d8
--- /dev/null
+++ b/test/openstack/compute/client/unshelveServer-test.js
@@ -0,0 +1,106 @@
+/*
+*
+* (C) 2014 Alvaro M. Reol
+*
+*/
+
+var helpers = require('../../../helpers');
+
+var should = require('should'),
+ async = require('async'),
+ hock = require('hock'),
+ http = require('http'),
+ mock = !!process.env.MOCK;
+
+var client = helpers.createClient('openstack', 'compute');
+
+describe('pkgcloud/common/compute/server[openstack]', function () {
+
+ var authHockInstance, hockInstance, authServer, server;
+
+ before(function (done) {
+
+ if (!mock) {
+ return done();
+ }
+
+ hockInstance = hock.createHock({ throwOnUnmatched: false });
+ authHockInstance = hock.createHock();
+
+ server = http.createServer(hockInstance.handler);
+ authServer = http.createServer(authHockInstance.handler);
+
+ async.parallel([
+ function (next) {
+ server.listen(12345, next);
+ },
+ function (next) {
+ authServer.listen(12346, next);
+ }
+ ], done);
+ });
+
+ it('the server.unshelveServer() method should unshelve a server instance', function (done) {
+ if (mock) {
+ authHockInstance
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ }
+ }
+ })
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/initialToken.json')
+ .get('/v2.0/tenants')
+ .replyWithFile(200, __dirname + '/../../../fixtures/openstack/tenantId.json')
+ .post('/v2.0/tokens', {
+ auth: {
+ passwordCredentials: {
+ username: 'MOCK-USERNAME',
+ password: 'MOCK-PASSWORD'
+ },
+ tenantId: '72e90ecb69c44d0296072ea39e537041'
+ }
+ })
+ .reply(200, helpers.getOpenstackAuthResponse());
+
+ hockInstance
+ .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action',
+ { 'unshelve': null })
+ .reply(200, '');
+ }
+
+ client.unshelveServer('a2e90ecb69c44d0296072ea39e53704a', function (err) {
+ should.not.exist(err);
+
+ authHockInstance && authHockInstance.done();
+ hockInstance && hockInstance.done();
+
+ done();
+ });
+
+
+ });
+
+ after(function (done) {
+ if (!mock) {
+ return done();
+ }
+
+ async.parallel([
+ function (next) {
+ server.close(next);
+ },
+ function (next) {
+ authServer.close(next);
+ }
+ ], done);
+ });
+
+});
+
+
+
+
+