Node.js SDK 1.9.0 release
This commit is contained in:
Rajesh Nagpal 2016-07-07 17:55:01 -07:00
Родитель a26f895e1f
Коммит 6bcf401f8e
16 изменённых файлов: 443 добавлений и 104 удалений

Просмотреть файл

@ -31,7 +31,9 @@
<Compile Include="lib\endpointDiscoveryRetryPolicy.js" />
<Compile Include="lib\globalEndpointManager.js" />
<Compile Include="lib\range.js" />
<Compile Include="lib\retryOptions.js" />
<Compile Include="lib\retryUtility.js" />
<Compile Include="lib\resourceThrottleRetryPolicy.js" />
<Compile Include="test\baseTests.js">
<TestFramework>Mocha</TestFramework>
</Compile>

Просмотреть файл

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}") = "DocumentDB.Node.master", "DocumentDB.Node.master.njsproj", "{411C2C02-66EF-40C8-A964-AF8FDACF3961}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{411C2C02-66EF-40C8-A964-AF8FDACF3961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{411C2C02-66EF-40C8-A964-AF8FDACF3961}.Debug|Any CPU.Build.0 = Debug|Any CPU
{411C2C02-66EF-40C8-A964-AF8FDACF3961}.Release|Any CPU.ActiveCfg = Release|Any CPU
{411C2C02-66EF-40C8-A964-AF8FDACF3961}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Просмотреть файл

@ -1,3 +1,13 @@
## Changes in 1.9.0 : ##
- Added the retry policy support for throttle requests(Request Rate too large - Error code 429). By default, we now retry 9 times for each request when we get a 429, honoring the retryAfter time in the response header.
We also allow setting a fixed retry interval time as part of the RetryOptions property on the ConnectionPolicy object if you want to ignore the retryAfter time returned by server between the retries. We will also
now wait for max 30 sec for each request that is being throttled(irrespective of retry count) and will return the request with a 429. This time can also be overriden in the RetryOptions property on ConnectionPolicy object.
- We also return x-ms-throttle-retry-count and x-ms-throttle-retry-wait-time-ms as the response headers in every request to denote the throttle retry count and the cummulative time the request waited between the retries.
- Introducing a RetryOptions class and exposing the RetryOptions property on ConnectionPolicy class that can be used to override some of the default retry options we set.
## Changes in 1.8.0 : ##
- Added the support for geo-replicated database accounts.

Просмотреть файл

@ -64,7 +64,7 @@ var AuthHandler = {
if (resourceTokens[resourceId]) {
return resourceTokens[resourceId];
} else {
var pathParts = path.split("/");
var pathParts = path && path.split("/") || [];
var resourceTypes = ["dbs", "colls", "docs", "sprocs", "udfs", "triggers", "users", "permissions", "attachments", "media", "conflicts", "offers"];
// Get the last resource id from the path and get it's token from resourceTokens
for (var i = pathParts.length - 1; i >= 0; i--) {

Просмотреть файл

@ -158,9 +158,13 @@ var Constants = {
Name: 'name',
DatabaseAccountEndpoint: 'databaseAccountEndpoint',
// Client generated retry count response header
ThrottleRetryCount: "x-ms-throttle-retry-count",
ThrottleRetryWaitTimeInMs: "x-ms-throttle-retry-wait-time-ms",
CurrentVersion: "2016-05-30",
UserAgent: "documentdb-nodejs-sdk-1.8.0",
UserAgent: "documentdb-nodejs-sdk-1.9.0",
DefaultPrecisions: {
DefaultNumberHashPrecision: 3,

Просмотреть файл

@ -27,6 +27,7 @@ var Base = require("./base")
, AzureDocuments = require("./documents")
, QueryIterator = require("./queryIterator")
, RequestHandler = require("./request")
, RetryOptions = require("./retryOptions")
, GlobalEndpointManager = require("./globalEndpointManager")
, Constants = require("./constants");
@ -1312,8 +1313,8 @@ var DocumentClient = Base.defineClass(
};
if (options.partitionKey === undefined) {
this.getPartitionKeyDefinition(Base.getCollectionLink(documentLink), function (err, partitionKeyDefinition) {
if (err) return callback(err);
this.getPartitionKeyDefinition(Base.getCollectionLink(documentLink), function (err, partitionKeyDefinition, response, headers) {
if (err) return callback(err, response, headers);
options.partitionKey = that.extractPartitionKey(newDocument, partitionKeyDefinition);
task();
@ -2017,8 +2018,8 @@ var DocumentClient = Base.defineClass(
};
if (options.partitionKey === undefined) {
this.getPartitionKeyDefinition(collectionLink, function (err, partitionKeyDefinition) {
if (err) return callback(err);
this.getPartitionKeyDefinition(collectionLink, function (err, partitionKeyDefinition, response, headers) {
if (err) return callback(err, response, headers);
options.partitionKey = that.extractPartitionKey(body, partitionKeyDefinition);
task();
@ -2057,8 +2058,8 @@ var DocumentClient = Base.defineClass(
};
if (options.partitionKey === undefined) {
this.getPartitionKeyDefinition(collectionLink, function (err, partitionKeyDefinition) {
if (err) return callback(err);
this.getPartitionKeyDefinition(collectionLink, function (err, partitionKeyDefinition, response, headers) {
if (err) return callback(err, response, headers);
options.partitionKey = that.extractPartitionKey(body, partitionKeyDefinition);
task();
@ -2182,6 +2183,10 @@ var DocumentClient = Base.defineClass(
},
/** @ignore */
/** Gets the partition key definition first by looking into the cache otherwise by reading the collection.
* @param {string} collectionLink - Link to the collection whose partition key needs to be extracted.
* @param {function} callback - The arguments to the callback are(in order): error, partitionKeyDefinition, response object and response headers
*/
getPartitionKeyDefinition: function (collectionLink, callback) {
// $ISSUE-felixfan-2016-03-17: Make name based path and link based path use the same key
// $ISSUE-felixfan-2016-03-17: Refresh partitionKeyDefinitionCache when necessary
@ -2191,9 +2196,9 @@ var DocumentClient = Base.defineClass(
var that = this;
this.readCollection(collectionLink, function (err, collection) {
if (err) return callback(err);
callback(err, that.partitionKeyDefinitionCache[collectionLink]);
this.readCollection(collectionLink, function (err, collection, headers) {
if (err) return callback(err, undefined, collection, headers);
callback(err, that.partitionKeyDefinitionCache[collectionLink], collection, headers);
});
},
@ -2519,6 +2524,7 @@ var DocumentClient = Base.defineClass(
if (typeof exports !== "undefined") {
exports.DocumentClient = DocumentClient;
exports.DocumentBase = AzureDocuments;
exports.RetryOptions = RetryOptions;
exports.Base = Base;
exports.Constants = Constants;
}

Просмотреть файл

@ -23,7 +23,8 @@ SOFTWARE.
"use strict";
var Base = require("./base");
var Base = require("./base"),
RetryOptions = require("./retryOptions");
//SCRIPT START
var AzureDocuments = Base.defineClass(null, null,
@ -265,10 +266,13 @@ var AzureDocuments = Base.defineClass(null, null,
/**
* @global
* Represents the Connection policy assocated with a DocumentClient.
* @property {string} MediaReadMode - Attachment content (aka media) download mode. Should be one of the values of {@link MediaReadMode}
* @property {number} MediaRequestTimeout - Time to wait for response from network peer for attachment content (aka media) operations. Represented in milliseconds.
* @property {number} RequestTimeout - Request timeout (time to wait for response from network peer). Represented in milliseconds.
* Represents the Connection policy associated with a DocumentClient.
* @property {string} MediaReadMode - Attachment content (aka media) download mode. Should be one of the values of {@link MediaReadMode}
* @property {number} MediaRequestTimeout - Time to wait for response from network peer for attachment content (aka media) operations. Represented in milliseconds.
* @property {number} RequestTimeout - Request timeout (time to wait for response from network peer). Represented in milliseconds.
* @property {bool} EnableEndpointDiscovery - Flag to enable/disable automatic redirecting of requests based on read/write operations.
* @property {Array} PreferredLocations - List of azure regions to be used as preferred locations for read requests.
* @property {RetryOptions} RetryOptions - RetryOptions instance which defines several configurable properties used during retry.
*/
ConnectionPolicy: Base.defineClass(function() {
Object.defineProperty(this, "_defaultRequestTimeout", {
@ -292,6 +296,7 @@ var AzureDocuments = Base.defineClass(null, null,
this.RequestTimeout = this._defaultRequestTimeout;
this.EnableEndpointDiscovery = true;
this.PreferredLocations = [];
this.RetryOptions = new RetryOptions();
})
}
);

Просмотреть файл

@ -28,10 +28,8 @@ var Base = require("./base");
//SCRIPT START
/**
* This class implements the retry policy for endpoint discovery.
* @property {object} globalEndpointManager - The GlobalEndpointManager instance.
* @property {int} _maxRetryAttemptCount - Max number of retry attempts to perform.
* @property {int} _currentRetryAttemptCount - Current retry attempt count.
* @property {int} _retryAfterInMilliseconds - Retry interval in milliseconds.
*/
var EndpointDiscoveryRetryPolicy = Base.defineClass(
/**
@ -39,72 +37,20 @@ var EndpointDiscoveryRetryPolicy = Base.defineClass(
* @param {object} globalEndpointManager - The GlobalEndpointManager instance.
*/
function (globalEndpointManager) {
this.globalEndpointManager = globalEndpointManager;
this._maxRetryAttemptCount = EndpointDiscoveryRetryPolicy.maxRetryAttemptCount;
this._currentRetryAttemptCount = 0;
this._retryAfterInMilliseconds = EndpointDiscoveryRetryPolicy.retryAfterInMilliseconds;
},
{
/**
* Applies the retry policy for the created request object.
* @param {object} body - a dictionary containing 'buffer' and 'stream' keys to hold corresponding buffer or stream body, null otherwise.
* @param {function} createRequestObjectFunc - function that creates the request object.
* @param {object} connectionPolicy - an instance of ConnectionPolicy that has the connection configs.
* @param {RequestOptions} requestOptions - The request options.
* @param {function} callback - the callback that will be called when the response is retrieved and processed.
*/
apply: function (body, createRequestObjectFunc, connectionPolicy, requestOptions, callback) {
var that = this;
var httpsRequest = createRequestObjectFunc(connectionPolicy, requestOptions, function (err, response, headers) {
// Check if it 's a write-forbidden exception, which has StatusCode=403 and SubStatus=3 and whether EnableEndpointDiscovery is set to True
if (err) {
if (that._currentRetryAttemptCount < that._maxRetryAttemptCount && err.code === 403 && err.substatus === 3 && that.globalEndpointManager.enableEndpointDiscovery) {
that._currentRetryAttemptCount++;
console.log("Write region was changed, refreshing the regions list from database account and will retry the request.");
that.globalEndpointManager.refreshEndpointList(function (writeEndpoint, readEndpoint) {
that.globalEndpointManager.setWriteEndpoint(writeEndpoint);
that.globalEndpointManager.setReadEndpoint(readEndpoint);
setTimeout(function () {
that.apply(body, createRequestObjectFunc, connectionPolicy, requestOptions, callback);
}, that._retryAfterInMilliseconds);
});
}
else {
console.log("Operation will NOT be retried or has maxed out the retry count.", err);
// This is a test hook to call a callback after the retry counts have been exhausted
if (EndpointDiscoveryRetryPolicy.retryFinishCallback) {
EndpointDiscoveryRetryPolicy.retryFinishCallback(that._currentRetryAttemptCount, that._maxRetryAttemptCount, function () {
callback(err, response, headers);
return;
});
} else {
callback(err, response, headers);
return;
}
}
} else {
callback(undefined, response, headers);
return;
}
});
if (httpsRequest) {
if (body["stream"] !== null) {
body["stream"].pipe(httpsRequest);
} else if (body["buffer"] !== null) {
httpsRequest.write(body["buffer"]);
httpsRequest.end();
} else {
httpsRequest.end();
}
}
}
},
{
maxRetryAttemptCount : 120,
retryAfterInMilliseconds : 1000,
retryFinishCallback: undefined
}
);
//SCRIPT END

Просмотреть файл

@ -34,7 +34,7 @@ var Base = require("./base")
* @property {object} client - The document client instance.
* @property {string} defaultEndpoint - The endpoint used to create the client instance.
* @property {bool} enableEndpointDiscovery - Flag to enable/disable automatic redirecting of requests based on read/write operations.
* @property {Array} preferredLocations - List of azure regions to be used as preferred locations for any requests.
* @property {Array} preferredLocations - List of azure regions to be used as preferred locations for read requests.
* @property {bool} isEndpointCacheInitialized - Flag to determine whether the endpoint cache is initialized or not.
*/
var GlobalEndpointManager = Base.defineClass(

Просмотреть файл

@ -32,6 +32,7 @@ if (typeof exports !== "undefined") {
exports.DocumentBase = Client.DocumentBase;
exports.Base = Client.Base;
exports.Constants = Client.Constants;
exports.RetryOptions = Client.RetryOptions;
exports.Range = Range.Range;
exports.RangePartitionResolver = Range.RangePartitionResolver;
exports.HashPartitionResolver = Hash.HashPartitionResolver;

Просмотреть файл

@ -56,8 +56,7 @@ function createRequestObject(connectionPolicy, requestOptions, callback){
var httpsRequest = https.request(requestOptions, function(response) {
// In case of media response, return the stream to the user and the user will need to handle reading the stream.
if (isMedia && connectionPolicy.MediaReadMode === Documents.MediaReadMode.Streamed) {
callback(undefined, response, response.headers);
return;
return callback(undefined, response, response.headers);
}
var data = "";
@ -66,14 +65,8 @@ function createRequestObject(connectionPolicy, requestOptions, callback){
});
response.on("end", function() {
if (response.statusCode >= 400) {
if (Constants.HttpHeaders.SubStatus in response.headers) {
var subStatus = parseInt(response.headers[Constants.HttpHeaders.SubStatus]);
callback({ code: response.statusCode, substatus: subStatus, body: data }, undefined, response.headers);
} else {
callback({ code: response.statusCode, body: data }, undefined, response.headers);
return callback(getErrorBody(response, data), undefined, response.headers);
}
return;
}
var result;
try {
@ -83,8 +76,7 @@ function createRequestObject(connectionPolicy, requestOptions, callback){
result = data.length > 0 ? JSON.parse(data) : undefined;
}
} catch (exception) {
callback(exception);
return;
return callback(exception);
}
callback(undefined, result, response.headers);
@ -101,7 +93,7 @@ function createRequestObject(connectionPolicy, requestOptions, callback){
socket.once("timeout", onTimeout);
httpsRequest.once("response", function () {
socket.removeListener("timeout", onTimeout);
socket.removeListener("timeout", onTimeout);
});
});
@ -109,6 +101,25 @@ function createRequestObject(connectionPolicy, requestOptions, callback){
return httpsRequest;
}
/**
* Constructs the error body from the response and the data returned from the request.
* @param {object} response - response object returned from the executon of a request.
* @param {object} data - the data body returned from the executon of a request.
*/
function getErrorBody(response, data) {
var errorBody = { code: response.statusCode, body: data };
if (Constants.HttpHeaders.SubStatus in response.headers) {
errorBody.substatus = parseInt(response.headers[Constants.HttpHeaders.SubStatus]);
}
if (Constants.HttpHeaders.RetryAfterInMilliseconds in response.headers) {
errorBody.retryAfterInMilliseconds = parseInt(response.headers[Constants.HttpHeaders.RetryAfterInMilliseconds]);
}
return errorBody;
}
var RequestHandler = {
_createRequestObjectStub: function (connectionPolicy, requestOptions, callback) {
return createRequestObject(connectionPolicy, requestOptions, callback);

Просмотреть файл

@ -0,0 +1,86 @@
/*
The MIT License (MIT)
Copyright (c) 2014 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
"use strict";
var Base = require("./base");
//SCRIPT START
/**
* This class implements the resource throttle retry policy for requests.
* @property {int} _maxRetryAttemptCount - Max number of retries to be performed for a request.
* @property {int} _fixedRetryIntervalInMilliseconds - Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response.
* @property {int} _maxWaitTimeInMilliseconds - Max wait time in milliseconds to wait for a request while the retries are happening.
* @property {int} currentRetryAttemptCount - Current retry attempt count.
* @property {int} cummulativeWaitTimeinMilliseconds - Cummulative wait time in milliseconds for a request while the retries are happening.
*/
var ResourceThrottleRetryPolicy = Base.defineClass(
/**
* @constructor ResourceThrottleRetryPolicy
* @param {int} maxRetryAttemptCount - Max number of retries to be performed for a request.
* @param {int} fixedRetryIntervalInMilliseconds - Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response.
* @param {int} maxWaitTimeInSeconds - Max wait time in seconds to wait for a request while the retries are happening.
*/
function (maxRetryAttemptCount, fixedRetryIntervalInMilliseconds, maxWaitTimeInSeconds) {
this._maxRetryAttemptCount = maxRetryAttemptCount;
this._fixedRetryIntervalInMilliseconds = fixedRetryIntervalInMilliseconds;
this._maxWaitTimeInMilliseconds = maxWaitTimeInSeconds * 1000;
this.currentRetryAttemptCount = 0;
this.cummulativeWaitTimeinMilliseconds = 0;
},
{
/**
* Determines whether the request should be retried or not.
* @param {object} err - Error returned by the request.
* @param {function} callback - The callback function which takes bool argument which specifies whether the request will be retried or not.
*/
shouldRetry: function (err, callback) {
if (err) {
if (this.currentRetryAttemptCount < this._maxRetryAttemptCount) {
this.currentRetryAttemptCount++;
this.retryAfterInMilliseconds = 0;
if (this._fixedRetryIntervalInMilliseconds) {
this.retryAfterInMilliseconds = this._fixedRetryIntervalInMilliseconds;
} else if (err.retryAfterInMilliseconds) {
this.retryAfterInMilliseconds = err.retryAfterInMilliseconds;
}
if (this.cummulativeWaitTimeinMilliseconds < this._maxWaitTimeInMilliseconds) {
this.cummulativeWaitTimeinMilliseconds += this.retryAfterInMilliseconds;
return callback(true);
}
}
}
return callback(false);
}
},
{
THROTTLE_STATUS_CODE: 429
}
);
//SCRIPT END
if (typeof exports !== "undefined") {
module.exports = ResourceThrottleRetryPolicy;
}

Просмотреть файл

@ -0,0 +1,66 @@
/*
The MIT License (MIT)
Copyright (c) 2014 Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
"use strict";
var Base = require("./base");
//SCRIPT START
/**
* Represents the Retry policy assocated with throttled requests.
* @property {int} [MaxRetryAttemptCount] - Max number of retries to be performed for a request. Default value 9.
* @property {int} [FixedRetryIntervalInMilliseconds] - Fixed retry interval in milliseconds to wait between each retry ignoring the retryAfter returned as part of the response.
* @property {int} [MaxWaitTimeInSeconds] - Max wait time in seconds to wait for a request while the retries are happening. Default value 30 seconds.
*/
var RetryOptions = Base.defineClass(
function RetryOptions(maxRetryAttemptCount, fixedRetryIntervalInMilliseconds, maxWaitTimeInSeconds) {
this._maxRetryAttemptCount = maxRetryAttemptCount || 9;
this._fixedRetryIntervalInMilliseconds = fixedRetryIntervalInMilliseconds;
this._maxWaitTimeInSeconds = maxWaitTimeInSeconds || 30;
Object.defineProperty(this, "MaxRetryAttemptCount", {
get: function () {
return this._maxRetryAttemptCount;
},
enumerable: true
});
Object.defineProperty(this, "FixedRetryIntervalInMilliseconds", {
get: function () {
return this._fixedRetryIntervalInMilliseconds;
},
enumerable: true
});
Object.defineProperty(this, "MaxWaitTimeInSeconds", {
get: function () {
return this._maxWaitTimeInSeconds;
},
enumerable: true
});
})
//SCRIPT END
if (typeof exports !== "undefined") {
module.exports = RetryOptions;
}

Просмотреть файл

@ -24,7 +24,9 @@ SOFTWARE.
"use strict";
var Base = require("./base"),
EndpointDiscoveryRetryPolicy = require("./endpointDiscoveryRetryPolicy");
Constants = require("./constants"),
EndpointDiscoveryRetryPolicy = require("./endpointDiscoveryRetryPolicy"),
ResourceThrottleRetryPolicy = require("./resourceThrottleRetryPolicy");
//SCRIPT START
var RetryUtility = {
@ -37,9 +39,67 @@ var RetryUtility = {
* @param {RequestOptions} requestOptions - The request options.
* @param {function} callback - the callback that will be called when the request is finished executing.
*/
execute: function (globalEndpointManager, body, createRequestObjectStub, connectionPolicy, requestOptions, callback) {
execute: function (globalEndpointManager, body, createRequestObjectFunc, connectionPolicy, requestOptions, callback) {
var endpointDiscoveryRetryPolicy = new EndpointDiscoveryRetryPolicy(globalEndpointManager);
endpointDiscoveryRetryPolicy.apply(body, createRequestObjectStub, connectionPolicy, requestOptions, callback);
var resourceThrottleRetryPolicy = new ResourceThrottleRetryPolicy(connectionPolicy.RetryOptions.MaxRetryAttemptCount,
connectionPolicy.RetryOptions.FixedRetryIntervalInMilliseconds,
connectionPolicy.RetryOptions.MaxWaitTimeInSeconds);
this.apply(body, createRequestObjectFunc, connectionPolicy, requestOptions, endpointDiscoveryRetryPolicy, resourceThrottleRetryPolicy, callback);
},
/**
* Applies the retry policy for the created request object.
* @param {object} body - a dictionary containing 'buffer' and 'stream' keys to hold corresponding buffer or stream body, null otherwise.
* @param {function} createRequestObjectFunc - function that creates the request object.
* @param {object} connectionPolicy - an instance of ConnectionPolicy that has the connection configs.
* @param {RequestOptions} requestOptions - The request options.
* @param {EndpointDiscoveryRetryPolicy} endpointDiscoveryRetryPolicy - The endpoint discovery retry policy instance.
* @param {ResourceThrottleRetryPolicy} resourceThrottleRetryPolicy - The resource throttle retry policy instance.
* @param {function} callback - the callback that will be called when the response is retrieved and processed.
*/
apply: function (body, createRequestObjectFunc, connectionPolicy, requestOptions, endpointDiscoveryRetryPolicy, resourceThrottleRetryPolicy, callback) {
var that = this;
var httpsRequest = createRequestObjectFunc(connectionPolicy, requestOptions, function (err, response, headers) {
if (err) {
var retryPolicy = null;
headers = headers || {};
if (err.code === EndpointDiscoveryRetryPolicy.FORBIDDEN_STATUS_CODE && err.substatus === EndpointDiscoveryRetryPolicy.WRITE_FORBIDDEN_SUB_STATUS_CODE) {
retryPolicy = endpointDiscoveryRetryPolicy;
} else if (err.code === ResourceThrottleRetryPolicy.THROTTLE_STATUS_CODE) {
retryPolicy = resourceThrottleRetryPolicy;
}
if (retryPolicy) {
retryPolicy.shouldRetry(err, function (shouldRetry) {
if (!shouldRetry) {
headers[Constants.ThrottleRetryCount] = resourceThrottleRetryPolicy.currentRetryAttemptCount;
headers[Constants.ThrottleRetryWaitTimeInMs] = resourceThrottleRetryPolicy.cummulativeWaitTimeinMilliseconds;
return callback(err, response, headers);
} else {
setTimeout(function () {
that.apply(body, createRequestObjectFunc, connectionPolicy, requestOptions, endpointDiscoveryRetryPolicy, resourceThrottleRetryPolicy, callback);
}, retryPolicy.retryAfterInMilliseconds);
return;
}
});
return;
}
}
headers[Constants.ThrottleRetryCount] = resourceThrottleRetryPolicy.currentRetryAttemptCount;
headers[Constants.ThrottleRetryWaitTimeInMs] = resourceThrottleRetryPolicy.cummulativeWaitTimeinMilliseconds;
return callback(err, response, headers);
});
if (httpsRequest) {
if (body["stream"] !== null) {
body["stream"].pipe(httpsRequest);
} else if (body["buffer"] !== null) {
httpsRequest.write(body["buffer"]);
httpsRequest.end();
} else {
httpsRequest.end();
}
}
}
}
//SCRIPT END

Просмотреть файл

@ -9,7 +9,7 @@
"database",
"cloud"
],
"version": "1.8.0",
"version": "1.9.0",
"author": "Microsoft Corporation",
"main": "./index.js",
"engine": {

Просмотреть файл

@ -34,7 +34,9 @@ var Base = lib.Base,
Constants = lib.Constants,
Range = lib.Range,
RangePartitionResolver = lib.RangePartitionResolver,
HashPartitionResolver = lib.HashPartitionResolver;
HashPartitionResolver = lib.HashPartitionResolver,
AzureDocuments = lib.AzureDocuments,
RetryOptions = lib.RetryOptions;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
@ -2202,24 +2204,16 @@ describe("NodeJS CRUD Tests", function () {
});
describe("Validate client request timeout", function () {
var timeoutTest = function (isNameBased, done) {
it("[nativeApi] Client Should throw exception", function (done) {
var connectionPolicy = new DocumentBase.ConnectionPolicy();
// making timeout 1 ms to make sure it will throw
connectionPolicy.RequestTimeout = 1;
// making timeout 5 ms to make sure it will throw(create database request takes 10ms-15ms to finish on emulator)
connectionPolicy.RequestTimeout = 5;
var client = new DocumentDBClient(host, { masterKey: masterKey }, connectionPolicy);
// create database
client.createDatabase({ id: "sample database" }, function (err, db) {
assert.equal(err.code, "ECONNRESET", "client should throw exception");
done();
});
};
it("[nativeApi] Client Should throw exception [name based]", function (done) {
timeoutTest(true, done);
});
it("[nativeApi] Client Should throw exception [rid based]", function (done) {
timeoutTest(false, done);
});
});
@ -3351,12 +3345,137 @@ describe("NodeJS CRUD Tests", function () {
it("CRUD operations", function (done) { test(false, done) });
it("CRUD operations with upsert", function (done) { test(true, done) });
});
describe("retry policy tests", function () {
var request = require("../lib/request");
var AzureDocuments = require("../lib/documents");
var ResourceThrottleRetryPolicy = require("../lib/resourceThrottleRetryPolicy");
this.timeout(300000);
var collectionDefinition = {
id: "sample collection"
};
var documentDefinition = {
id: "doc",
name: "sample document",
key: "value"
};
var connectionPolicy = new AzureDocuments.ConnectionPolicy();
// mocked database account to return the WritableLocations and ReadableLocations
// set with the default endpoint
var mockGetDatabaseAccount = function (options, callback) {
var databaseAccount = new AzureDocuments.DatabaseAccount();
callback(undefined, databaseAccount);
}
var retryAfterInMilliseconds = 1000;
// mocked request object stub that calls the callback with 429 throttling error
var mockCreateRequestObjectStub = function (connectionPolicy, requestOptions, callback) {
callback({ code: 429, body: "Request rate is too large", retryAfterInMilliseconds: retryAfterInMilliseconds });
}
it("throttle retry policy test default retryAfter", function (done) {
connectionPolicy.RetryOptions = new RetryOptions(5);
var client = new DocumentDBClient(host, { masterKey: masterKey }, connectionPolicy);
client.createDatabase({ "id": "sample database" }, function (err, db) {
assert.equal(err, undefined, "error creating database");
client.createCollection(db._self, collectionDefinition, function (err, collection) {
assert.equal(err, undefined, "error creating collection");
var originalGetDatabaseAccount = client.getDatabaseAccount;
client.getDatabaseAccount = mockGetDatabaseAccount;
var originalCreateRequestObjectStub = request._createRequestObjectStub;
request._createRequestObjectStub = mockCreateRequestObjectStub;
client.createDocument(collection._self, documentDefinition, function (err, createdDocument, responseHeaders) {
assert.equal(err.code, 429, "invalid error code");
assert.equal(responseHeaders[Constants.ThrottleRetryCount], connectionPolicy.RetryOptions.MaxRetryAttemptCount, "Current retry attempts not maxed out");
assert.ok(responseHeaders[Constants.ThrottleRetryWaitTimeInMs] >= connectionPolicy.RetryOptions.MaxRetryAttemptCount * retryAfterInMilliseconds);
request._createRequestObjectStub = originalCreateRequestObjectStub;
client.getDatabaseAccount = originalGetDatabaseAccount;
done();
});
});
});
});
it("throttle retry policy test fixed retryAfter", function (done) {
connectionPolicy.RetryOptions = new RetryOptions(5, 2000);
var client = new DocumentDBClient(host, { masterKey: masterKey }, connectionPolicy);
client.createDatabase({ "id": "sample database" }, function (err, db) {
assert.equal(err, undefined, "error creating database");
client.createCollection(db._self, collectionDefinition, function (err, collection) {
assert.equal(err, undefined, "error creating collection");
var originalGetDatabaseAccount = client.getDatabaseAccount;
client.getDatabaseAccount = mockGetDatabaseAccount;
var originalCreateRequestObjectStub = request._createRequestObjectStub;
request._createRequestObjectStub = mockCreateRequestObjectStub;
client.createDocument(collection._self, documentDefinition, function (err, createdDocument, responseHeaders) {
assert.equal(err.code, 429, "invalid error code");
assert.equal(responseHeaders[Constants.ThrottleRetryCount], connectionPolicy.RetryOptions.MaxRetryAttemptCount, "Current retry attempts not maxed out");
assert.ok(responseHeaders[Constants.ThrottleRetryWaitTimeInMs] >= connectionPolicy.RetryOptions.MaxRetryAttemptCount * connectionPolicy.RetryOptions.FixedRetryIntervalInMilliseconds);
request._createRequestObjectStub = originalCreateRequestObjectStub;
client.getDatabaseAccount = originalGetDatabaseAccount;
done();
});
});
});
});
it("throttle retry policy test max wait time", function (done) {
connectionPolicy.RetryOptions = new RetryOptions(5, 2000, 3);
var client = new DocumentDBClient(host, { masterKey: masterKey }, connectionPolicy);
client.createDatabase({ "id": "sample database" }, function (err, db) {
assert.equal(err, undefined, "error creating database");
client.createCollection(db._self, collectionDefinition, function (err, collection) {
assert.equal(err, undefined, "error creating collection");
var originalGetDatabaseAccount = client.getDatabaseAccount;
client.getDatabaseAccount = mockGetDatabaseAccount;
var originalCreateRequestObjectStub = request._createRequestObjectStub;
request._createRequestObjectStub = mockCreateRequestObjectStub;
client.createDocument(collection._self, documentDefinition, function (err, createdDocument, responseHeaders) {
assert.equal(err.code, 429, "invalid error code");
assert.ok(responseHeaders[Constants.ThrottleRetryWaitTimeInMs] >= connectionPolicy.RetryOptions.MaxWaitTimeInSeconds * 1000);
request._createRequestObjectStub = originalCreateRequestObjectStub;
client.getDatabaseAccount = originalGetDatabaseAccount;
done();
});
});
});
});
});
});
describe("GlobalDBTests", function () {
var RetryUtility = require("../lib/retryUtility");
var AzureDocuments = require("../lib/documents");
var request = require("../lib/request");
var AzureDocuments = require("../lib/documents");
var EndpointDiscoveryRetryPolicy = require("../lib/endpointDiscoveryRetryPolicy");
var host = "[YOUR_GLOBAL_ENDPOINT_HERE]";
@ -3464,12 +3583,12 @@ describe("GlobalDBTests", function () {
done();
});
});
}, 5000);
}, 20000);
});
});
});
});
}, 5000);
}, 20000);
});
});
});
@ -3564,11 +3683,11 @@ describe("GlobalDBTests", function () {
done();
});
});
}, 5000);
}, 20000);
});
});
});
}, 5000);
}, 20000);
});
});
@ -3763,6 +3882,7 @@ describe("GlobalDBTests", function () {
assert.ok(endDate.valueOf() - startDate.valueOf() > maxRetryAttemptCount * retryAfterInMilliseconds);
request._createRequestObjectStub = originalCreateRequestObjectStub;
client.getDatabaseAccount = originalGetDatabaseAccount;
done();
});
});