Node.js SDK 1.9.0 release
Node.js SDK 1.9.0 release
This commit is contained in:
Родитель
a26f895e1f
Коммит
6bcf401f8e
|
@ -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.
|
||||
* 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,64 +37,13 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -104,7 +51,6 @@ var EndpointDiscoveryRetryPolicy = Base.defineClass(
|
|||
{
|
||||
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,13 +65,7 @@ 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;
|
||||
return callback(getErrorBody(response, data), undefined, response.headers);
|
||||
}
|
||||
|
||||
var result;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
Загрузка…
Ссылка в новой задаче