diff --git a/docs/toc.json b/docs/toc.json index dcbdc7537fe..fbd147aaaca 100644 --- a/docs/toc.json +++ b/docs/toc.json @@ -101,6 +101,9 @@ }, { "title": "Snapshot", "type": "compute/snapshot" + }, { + "title": "Subnetwork", + "type": "compute/subnetwork" }, { "title": "VM", "type": "compute/vm" diff --git a/lib/compute/index.js b/lib/compute/index.js index eeeb890f3db..90faf1a42d5 100644 --- a/lib/compute/index.js +++ b/lib/compute/index.js @@ -1908,6 +1908,121 @@ Compute.prototype.getSnapshots = function(options, callback) { }); }; +/** + * Get a list of subnetworks in this project. + * + * @resource [Subnetworks Overview]{@link https://cloud.google.com/compute/docs/subnetworks} + * @resource [Subnetworks: list API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks} + * + * @param {object=} options - Subnetwork search options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {string} options.filter - Search filter in the format of + * `{name} {comparison} {filterString}`. + * - **`name`**: the name of the field to compare + * - **`comparison`**: the comparison operator, `eq` (equal) or `ne` + * (not equal) + * - **`filterString`**: the string to filter to. For string fields, this + * can be a regular expression. + * @param {number} options.maxApiCalls - Maximum number of API calls to make. + * @param {number} options.maxResults - Maximum number of subnetworks to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/subnetwork} callback.subnetworks - Subnetwork objects + * from your project. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * gce.getSubnetworks(function(err, subnetworks) { + * // `subnetworks` is an array of `Subnetworks` objects. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, subnetworks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * gce.getSubnetworks(nextQuery, callback); + * } + * } + * + * gce.getSubnetworks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the subnetworks from your project as a readable object stream. + * //- + * gce.getSubnetworks() + * .on('error', console.error) + * .on('data', function(subnetwork) { + * // `subnetwork` is a `Subnetwork` object. + * }) + * .on('end', function() { + * // All subnetworks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * gce.getSubnetworks() + * .on('data', function(subnetwork) { + * this.end(); + * }); + */ +Compute.prototype.getSubnetworks = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + this.request({ + uri: '/aggregated/subnetworks', + qs: options + }, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var regions = resp.items || {}; + + var subnetworks = Object.keys(regions).reduce(function(acc, regionName) { + var region = self.region(regionName.replace('regions/', '')); + var subnetworks = regions[regionName].subnetworks || []; + + subnetworks.forEach(function(subnetwork) { + var subnetworkInstance = region.subnetwork(subnetwork.name); + subnetworkInstance.metadata = subnetwork; + acc.push(subnetworkInstance); + }); + + return acc; + }, []); + + callback(null, subnetworks, nextQuery, resp); + }); +}; + /** * Get a list of virtual machine instances. * @@ -2272,6 +2387,7 @@ streamRouter.extend(Compute, [ 'getRules', 'getServices', 'getSnapshots', + 'getSubnetworks', 'getVMs', 'getZones' ]); diff --git a/lib/compute/network.js b/lib/compute/network.js index 749847299a3..e62b95dd4f7 100644 --- a/lib/compute/network.js +++ b/lib/compute/network.js @@ -214,6 +214,145 @@ Network.prototype.createFirewall = function(name, config, callback) { this.compute.createFirewall(name, config, callback); }; +/** + * Create a subnetwork in this network. + * + * @resource [Subnetwork Resource]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks#resource} + * @resource [Subnetwork: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks/insert} + * + * @param {string} name - Name of the subnetwork. + * @param {object} config - See a + * [Subnetwork resource](https://cloud.google.com/compute/docs/reference/v1/subnetworks#resource). + * @param {module:compute/region|string} config.region - The region where the + * Subnetwork resides. + * @param {string} config.range - The range of internal addresses that + * are owned by this subnetwork. + * [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range + * of addresses that are legal on this network. (Alias for + * `config.ipCidrRange`) + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/subnetwork} callback.subnetwork - The created + * Subnetwork object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var region = gce.region('us-east1'); + * + * var config = { + * region: region, + * range: '10.0.1.0/24' + * }; + * + * function callback(err, subnetwork, operation, apiResponse) { + * // `subnetwork` is a Subnetwork object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * } + * + * network.createSubnetwork('new-subnetwork-name', config, callback); + */ +Network.prototype.createSubnetwork = function(name, config, callback) { + config = extend({}, config, { + network: this.formattedName + }); + + var region = config.region; + + if (is.string(region)) { + region = this.compute.region(region); + } + + delete config.region; + + region.createSubnetwork(name, config, callback); +}; + +/** + * Get a list of subnetworks in this network. + * + * @resource [Subnetworks Overview]{@link https://cloud.google.com/compute/docs/subnetworks} + * @resource [Subnetworks: list API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks} + * + * @param {object=} options - Subnetwork search options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {string} options.filter - Search filter in the format of + * `{name} {comparison} {filterString}`. + * - **`name`**: the name of the field to compare + * - **`comparison`**: the comparison operator, `eq` (equal) or `ne` + * (not equal) + * - **`filterString`**: the string to filter to. For string fields, this + * can be a regular expression. + * @param {number} options.maxApiCalls - Maximum number of API calls to make. + * @param {number} options.maxResults - Maximum number of subnetworks to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/subnetwork} callback.subnetworks - Subnetwork objects + * from this network. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * network.getSubnetworks(function(err, subnetworks) { + * // `subnetworks` is an array of `Subnetworks` objects. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, subnetworks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * network.getSubnetworks(nextQuery, callback); + * } + * } + * + * network.getSubnetworks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the subnetworks from this network as a readable object stream. + * //- + * network.getSubnetworks() + * .on('error', console.error) + * .on('data', function(subnetwork) { + * // `subnetwork` is a `Subnetwork` object. + * }) + * .on('end', function() { + * // All subnetworks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * network.getSubnetworks() + * .on('data', function(subnetwork) { + * this.end(); + * }); + */ +Network.prototype.getSubnetworks = function(options, callback) { + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = extend({}, options, { + filter: 'network eq .*' + this.formattedName + }); + + return this.compute.getSubnetworks(options, callback); +}; + /** * Delete the network. * diff --git a/lib/compute/region.js b/lib/compute/region.js index 6351aa9bd78..d0e13eac122 100644 --- a/lib/compute/region.js +++ b/lib/compute/region.js @@ -30,6 +30,12 @@ var nodeutil = require('util'); */ var Address = require('./address.js'); +/** + * @type {module:compute/network} + * @private + */ +var Network = require('./network.js'); + /** * @type {module:compute/operation} * @private @@ -48,6 +54,12 @@ var Rule = require('./rule.js'); */ var ServiceObject = require('../common/service-object.js'); +/** + * @type {module:compute/subnetwork} + * @private + */ +var Subnetwork = require('./subnetwork.js'); + /** * @type {module:common/stream-router} * @private @@ -214,6 +226,80 @@ Region.prototype.createAddress = function(name, options, callback) { }); }; +/** + * Create a subnetwork in this region. + * + * @resource [Subnetwork Resource]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks#resource} + * @resource [Subnetwork: insert API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks/insert} + * + * @param {string} name - Name of the subnetwork. + * @param {object} config - See a + * [Subnetwork resource](https://cloud.google.com/compute/docs/reference/v1/subnetworks#resource). + * @param {module:compute/network|string} config.network - The network to which + * this subnetwork belongs. **Only networks that are in the distributed mode + * can have subnetworks.** + * @param {string} config.range - The range of internal addresses that + * are owned by this subnetwork. [CIDR](http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) range + * of addresses that are legal on this network. (Alias for + * `config.ipCidrRange`) + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/subnetwork} callback.subnetwork - The created + * Subnetwork object. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * var config = { + * network: 'network1', + * range: '10.0.1.0/24' + * }; + * + * function callback(err, subnetwork, operation, apiResponse) { + * // `subnetwork` is a Subnetwork object. + * + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * } + * + * region.createSubnetwork('new-subnetwork-name', config, callback); + */ +Region.prototype.createSubnetwork = function(name, config, callback) { + var self = this; + + var body = extend({}, config, { + name: name + }); + + if (body.network instanceof Network) { + body.network = body.network.formattedName; + } + + if (body.range) { + body.ipCidrRange = body.range; + delete body.range; + } + + this.request({ + method: 'POST', + uri: '/subnetworks', + json: body + }, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var subnetwork = self.subnetwork(name); + + var operation = self.operation(resp.name); + operation.metadata = resp; + + callback(null, subnetwork, operation, resp); + }); +}; + /** * Create a forwarding rule in this region. * @@ -582,6 +668,112 @@ Region.prototype.getRules = function(options, callback) { }); }; +/** + * Get a list of subnetworks in this region. + * + * @resource [Subnetworks Overview]{@link https://cloud.google.com/compute/docs/subnetworks} + * @resource [Subnetworks: list API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks} + * + * @param {object=} options - Subnetwork search options. + * @param {boolean} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {string} options.filter - Search filter in the format of + * `{name} {comparison} {filterString}`. + * - **`name`**: the name of the field to compare + * - **`comparison`**: the comparison operator, `eq` (equal) or `ne` + * (not equal) + * - **`filterString`**: the string to filter to. For string fields, this + * can be a regular expression. + * @param {number} options.maxApiCalls - Maximum number of API calls to make. + * @param {number} options.maxResults - Maximum number of subnetworks to return. + * @param {string} options.pageToken - A previously-returned page token + * representing part of the larger set of results to view. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/subnetworks} callback.subnetworks - Subnetwork objects + * from this region. + * @param {?object} callback.nextQuery - If present, query with this object to + * check for more results. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * region.getSubnetworks(function(err, subnetworks) { + * // `subnetworks` is an array of `Subnetwork` objects. + * }); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * function callback(err, subnetworks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * region.getSubnetworks(nextQuery, callback); + * } + * } + * + * region.getSubnetworks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the subnetworks from this region as a readable object stream. + * //- + * region.getSubnetworks() + * .on('error', console.error) + * .on('data', function(subnetwork) { + * // `subnetwork` is a `Subnetwork` object. + * }) + * .on('end', function() { + * // All subnetworks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * region.getSubnetworks() + * .on('data', function(subnetwork) { + * this.end(); + * }); + */ +Region.prototype.getSubnetworks = function(options, callback) { + var self = this; + + if (is.fn(options)) { + callback = options; + options = {}; + } + + options = options || {}; + + this.request({ + uri: '/subnetworks', + qs: options + }, function(err, resp) { + if (err) { + callback(err, null, null, resp); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var subnetworks = (resp.items || []).map(function(subnetwork) { + var subnetworkInstance = self.subnetwork(subnetwork.name); + subnetworkInstance.metadata = subnetwork; + return subnetworkInstance; + }); + + callback(null, subnetworks, nextQuery, resp); + }); +}; + /** * Get a reference to a Google Compute Engine region operation. * @@ -610,11 +802,31 @@ Region.prototype.rule = function(name) { return new Rule(this, name); }; +/** + * Get a reference to a Google Compute Engine subnetwork in this region. + * + * @resource [Subnetworks Overview]{@link https://cloud.google.com/compute/docs/subnetworks} + * + * @param {string} name - Name of the subnetwork. + * @return {module:compute/subnetwork} + * + * @example + * var subnetwork = region.subnetwork('subnetwork-name'); + */ +Region.prototype.subnetwork = function(name) { + return new Subnetwork(this, name); +}; + /*! Developer Documentation * * These methods can be used with either a callback or as a readable object * stream. `streamRouter` is used to add this dual behavior. */ -streamRouter.extend(Region, ['getAddresses', 'getOperations', 'getRules']); +streamRouter.extend(Region, [ + 'getAddresses', + 'getOperations', + 'getRules', + 'getSubnetworks' +]); module.exports = Region; diff --git a/lib/compute/subnetwork.js b/lib/compute/subnetwork.js new file mode 100644 index 00000000000..350d91b9449 --- /dev/null +++ b/lib/compute/subnetwork.js @@ -0,0 +1,190 @@ +/*! + * Copyright 2016 Google 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. + */ + +/*! + * @module compute/subnetwork + */ + +'use strict'; + +var nodeutil = require('util'); + +/** + * @type {module:common/service-object} + * @private + */ +var ServiceObject = require('../common/service-object.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + + +/*! Developer Documentation + * + * @param {module:region} region - Region this subnetwork belongs to. + * @param {string} name - Name of the subnetwork. + */ +/** + * An Subnetwork object allows you to interact with a Google Compute Engine + * subnetwork. + * + * @resource [Subnetworks Overview]{@link https://cloud.google.com/compute/docs/subnetworks} + * @resource [Subnetwork Resource]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks} + * + * @constructor + * @alias module:compute/subnetwork + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var gce = gcloud.compute(); + * + * var region = gce.region('region-name'); + * + * var subnetwork = region.subnetwork('subnetwork1'); + */ +function Subnetwork(region, name) { + this.name = name; + this.region = region; + + var methods = { + /** + * Create a subnetwork. + * + * @param {object} config - See {module:compute/region#createSubnetwork}. + * + * @example + * var config = { + * // ... + * }; + * + * function callback(err, subnetwork, operation, apiResponse) { + * // `subnetwork` is a Subnetwork object. + * + * // `operation` is an Operation object that can be used to check the + * // status of the request. + * } + * + * subnetwork.create(config, callback); + */ + create: true, + + /** + * Check if the subnetwork exists. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {boolean} callback.exists - Whether the subnetwork exists or not. + * + * @example + * subnetwork.exists(function(err, exists) {}); + */ + exists: true, + + /** + * Get a subnetwork if it exists. + * + * You may optionally use this to "get or create" an object by providing an + * object with `autoCreate` set to `true`. Any extra configuration that is + * normally required for the `create` method must be contained within this + * object as well. + * + * @param {options=} options - Configuration object. + * @param {boolean} options.autoCreate - Automatically create the object if + * it does not exist. Default: `false` + * + * @example + * subnetwork.get(function(err, subnetwork, apiResponse) { + * // `subnetwork` is a Subnetwork object. + * }); + */ + get: true, + + /** + * Get the metadata of this subnetwork. + * + * @resource [Subnetwork Resource]{@link https://cloud.google.com/compute/docs/reference/v1/subnetwork} + * @resource [Subnetwork: get API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetwork/get} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this + * request. + * @param {object} callback.metadata - The subnetwork's metadata. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * subnetwork.getMetadata(function(err, metadata, apiResponse) {}); + */ + getMetadata: true + }; + + ServiceObject.call(this, { + parent: region, + baseUrl: '/subnetworks', + id: this.name, + createMethod: region.createSubnetwork.bind(region), + methods: methods + }); +} + +nodeutil.inherits(Subnetwork, ServiceObject); + +/** + * Delete the subnetwork. + * + * @resource [Subnetworks: delete API Documentation]{@link https://cloud.google.com/compute/docs/reference/v1/subnetworks/delete} + * + * @param {function=} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {module:compute/operation} callback.operation - An operation object + * that can be used to check the status of the request. + * @param {object} callback.apiResponse - The full API response. + * + * @example + * subnetwork.delete(function(err, operation, apiResponse) { + * // `operation` is an Operation object that can be used to check the status + * // of the request. + * }); + */ +Subnetwork.prototype.delete = function(callback) { + callback = callback || util.noop; + + var region = this.region; + + this.request({ + method: 'DELETE', + uri: '' + }, function(err, resp) { + if (err) { + callback(err, null, resp); + return; + } + + var operation = region.operation(resp.name); + operation.metadata = resp; + + callback(null, operation, resp); + }); +}; + +module.exports = Subnetwork; diff --git a/system-test/compute.js b/system-test/compute.js index f0b554de9f3..2c2b80a68b8 100644 --- a/system-test/compute.js +++ b/system-test/compute.js @@ -1129,6 +1129,86 @@ describe('Compute', function() { }); }); + describe('subnetworks', function() { + var NETWORK_NAME = generateName('network'); + var network = compute.network(NETWORK_NAME); + + var SUBNETWORK_NAME = generateName('subnetwork'); + var subnetwork = region.subnetwork(SUBNETWORK_NAME); + + var NETWORK_CONFIG = { + autoCreateSubnetworks: false + }; + + var SUBNETWORK_CONFIG = { + network: 'global/networks/' + NETWORK_NAME, + range: '10.0.1.0/24' + }; + + before(function(done) { + async.series([ + create(network, NETWORK_CONFIG), + create(subnetwork, SUBNETWORK_CONFIG) + ], done); + }); + + it('should have created the subnetwork', function(done) { + subnetwork.getMetadata(function(err, metadata) { + assert.ifError(err); + assert.strictEqual(metadata.name, SUBNETWORK_NAME); + done(); + }); + }); + + it('should get a list of subnetworks', function(done) { + compute.getSubnetworks(function(err, subnetworks) { + assert.ifError(err); + assert(subnetworks.length > 0); + done(); + }); + }); + + it('should get a list of subnetworks in stream mode', function(done) { + var resultsMatched = 0; + + compute.getSubnetworks() + .on('error', done) + .on('data', function() { + resultsMatched++; + }) + .on('end', function() { + assert(resultsMatched > 0); + done(); + }); + }); + + it('should get a list of regional subnetworks', function(done) { + region.getSubnetworks(function(err, subnetworks) { + assert.ifError(err); + assert(subnetworks.length > 0); + done(); + }); + }); + + it('should get a list of regional subnetworks in stream', function(done) { + var resultsMatched = 0; + + region.getSubnetworks() + .on('error', done) + .on('data', function() { + resultsMatched++; + }) + .on('end', function() { + assert(resultsMatched > 0); + done(); + }); + }); + + it('should access a subnetwork through a Region', function(done) { + region.subnetwork(SUBNETWORK_NAME).getMetadata(done); + }); + }); + function generateName(customPrefix) { return TESTS_PREFIX + customPrefix + '-' + Date.now(); } @@ -1154,6 +1234,7 @@ describe('Compute', function() { 'getDisks', 'getInstanceGroups', 'getFirewalls', + 'getSubnetworks', 'getHealthChecks', 'getNetworks', 'getRules', diff --git a/test/compute/index.js b/test/compute/index.js index 1367b91560d..017b412dd7d 100644 --- a/test/compute/index.js +++ b/test/compute/index.js @@ -53,6 +53,7 @@ var fakeStreamRouter = { 'getRules', 'getServices', 'getSnapshots', + 'getSubnetworks', 'getVMs', 'getZones' ]); @@ -78,6 +79,7 @@ function FakeOperation() { function FakeRegion() { this.calledWith_ = slice.call(arguments); this.address = function() { return {}; }; + this.subnetwork = function() { return {}; }; } function FakeRule() { @@ -2031,6 +2033,123 @@ describe('Compute', function() { }); }); + describe('getSubnetworks', function() { + it('should accept only a callback', function(done) { + compute.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); + done(); + }; + + compute.getSubnetworks(assert.ifError); + }); + + it('should make the correct API request', function(done) { + var options = {}; + + compute.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/aggregated/subnetworks'); + assert.strictEqual(reqOpts.qs, options); + done(); + }; + + compute.getSubnetworks(options, assert.ifError); + }); + + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { a: 'b', c: 'd' }; + + beforeEach(function() { + compute.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); + + it('should execute callback with error & API response', function(done) { + compute.getSubnetworks({}, function(err, subnetworks, nextQuery, resp) { + assert.strictEqual(err, error); + assert.strictEqual(subnetworks, null); + assert.strictEqual(nextQuery, null); + assert.strictEqual(resp, apiResponse); + + done(); + }); + }); + }); + + describe('success', function() { + var REGION_NAME = 'region-1'; + var FULL_REGION_NAME = 'regions/' + REGION_NAME; + + var subnetwork = { name: 'subnetwork-1' }; + var apiResponse = { + items: {} + }; + + apiResponse.items[FULL_REGION_NAME] = { + subnetworks: [subnetwork] + }; + + beforeEach(function() { + compute.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); + + it('should create Subnetwork objects from the response', function(done) { + var region = {}; + var fakeSubnetwork = {}; + + compute.region = function(name) { + assert.strictEqual(name, REGION_NAME); + return region; + }; + + region.subnetwork = function(name) { + assert.strictEqual(name, subnetwork.name); + return fakeSubnetwork; + }; + + compute.getSubnetworks({}, function(err, subnetworks, nextQuery, resp) { + assert.ifError(err); + + assert.strictEqual(subnetworks[0], fakeSubnetwork); + assert.strictEqual(subnetworks[0].metadata, subnetwork); + + assert.strictEqual(nextQuery, null); + assert.strictEqual(resp, apiResponse); + + done(); + }); + }); + + it('should build a nextQuery if necessary', function(done) { + var apiResponseWithNextPageToken = extend({}, apiResponse, { + nextPageToken: 'next-page-token' + }); + + var query = { a: 'b', c: 'd' }; + var originalQuery = extend({}, query); + + compute.request = function(reqOpts, callback) { + callback(null, apiResponseWithNextPageToken); + }; + + compute.getSubnetworks(query, function(err, subnetworks, nextQuery) { + assert.ifError(err); + + assert.deepEqual(query, originalQuery); + + assert.deepEqual(nextQuery, extend({}, query, { + pageToken: apiResponseWithNextPageToken.nextPageToken + })); + + done(); + }); + }); + }); + }); + describe('getVMs', function() { it('should work with only a callback', function(done) { compute.request = function(reqOpts) { diff --git a/test/compute/network.js b/test/compute/network.js index fcc40e55411..a0fc4dc6790 100644 --- a/test/compute/network.js +++ b/test/compute/network.js @@ -36,6 +36,9 @@ describe('Network', function() { var Network; var network; + var REGION; + var Region; + var COMPUTE = { projectId: 'project-id', createNetwork: util.noop @@ -45,6 +48,7 @@ describe('Network', function() { pId: COMPUTE.projectId, name: NETWORK_NAME }); + var REGION_NAME = 'region-name'; before(function() { mockery.registerMock( @@ -57,6 +61,7 @@ describe('Network', function() { }); Network = require('../../lib/compute/network.js'); + Region = require('../../lib/compute/region.js'); }); after(function() { @@ -66,6 +71,7 @@ describe('Network', function() { beforeEach(function() { network = new Network(COMPUTE, NETWORK_NAME); + REGION = new Region(COMPUTE, REGION_NAME); }); describe('instantiation', function() { @@ -146,6 +152,37 @@ describe('Network', function() { }); }); + describe('createSubnetwork', function() { + it('should call region.createSubnetwork correctly', function(done) { + var name = 'subnetwork-name'; + var region = {}; + var config = { + a: 'b', + c: 'd', + region: REGION_NAME + }; + + var expectedConfig = extend({}, config, { + network: network.formattedName + }); + delete expectedConfig.region; + + network.compute.region = function(name) { + assert.strictEqual(name, REGION_NAME); + return region; + }; + + region.createSubnetwork = function(name_, config, callback) { + assert.strictEqual(name_, name); + assert.deepEqual(config, expectedConfig); + + callback(); // done(); + }; + + network.createSubnetwork(name, config, done); + }); + }); + describe('delete', function() { it('should call ServiceObject.delete', function(done) { FakeServiceObject.prototype.delete = function() { @@ -280,4 +317,50 @@ describe('Network', function() { assert.strictEqual(network.getFirewalls(), resultOfGetFirewalls); }); }); + + describe('getSubnetworks', function() { + it('should call to compute.getSubnetworks correctly', function(done) { + var options = { a: 'b', c: 'd' }; + var expectedOptions = extend({}, options, { + filter: 'network eq .*' + network.formattedName + }); + + network.compute.getSubnetworks = function(options, callback) { + assert.deepEqual(options, expectedOptions); + callback(); + }; + + network.getSubnetworks(options, done); + }); + + it('should not require options', function(done) { + network.compute.getSubnetworks = function(options, callback) { + callback(); + }; + + network.getSubnetworks(done); + }); + + it('should not require any arguments', function(done) { + network.compute.getSubnetworks = function(options, callback) { + assert.deepEqual(options, { + filter: 'network eq .*' + network.formattedName + }); + assert.strictEqual(typeof callback, 'undefined'); + done(); + }; + + network.getSubnetworks(); + }); + + it('should return the result of calling Compute', function() { + var resultOfGetSubnetworks = {}; + + network.compute.getSubnetworks = function() { + return resultOfGetSubnetworks; + }; + + assert.strictEqual(network.getSubnetworks(), resultOfGetSubnetworks); + }); + }); }); diff --git a/test/compute/region.js b/test/compute/region.js index cb4730dd442..d6ed4c67095 100644 --- a/test/compute/region.js +++ b/test/compute/region.js @@ -37,6 +37,14 @@ function FakeRule() { this.calledWith_ = [].slice.call(arguments); } +function FakeNetwork() { + this.calledWith_ = [].slice.call(arguments); +} + +function FakeSubnetwork() { + this.calledWith_ = [].slice.call(arguments); +} + function FakeServiceObject() { this.calledWith_ = arguments; ServiceObject.apply(this, arguments); @@ -54,7 +62,11 @@ var fakeStreamRouter = { extended = true; methods = arrify(methods); assert.equal(Class.name, 'Region'); - assert.deepEqual(methods, ['getAddresses', 'getOperations', 'getRules']); + assert.deepEqual(methods, [ + 'getAddresses', + 'getOperations', + 'getRules', 'getSubnetworks' + ]); } }; @@ -74,8 +86,10 @@ describe('Region', function() { ); mockery.registerMock('../../lib/common/stream-router.js', fakeStreamRouter); mockery.registerMock('../../lib/compute/address.js', FakeAddress); + mockery.registerMock('../../lib/compute/network.js', FakeNetwork); mockery.registerMock('../../lib/compute/operation.js', FakeOperation); mockery.registerMock('../../lib/compute/rule.js', FakeRule); + mockery.registerMock('../../lib/compute/subnetwork.js', FakeSubnetwork); mockery.enable({ useCleanCache: true, @@ -264,6 +278,120 @@ describe('Region', function() { }); }); + describe('createSubnetwork', function() { + var NAME = 'subnetwork-name'; + var CONFIG = { + a: 'b', + c: 'd', + network: 'network-name' + }; + var EXPECTED_BODY = extend({}, CONFIG, { name: NAME }); + + it('should make the correct API request', function(done) { + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'POST'); + assert.strictEqual(reqOpts.uri, '/subnetworks'); + assert.deepEqual(reqOpts.json, EXPECTED_BODY); + + done(); + }; + + region.createSubnetwork(NAME, CONFIG, assert.ifError); + }); + + describe('config.network', function() { + it('should accept a Network object', function(done) { + var network = new FakeNetwork(); + network.formattedName = 'formatted-name'; + + var config = extend({}, CONFIG, { + network: network + }); + + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.network, network.formattedName); + done(); + }; + + region.createSubnetwork(NAME, config, assert.ifError); + }); + }); + + describe('config.range', function() { + it('should accept and delete a range property', function(done) { + var config = extend({}, CONFIG, { + range: '...' + }); + + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.json.ipCidrRange, config.range); + assert.strictEqual(reqOpts.json.range, undefined); + done(); + }; + + region.createSubnetwork(NAME, config, assert.ifError); + }); + }); + + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { a: 'b', c: 'd' }; + + beforeEach(function() { + region.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); + + it('should execute callback with error & API response', function(done) { + region.createSubnetwork(NAME, CONFIG, function(err, sub, op, resp) { + assert.strictEqual(err, error); + assert.strictEqual(sub, null); + assert.strictEqual(op, null); + assert.strictEqual(resp, apiResponse); + done(); + }); + }); + }); + + describe('success', function() { + var apiResponse = { name: 'operation-name' }; + + beforeEach(function() { + region.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); + + it('should exec cb with Subnetwork, Op & apiResponse', function(done) { + var subnetwork = {}; + var operation = {}; + + region.subnetwork = function(name) { + assert.strictEqual(name, NAME); + return subnetwork; + }; + + region.operation = function(name) { + assert.strictEqual(name, apiResponse.name); + return operation; + }; + + region.createSubnetwork(NAME, CONFIG, function(err, sub, op, resp) { + assert.ifError(err); + + assert.strictEqual(sub, subnetwork); + + assert.strictEqual(op, operation); + assert.strictEqual(op.metadata, resp); + + assert.strictEqual(resp, apiResponse); + done(); + }); + }); + }); + }); + describe('getAddresses', function() { it('should accept only a callback', function(done) { region.request = function(reqOpts) { @@ -567,6 +695,107 @@ describe('Region', function() { }); }); + describe('getSubnetworks', function() { + it('should accept only a callback', function(done) { + region.request = function(reqOpts) { + assert.deepEqual(reqOpts.qs, {}); + done(); + }; + + region.getSubnetworks(assert.ifError); + }); + + it('should make the correct API request', function(done) { + var query = { a: 'b', c: 'd' }; + + region.request = function(reqOpts) { + assert.strictEqual(reqOpts.uri, '/subnetworks'); + assert.strictEqual(reqOpts.qs, query); + + done(); + }; + + region.getSubnetworks(query, assert.ifError); + }); + + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { a: 'b', c: 'd' }; + + beforeEach(function() { + region.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); + + it('should execute callback with error & API response', function(done) { + region.getSubnetworks({}, function(err, subnetworks, nextQuery, resp) { + assert.strictEqual(err, error); + assert.strictEqual(subnetworks, null); + assert.strictEqual(nextQuery, null); + assert.strictEqual(resp, apiResponse); + done(); + }); + }); + }); + + describe('success', function() { + var apiResponse = { + items: [ + { name: 'subnetwork-name' } + ] + }; + + beforeEach(function() { + region.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); + + it('should build a nextQuery if necessary', function(done) { + var nextPageToken = 'next-page-token'; + var apiResponseWithNextPageToken = extend({}, apiResponse, { + nextPageToken: nextPageToken + }); + var expectedNextQuery = { + pageToken: nextPageToken + }; + + region.request = function(reqOpts, callback) { + callback(null, apiResponseWithNextPageToken); + }; + + region.getSubnetworks({}, function(err, subnetworks, nextQuery) { + assert.ifError(err); + + assert.deepEqual(nextQuery, expectedNextQuery); + + done(); + }); + }); + + it('should execute callback with Operations & API resp', function(done) { + var subnetwork = {}; + + region.subnetwork = function(name) { + assert.strictEqual(name, apiResponse.items[0].name); + return subnetwork; + }; + + region.getSubnetworks({}, function(err, subnetworks, nextQuery, resp) { + assert.ifError(err); + + assert.strictEqual(subnetworks[0], subnetwork); + assert.strictEqual(subnetworks[0].metadata, apiResponse.items[0]); + + assert.strictEqual(resp, apiResponse); + + done(); + }); + }); + }); + }); + describe('operation', function() { var NAME = 'operation-name'; @@ -588,4 +817,16 @@ describe('Region', function() { assert.strictEqual(rule.calledWith_[1], NAME); }); }); + + describe('subnetwork', function() { + var NAME = 'subnetwork-name'; + + it('should return a Subnetwork object', function() { + var subnetwork = region.subnetwork(NAME); + assert(subnetwork instanceof FakeSubnetwork); + assert.strictEqual(subnetwork.calledWith_[0], region); + assert.strictEqual(subnetwork.calledWith_[1], NAME); + }); + }); }); + diff --git a/test/compute/subnetwork.js b/test/compute/subnetwork.js new file mode 100644 index 00000000000..eaaaf0c2a46 --- /dev/null +++ b/test/compute/subnetwork.js @@ -0,0 +1,178 @@ +/** + * Copyright 2016 Google 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. + */ + +'use strict'; + +var assert = require('assert'); +var extend = require('extend'); +var mockery = require('mockery-next'); +var nodeutil = require('util'); + +var ServiceObject = require('../../lib/common/service-object.js'); +var util = require('../../lib/common/util.js'); + +function FakeServiceObject() { + this.calledWith_ = arguments; + ServiceObject.apply(this, arguments); +} + +nodeutil.inherits(FakeServiceObject, ServiceObject); + +describe('Subnetwork', function() { + var Subnetwork; + var subnetwork; + + var SUBNETWORK_NAME = 'subnetwork_name'; + var REGION_NAME = 'region-1'; + var REGION = { + createSubnetwork: util.noop, + name: REGION_NAME + }; + + before(function() { + mockery.registerMock( + '../../lib/common/service-object.js', + FakeServiceObject + ); + mockery.enable({ + useCleanCache: true, + warnOnUnregistered: false + }); + + Subnetwork = require('../../lib/compute/subnetwork.js'); + }); + + after(function() { + mockery.deregisterAll(); + mockery.disable(); + }); + + beforeEach(function() { + subnetwork = new Subnetwork(REGION, SUBNETWORK_NAME); + }); + + describe('instantiation', function() { + it('should localize the name', function() { + assert.strictEqual(subnetwork.name, SUBNETWORK_NAME); + }); + + it('should localize the region', function() { + assert.strictEqual(subnetwork.region, REGION); + }); + + it('should inherit from ServiceObject', function() { + var createSubnetworkBound = {}; + + var regionInstance = extend({}, REGION, { + createSubnetwork: { + bind: function(context) { + assert.strictEqual(context, regionInstance); + return createSubnetworkBound; + } + } + }); + + var subnetwork = new Subnetwork(regionInstance, SUBNETWORK_NAME); + assert(subnetwork instanceof ServiceObject); + + var calledWith = subnetwork.calledWith_[0]; + + assert.strictEqual(calledWith.parent, regionInstance); + assert.strictEqual(calledWith.baseUrl, '/subnetworks'); + assert.strictEqual(calledWith.id, SUBNETWORK_NAME); + assert.strictEqual(calledWith.createMethod, createSubnetworkBound); + assert.deepEqual(calledWith.methods, { + create: true, + exists: true, + get: true, + getMetadata: true + }); + }); + }); + + describe('delete', function() { + it('should make the correct API request', function(done) { + subnetwork.request = function(reqOpts) { + assert.strictEqual(reqOpts.method, 'DELETE'); + assert.strictEqual(reqOpts.uri, ''); + done(); + }; + + subnetwork.delete(assert.ifError); + }); + + describe('error', function() { + var error = new Error('Error.'); + var apiResponse = { a: 'b', c: 'd' }; + + beforeEach(function() { + subnetwork.request = function(reqOpts, callback) { + callback(error, apiResponse); + }; + }); + + it('should return an error if the request fails', function(done) { + subnetwork.delete(function(err, operation, apiResponse_) { + assert.strictEqual(err, error); + assert.strictEqual(operation, null); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should not require a callback', function() { + assert.doesNotThrow(function() { + subnetwork.delete(); + }); + }); + }); + + describe('success', function() { + var apiResponse = { + name: 'op-name' + }; + + beforeEach(function() { + subnetwork.request = function(reqOpts, callback) { + callback(null, apiResponse); + }; + }); + + it('should execute callback with Operation & Response', function(done) { + var operation = {}; + + subnetwork.region.operation = function(name) { + assert.strictEqual(name, apiResponse.name); + return operation; + }; + + subnetwork.delete(function(err, operation_, apiResponse_) { + assert.ifError(err); + assert.strictEqual(operation_, operation); + assert.strictEqual(operation_.metadata, apiResponse); + assert.strictEqual(apiResponse_, apiResponse); + done(); + }); + }); + + it('should not require a callback', function() { + assert.doesNotThrow(function() { + subnetwork.delete(); + }); + }); + }); + }); +}); diff --git a/test/docs.js b/test/docs.js index e458b6750ae..ef0362036f3 100644 --- a/test/docs.js +++ b/test/docs.js @@ -16,7 +16,6 @@ 'use strict'; -var assert = require('assert'); var fs = require('fs'); var gcloud = require('../'); var glob = require('glob'); @@ -35,9 +34,8 @@ function runCodeInSandbox(code, sandbox) { }); } catch(err) { // rethrow the error with code for context and resolving issues faster. - var lineCol = err.stack.match('assert-code\.vm:(.+):(.+)'); + var lineCol = err.stack.match('assert-code\.vm:(.+)'); lineCol.line = lineCol[1]; - lineCol.col = lineCol[2]; var lines = code.split('\n') .filter(function(line, index) { @@ -133,7 +131,7 @@ describe('documentation', function() { displayName += '#' + method.id; it('should run ' + displayName + ' example without errors', function() { - assert.doesNotThrow(runCodeInSandbox.bind(null, code, sandbox)); + runCodeInSandbox(code, sandbox); }); }); });