From c2791bf61a3852d1edb5ad241dc32f7f8e7b9f6e Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Fri, 25 Jan 2013 17:43:48 -0800 Subject: [PATCH 1/3] SQL Server Database wrappers --- lib/azure.js | 33 ++- lib/services/core/serviceclient.js | 1 + lib/services/core/servicemanagementclient.js | 2 +- lib/services/core/sqlserviceclient.js | 125 ++++++++++++ lib/services/serviceBus/models/queueresult.js | 26 +-- lib/services/serviceBus/models/ruleresult.js | 30 +-- .../serviceBus/models/subscriptionresult.js | 26 +-- lib/services/serviceBus/models/topicresult.js | 26 +-- ...baseservice.js => sqlmanagementservice.js} | 28 +-- .../sqlAzure/models/databaseresult.js | 81 ++++++++ lib/services/sqlAzure/sqlserveracs.js | 135 ++++++++++++ lib/services/sqlAzure/sqlservice.js | 192 ++++++++++++++++++ lib/services/table/tableservice.js | 1 - lib/util/constants.js | 4 + ...tests.js => sqlmanagementservice-tests.js} | 2 +- test/services/sqlAzure/sqlservice-tests.js | 190 +++++++++++++++++ test/testlist.txt | 3 +- test/util/util.js | 2 +- 18 files changed, 828 insertions(+), 79 deletions(-) create mode 100644 lib/services/core/sqlserviceclient.js rename lib/services/serviceManagement/{sqldatabaseservice.js => sqlmanagementservice.js} (89%) create mode 100644 lib/services/sqlAzure/models/databaseresult.js create mode 100644 lib/services/sqlAzure/sqlserveracs.js create mode 100644 lib/services/sqlAzure/sqlservice.js rename test/services/serviceManagement/{sqldatabaseservice-tests.js => sqlmanagementservice-tests.js} (99%) create mode 100644 test/services/sqlAzure/sqlservice-tests.js diff --git a/lib/azure.js b/lib/azure.js index 114a30ace0..1c00ec1e2b 100644 --- a/lib/azure.js +++ b/lib/azure.js @@ -97,6 +97,27 @@ exports.createServiceBusService = function (namespaceOrConnectionString, accessK return new ServiceBusService(namespaceOrConnectionString, accessKey, issuer, acsNamespace, host, authenticationProvider); }; +/** +* SqlService client exports. +*/ + +var SqlService = require('./services/sqlAzure/sqlservice'); +exports.SqlService = SqlService; + +/** +* Creates a new SqlManagementService object. +* +* @param {string} serverName The SQL server name. +* @param {string} administratorLogin The SQL Server administrator login. +* @param {string} administratorLoginPassword The SQL Server administrator login password. +* @param {string} [host] The host for the service. +* @param {string} [acsHost] The acs host. +* @param {object} [authenticationProvider] The authentication provider. +*/ +exports.createSqlService = function(serverName, administratorLogin, administratorLoginPassword, host, acsHost, authenticationProvider) { + return new SqlService(serverName, administratorLogin, administratorLoginPassword, host, acsHost, authenticationProvider); +}; + /** * ServiceManagement client exports. */ @@ -127,14 +148,14 @@ exports.createServiceManagementService = function(subscriptionId, authentication }; /** -* SqlDatabaseService client exports. +* SqlManagementService client exports. */ -var SqlDatabaseService = require('./services/serviceManagement/sqldatabaseservice'); -exports.SqlDatabaseService = SqlDatabaseService; +var SqlManagementService = require('./services/serviceManagement/sqlmanagementservice'); +exports.SqlManagementService = SqlManagementService; /** -* Creates a new SqlDatabaseService object. +* Creates a new SqlManagementService object. * * @param {string} subscriptionId The subscription ID for the account. * @param {string} authentication The authentication object for the client. @@ -151,8 +172,8 @@ exports.SqlDatabaseService = SqlDatabaseService; * serializetype: 'XML' * } */ -exports.createSqlDatabaseService = function(subscriptionId, authentication, hostOptions) { - return new SqlDatabaseService(subscriptionId, authentication, hostOptions); +exports.createSqlManagementService = function(subscriptionId, authentication, hostOptions) { + return new SqlManagementService(subscriptionId, authentication, hostOptions); }; /** diff --git a/lib/services/core/serviceclient.js b/lib/services/core/serviceclient.js index a520a6acc8..b2840be2a4 100644 --- a/lib/services/core/serviceclient.js +++ b/lib/services/core/serviceclient.js @@ -72,6 +72,7 @@ ServiceClient.CLOUD_TABLE_HOST = 'table.core.windows.net'; ServiceClient.CLOUD_SERVICEBUS_HOST = 'servicebus.windows.net'; ServiceClient.CLOUD_ACCESS_CONTROL_HOST = 'accesscontrol.windows.net'; ServiceClient.CLOUD_SERVICE_MANAGEMENT_HOST = 'management.core.windows.net'; +ServiceClient.CLOUD_DATABASE_HOST = 'database.windows.net'; /** * The default service bus issuer. diff --git a/lib/services/core/servicemanagementclient.js b/lib/services/core/servicemanagementclient.js index 545ab62a48..cceb99e2b1 100644 --- a/lib/services/core/servicemanagementclient.js +++ b/lib/services/core/servicemanagementclient.js @@ -41,7 +41,7 @@ ServiceManagementClient.DefaultAPIVersion = '2012-03-01'; ServiceManagementClient.DefaultSerializeType = 'JSON'; /** -* Creates a new ServiceClient object. +* Creates a new ServiceManagementClient object. * * @constructor * @param {string} hostOptions The host options to override defaults. diff --git a/lib/services/core/sqlserviceclient.js b/lib/services/core/sqlserviceclient.js new file mode 100644 index 0000000000..9f1a0173a8 --- /dev/null +++ b/lib/services/core/sqlserviceclient.js @@ -0,0 +1,125 @@ +/** +* Copyright (c) Microsoft. 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 dependencies. +var util = require('util'); +var url = require('url'); + +var azureutil = require('../../util/util'); + +var ServiceClient = require('./serviceclient'); +var SqlServerAcs = require('../sqlAzure/sqlserveracs'); +var Constants = require('../../util/constants'); +var HeaderConstants = Constants.HeaderConstants; +var QueryStringConstants = Constants.QueryStringConstants; +var HttpConstants = Constants.HttpConstants; + +// Expose 'SqlServiceClient'. +exports = module.exports = SqlServiceClient; + +/** +* Creates a new SqlServiceClient object. +* +* @constructor +* @param {string} serverName The SQL server name. +* @param {string} administratorLogin The SQL Server administrator login. +* @param {string} administratorLoginPassword The SQL Server administrator login password. +* @param {string} host The host for the service. +* @param {string} acsHost The acs host. Usually the same as the sb namespace with "-sb" suffix. +* @param {object} [authenticationProvider] The authentication provider. +*/ +function SqlServiceClient(serverName, administratorLogin, administratorLoginPassword, host, acsHost, authenticationProvider) { + SqlServiceClient.super_.call(this, host, authenticationProvider); + + this.authenticationProvider = authenticationProvider; + if (!this.authenticationProvider) { + this.authenticationProvider = new SqlServerAcs(acsHost, serverName, administratorLogin, administratorLoginPassword); + } +} + +util.inherits(SqlServiceClient, ServiceClient); + +/** +* Builds the request options to be passed to the http.request method. +* +* @param {WebResource} webResource The webresource where to build the options from. +* @param {object} options The request options. +* @param {function(error, requestOptions)} callback The callback function. +*/ +SqlServiceClient.prototype._buildRequestOptions = function (webResource, options, callback) { + var self = this; + + if (!webResource.headers || !webResource.headers[HeaderConstants.CONTENT_TYPE]) { + webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, ''); + } + + if (!webResource.headers || !webResource.headers[HeaderConstants.CONTENT_LENGTH]) { + webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, 0); + } + + webResource.addOptionalHeader(HeaderConstants.ACCEPT_CHARSET_HEADER, 'UTF-8'); + webResource.addOptionalHeader(HeaderConstants.HOST_HEADER, this.host + ':' + this.port); + + // Sets the request url in the web resource. + this._setRequestUrl(webResource); + + // If wrap is used, make sure proxy settings are in sync + if (this.useProxy && + this.authenticationProvider && + this.authenticationProvider.wrapTokenManager && + this.authenticationProvider.wrapTokenManager.wrapService) { + this.authenticationProvider.wrapTokenManager.wrapService.setProxy(this.proxyUrl, this.proxyPort); + } + + // Now that the web request is finalized, sign it + this.authenticationProvider.signRequest(webResource, function (error) { + var requestOptions = null; + + if (!error) { + requestOptions = { + url: url.format({ + protocol: self._isHttps() ? 'https:' : 'http:', + hostname: self.host, + port: self.port, + pathname: webResource.path + webResource.getQueryString(true) + }), + method: webResource.httpVerb, + headers: webResource.headers + }; + + self._setRequestOptionsProxy(requestOptions); + } + + callback(error, requestOptions); + }); +}; + +/** +* Retrieves the normalized path to be used in a request. +* It adds a leading "/" to the path in case +* it's not there before. +* +* @param {string} path The path to be normalized. +* @return {string} The normalized path. +*/ +SqlServiceClient.prototype._getPath = function (path) { + if (path === null || path === undefined) { + path = '/'; + } else if (path.indexOf('/') !== 0) { + path = '/' + path; + } + + return path; +}; \ No newline at end of file diff --git a/lib/services/serviceBus/models/queueresult.js b/lib/services/serviceBus/models/queueresult.js index 5e585bbb4b..94ba62282c 100644 --- a/lib/services/serviceBus/models/queueresult.js +++ b/lib/services/serviceBus/models/queueresult.js @@ -37,19 +37,6 @@ QueueResult.serialize = function (path, queue) { } }; - var atomQueue = { - 'title': '', - 'updated': ISO8061Date.format(new Date()), - 'author': { - name: '' - }, - 'id': '', - 'content': { - '$': { type: 'application/xml' }, - QueueDescription: queueDescription - } - }; - if (queue) { if (queue[ServiceBusConstants.LOCK_DURATION]) { queueDescription[ServiceBusConstants.LOCK_DURATION] = queue[ServiceBusConstants.LOCK_DURATION]; @@ -96,6 +83,19 @@ QueueResult.serialize = function (path, queue) { } } + var atomQueue = { + 'title': '', + 'updated': ISO8061Date.format(new Date()), + 'author': { + name: '' + }, + 'id': '', + 'content': { + '$': { type: 'application/xml' }, + QueueDescription: queueDescription + } + }; + var atomHandler = new AtomHandler(null, null); var xml = atomHandler.serialize(atomQueue); diff --git a/lib/services/serviceBus/models/ruleresult.js b/lib/services/serviceBus/models/ruleresult.js index 20005ea296..ceab7f2c92 100644 --- a/lib/services/serviceBus/models/ruleresult.js +++ b/lib/services/serviceBus/models/ruleresult.js @@ -34,21 +34,6 @@ RuleResult.serialize = function (name, path, rule) { } }; - var atomRule = { - 'title': { - '$': { - 'type': 'text' - }, - '_': name - }, - 'updated': ISO8061Date.format(new Date()), - 'id': '', - 'content': { - '$': { type: 'application/xml' }, - RuleDescription: ruleDescription - } - }; - if (rule) { var filters = []; if (rule.sqlExpressionFilter) { @@ -122,6 +107,21 @@ RuleResult.serialize = function (name, path, rule) { } } + var atomRule = { + 'title': { + '$': { + 'type': 'text' + }, + '_': name + }, + 'updated': ISO8061Date.format(new Date()), + 'id': '', + 'content': { + '$': { type: 'application/xml' }, + RuleDescription: ruleDescription + } + }; + var atomHandler = new AtomHandler(null, null); var xml = atomHandler.serialize(atomRule); return xml; diff --git a/lib/services/serviceBus/models/subscriptionresult.js b/lib/services/serviceBus/models/subscriptionresult.js index d6cc3fa9cc..61201e7282 100644 --- a/lib/services/serviceBus/models/subscriptionresult.js +++ b/lib/services/serviceBus/models/subscriptionresult.js @@ -35,19 +35,6 @@ SubscriptionResult.serialize = function (path, subscription) { } }; - var atomQueue = { - 'title': '', - 'updated': ISO8061Date.format(new Date()), - 'author': { - name: '' - }, - 'id': '', - 'content': { - '$': { type: 'application/xml' }, - SubscriptionDescription: subscriptionDescription - } - }; - if (subscription) { if (subscription[ServiceBusConstants.LOCK_DURATION]) { subscriptionDescription[ServiceBusConstants.LOCK_DURATION] = subscription[ServiceBusConstants.LOCK_DURATION]; @@ -86,6 +73,19 @@ SubscriptionResult.serialize = function (path, subscription) { } } + var atomQueue = { + 'title': '', + 'updated': ISO8061Date.format(new Date()), + 'author': { + name: '' + }, + 'id': '', + 'content': { + '$': { type: 'application/xml' }, + SubscriptionDescription: subscriptionDescription + } + }; + var atomHandler = new AtomHandler(null, null); var xml = atomHandler.serialize(atomQueue); diff --git a/lib/services/serviceBus/models/topicresult.js b/lib/services/serviceBus/models/topicresult.js index 4092a2302b..69f54a615d 100644 --- a/lib/services/serviceBus/models/topicresult.js +++ b/lib/services/serviceBus/models/topicresult.js @@ -35,19 +35,6 @@ TopicResult.serialize = function (path, topic) { } }; - var atomQueue = { - 'title': '', - 'updated': ISO8061Date.format(new Date()), - 'author': { - name: '' - }, - 'id': '', - 'content': { - '$': { type: 'application/xml' }, - TopicDescription: topicDescription - } - }; - if (topic) { if (topic[ServiceBusConstants.DEFAULT_MESSAGE_TIME_TO_LIVE]) { topicDescription[ServiceBusConstants.DEFAULT_MESSAGE_TIME_TO_LIVE] = topic[ServiceBusConstants.DEFAULT_MESSAGE_TIME_TO_LIVE]; @@ -74,6 +61,19 @@ TopicResult.serialize = function (path, topic) { } } + var atomQueue = { + 'title': '', + 'updated': ISO8061Date.format(new Date()), + 'author': { + name: '' + }, + 'id': '', + 'content': { + '$': { type: 'application/xml' }, + TopicDescription: topicDescription + } + }; + var atomHandler = new AtomHandler(null, null); var xml = atomHandler.serialize(atomQueue); return xml; diff --git a/lib/services/serviceManagement/sqldatabaseservice.js b/lib/services/serviceManagement/sqlmanagementservice.js similarity index 89% rename from lib/services/serviceManagement/sqldatabaseservice.js rename to lib/services/serviceManagement/sqlmanagementservice.js index a0a01593a7..72e2e59611 100644 --- a/lib/services/serviceManagement/sqldatabaseservice.js +++ b/lib/services/serviceManagement/sqlmanagementservice.js @@ -25,12 +25,12 @@ var parseserverresponse = require('./models/parseserverresponse'); var Constants = require('../../util/constants'); var HttpConstants = Constants.HttpConstants; -// Expose 'SqlDatabaseService'. -exports = module.exports = SqlDatabaseService; +// Expose 'SqlManagementService'. +exports = module.exports = SqlManagementService; /** * -* Creates a new SqlDatabaseService object +* Creates a new SqlManagementService object * * @constructor * @param {string} subscriptionId Subscription ID for the account or the connection string @@ -49,7 +49,7 @@ exports = module.exports = SqlDatabaseService; * } */ -function SqlDatabaseService(subscriptionId, authentication, hostOptions) { +function SqlManagementService(subscriptionId, authentication, hostOptions) { if (typeof subscriptionId != 'string' || subscriptionId.length === 0) { throw new Error('A subscriptionId or a connection string is required'); } @@ -59,19 +59,19 @@ function SqlDatabaseService(subscriptionId, authentication, hostOptions) { } hostOptions.serializetype = 'XML'; - SqlDatabaseService.super_.call(this, authentication, hostOptions); + SqlManagementService.super_.call(this, authentication, hostOptions); this.subscriptionId = subscriptionId; } -util.inherits(SqlDatabaseService, ServiceManagementClient); +util.inherits(SqlManagementService, ServiceManagementClient); /** * Lists the available SQL Servers. * * @param {function} callback function (err, results, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.listServers = function (callback) { +SqlManagementService.prototype.listServers = function (callback) { var path = this._makePath('servers'); var webResource = WebResource.get(path); webResource.withOkCode(HttpConstants.HttpResponseCodes.OK_CODE, true); @@ -98,7 +98,7 @@ SqlDatabaseService.prototype.listServers = function (callback) { * @param {string} name The SQL Server name. * @param {function} callback function (err, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.deleteServer = function (name, callback) { +SqlManagementService.prototype.deleteServer = function (name, callback) { var path = this._makePath('servers') + '/' + name; var webResource = WebResource.del(path); @@ -119,7 +119,7 @@ SqlDatabaseService.prototype.deleteServer = function (name, callback) { * @param {string} location The server's location. * @param {function} callback function (err, server, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.createServer = function (administratorLogin, administratorLoginPassword, location, callback) { +SqlManagementService.prototype.createServer = function (administratorLogin, administratorLoginPassword, location, callback) { var path = this._makePath('servers'); var webResource = WebResource.post(path); @@ -159,7 +159,7 @@ SqlDatabaseService.prototype.createServer = function (administratorLogin, admini * @param {string} serverName The server name. * @param {function} callback function (err, results, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.listServerFirewallRules = function (serverName, callback) { +SqlManagementService.prototype.listServerFirewallRules = function (serverName, callback) { var path = this._makePath('servers') + '/' + serverName + '/firewallrules'; var webResource = WebResource.get(path); webResource.withOkCode(HttpConstants.HttpResponseCodes.OK_CODE, true); @@ -188,7 +188,7 @@ SqlDatabaseService.prototype.listServerFirewallRules = function (serverName, cal * @param {string} ruleName The rule name. * @param {function} callback function (err, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.deleteServerFirewallRule = function (serverName, ruleName, callback) { +SqlManagementService.prototype.deleteServerFirewallRule = function (serverName, ruleName, callback) { var path = this._makePath('servers') + '/' + serverName + '/firewallrules/' + ruleName; var webResource = WebResource.del(path); @@ -210,7 +210,7 @@ SqlDatabaseService.prototype.deleteServerFirewallRule = function (serverName, ru * @param {string} endIPAddress The ending IP address for the rule. * @param {function} callback function (err, rule, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.createServerFirewallRule = function (serverName, ruleName, startIPAddress, endIPAddress, callback) { +SqlManagementService.prototype.createServerFirewallRule = function (serverName, ruleName, startIPAddress, endIPAddress, callback) { var path = this._makePath('servers') + '/' + serverName + '/firewallrules'; var webResource = WebResource.post(path); @@ -254,7 +254,7 @@ SqlDatabaseService.prototype.createServerFirewallRule = function (serverName, ru * @param {string} endIPAddress The ending IP address for the rule. * @param {function} callback function (err, rule, response) The callback function called on completion. Required. */ -SqlDatabaseService.prototype.updateServerFirewallRule = function (serverName, ruleName, startIPAddress, endIPAddress, callback) { +SqlManagementService.prototype.updateServerFirewallRule = function (serverName, ruleName, startIPAddress, endIPAddress, callback) { var path = this._makePath('servers') + '/' + serverName + '/firewallrules/' + ruleName; var webResource = WebResource.put(path); webResource.withOkCode(HttpConstants.HttpResponseCodes.OK_CODE, true); @@ -290,6 +290,6 @@ SqlDatabaseService.prototype.updateServerFirewallRule = function (serverName, ru }); }; -SqlDatabaseService.prototype._makePath = function (operationName) { +SqlManagementService.prototype._makePath = function (operationName) { return '/' + this.subscriptionId + '/services/sqlservers/' + operationName; }; \ No newline at end of file diff --git a/lib/services/sqlAzure/models/databaseresult.js b/lib/services/sqlAzure/models/databaseresult.js new file mode 100644 index 0000000000..d7ea0abd16 --- /dev/null +++ b/lib/services/sqlAzure/models/databaseresult.js @@ -0,0 +1,81 @@ +/** +* Copyright (c) Microsoft. 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 dependencies. +var AtomHandler = require('../../../util/atomhandler'); +var ISO8061Date = require('../../../util/iso8061date'); +var Constants = require('../../../util/constants'); +var ServiceBusConstants = Constants.ServiceBusConstants; +var HeaderConstants = Constants.HeaderConstants; + +// Expose 'DatabaseResult'. +exports = module.exports = DatabaseResult; + +function DatabaseResult() { } + +DatabaseResult.serialize = function (databaseName, collation, edition, maxSizeInGB) { + var databaseDescription = { }; + + if (collation) { + databaseDescription['d:CollationName'] = collation; + } else { + databaseDescription['d:CollationName'] = { '$': { 'm:null': 'true' } }; + } + + databaseDescription['d:CreationDate'] = { '$': { 'm:type': 'Edm.DateTime' }, '_': '0001-01-01T00:00:00' }; + + if (edition) { + databaseDescription['d:Edition'] = edition; + } else { + databaseDescription['d:Edition'] = { '$': { 'm:null': 'true' } }; + } + + databaseDescription['d:Id'] = { '$': { 'm:type': 'Edm.Int32' }, '_': '0' }; + databaseDescription['d:IsFederationRoot'] = { '$': { 'm:type': 'Edm.Boolean', 'm:null': 'true' } }; + databaseDescription['d:IsReadonly'] = { '$': { 'm:type': 'Edm.Boolean' }, '_': 'false' }; + databaseDescription['d:IsRecursiveTriggersOn'] = { '$': { 'm:type': 'Edm.Boolean', 'm:null': 'true' } }; + databaseDescription['d:IsSystemObject'] = { '$': { 'm:type': 'Edm.Boolean' }, '_': 'false' }; + + if (maxSizeInGB) { + databaseDescription['d:MaxSizeGB'] = { '$': { 'm:type': 'Edm.Int32' }, '_': maxSizeInGB }; + } else { + databaseDescription['d:MaxSizeGB'] = { '$': { 'm:type': 'Edm.Int32', 'm:null': 'true' } }; + } + + if (databaseName) { + databaseDescription['d:Name'] = databaseName; + } + + databaseDescription['d:SizeMB'] = { '$': { 'm:type': 'Edm.Decimal' }, '_': '0' }; + databaseDescription['d:Status'] = { '$': { 'm:type': 'Edm.Int32' }, '_': '0' }; + + var atom = { + 'title': '', + 'updated': ISO8061Date.format(new Date(), false, 7), + 'author': { + name: '' + }, + 'id': '', + 'content': { + '$': { type: 'application/xml' }, + 'm:properties': databaseDescription + } + }; + + var atomHandler = new AtomHandler(); + var xml = atomHandler.serialize(atom); + + return xml; +}; diff --git a/lib/services/sqlAzure/sqlserveracs.js b/lib/services/sqlAzure/sqlserveracs.js new file mode 100644 index 0000000000..6adff85094 --- /dev/null +++ b/lib/services/sqlAzure/sqlserveracs.js @@ -0,0 +1,135 @@ +/** +* Copyright (c) Microsoft. 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 dependencies. +var util = require('util'); +var url = require('url'); +var _ = require('underscore'); + +var azureutil = require('../../util/util'); + +var ServiceClient = require('../core/serviceclient'); + +var WebResource = require('../../http/webresource'); +var Constants = require('../../util/constants'); +var QueryStringConstants = Constants.QueryStringConstants; +var HttpConstants = Constants.HttpConstants; +var HeaderConstants = Constants.HeaderConstants; +var SqlAzureConstants = Constants.SqlAzureConstants; + +// Expose 'SqlServerAcs'. +exports = module.exports = SqlServerAcs; + +/** +* Creates a new SqlServerAcs object. +* +* @param {string} acsHost The access control host. +* @param {string} serverName The SQL server name. +* @param {string} administratorLogin The administrator login. +* @param {string} administratorLoginPassword The administrator login password. +*/ +function SqlServerAcs(acsHost, serverName, administratorLogin, administratorLoginPassword) { + this.acsHost = acsHost; + this.serverName = serverName; + this.administratorLogin = administratorLogin; + this.administratorLoginPassword = administratorLoginPassword; + + SqlServerAcs.super_.call(this, acsHost); +} + +util.inherits(SqlServerAcs, ServiceClient); + +/** +* Signs a request with the Authentication header. +* +* @param {WebResource} The webresource to be signed. +* @return {undefined} +*/ +SqlServerAcs.prototype.signRequest = function (webResourceToSign, callback) { + var escapedLogin = escapeConnectionCredentials(this.administratorLogin); + var escapedLoginPassword = escapeConnectionCredentials(this.administratorLoginPassword); + + var escapedCredentials = escapedLogin + ':' + escapedLoginPassword; + + var encodedCredentials = 'Basic ' + (new Buffer(escapedCredentials).toString('base64')); + + var webResource = WebResource.get('/v1/ManagementService.svc/GetAccessToken'); + + webResource.addOptionalHeader('sqlauthorization', encodedCredentials); + + var processResponseCallback = function (responseObject, next) { + if (!responseObject.error) { + if (responseObject.response.headers['set-cookie']) { + _.each(responseObject.response.headers['set-cookie'], function (cookie) { + if (_.startsWith(cookie, SqlAzureConstants.SQL_SERVER_MANAGEMENT_COOKIE)) { + webResourceToSign.addOptionalHeader('Cookie', cookie.split(';')[0]); + } + }) + } + + webResourceToSign.addOptionalHeader('AccessToken', responseObject.response.body.string[Constants.XML_VALUE_MARKER]); + } + + var finalCallback = function (returnObject) { + callback(returnObject.error); + }; + + next(responseObject, finalCallback); + }; + + this.performRequest(webResource, null, null, processResponseCallback); +}; + +SqlServerAcs.prototype._buildRequestOptions = function (webResource, options, callback) { + var self = this; + + // Sets the request url in the web resource. + this._setRequestUrl(webResource); + + var requestOptions = { + url: url.format({ + protocol: self._isHttps() ? 'https:' : 'http:', + hostname: self.host, + port: self.port, + pathname: webResource.path + webResource.getQueryString(true) + }), + method: webResource.httpVerb, + headers: webResource.headers + }; + + callback(null, requestOptions); +}; + +/** +* Retrieves the normalized path to be used in a request. +* It adds a leading "/" to the path in case +* it's not there before. +* +* @param {string} path The path to be normalized. +* @return {string} The normalized path. +*/ +SqlServerAcs.prototype._getPath = function (path) { + if (path === null || path === undefined) { + path = '/'; + } else if (path.indexOf('/') !== 0) { + path = '/' + path; + } + + return path; +}; + +function escapeConnectionCredentials(value) { + return value.replace(/\\/g, /\\\\/g).replace(/:/g, /\\:/g); +} \ No newline at end of file diff --git a/lib/services/sqlAzure/sqlservice.js b/lib/services/sqlAzure/sqlservice.js new file mode 100644 index 0000000000..1230379045 --- /dev/null +++ b/lib/services/sqlAzure/sqlservice.js @@ -0,0 +1,192 @@ +/** +* Copyright (c) Microsoft. 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 dependencies. +var util = require('util'); +var xml2js = require('xml2js'); +var url = require('url'); +var _ = require('underscore'); + +var SqlServiceClient = require('../core/sqlserviceclient'); +var WebResource = require('../../http/webresource'); +var ServiceClient = require('../core/serviceclient'); + +var AtomHandler = require('../../util/atomhandler'); +var DatabaseResult = require('./models/databaseresult'); + +var Constants = require('../../util/constants'); +var HttpConstants = Constants.HttpConstants; +var HeaderConstants = Constants.HeaderConstants; + +// Expose 'SqlService'. +exports = module.exports = SqlService; + +/** +* +* Creates a new SqlService object +* +* @constructor +* @param {string} serverName The SQL server name. +* @param {string} administratorLogin The SQL Server administrator login. +* @param {string} administratorLoginPassword The SQL Server administrator login password. +* @param {string} [host] The host for the service. +* @param {string} [acsHost] The acs host. +* @param {object} [authenticationProvider] The authentication provider. +*/ + +function SqlService(serverName, administratorLogin, administratorLoginPassword, host, acsHost, authenticationProvider) { + this.serverName = serverName; + + var endpoint = url.format({ protocol: 'https:', port: 443, hostname: serverName + '.' + ServiceClient.CLOUD_DATABASE_HOST }); + var acsEndpoint = url.format({ protocol: 'https:', port: 443, hostname: serverName + '.' + ServiceClient.CLOUD_DATABASE_HOST }); + + SqlService.super_.call(this, + serverName, + administratorLogin, + administratorLoginPassword, + endpoint, + acsEndpoint, + authenticationProvider); +} + +util.inherits(SqlService, SqlServiceClient); + +/** +* Creates a SQL Server database. +* +* @param {string} databaseName The database name. +* @param {object|function} [optionsOrCallback] The get options or callback function. +* @param {string} [optionsOrCallback.collation] The database collation to be used. +* @param {string} [optionsOrCallback.edition] The database edition to be used. +* @param {string} [optionsOrCallback.maxSizeInGB] The database maximum size in gigabytes. +* @param {function} callback function (err, response) The callback function called on completion. Required. +*/ +SqlService.prototype.createServerDatabase = function (databaseName, optionsOrCallback, callback) { + var options = null; + if (typeof optionsOrCallback === 'function' && !callback) { + callback = optionsOrCallback; + options = { }; + } else { + options = optionsOrCallback; + } + + validateCallback(callback); + + var databaseXml = DatabaseResult.serialize(databaseName, options.collation, options.edition, options.maxSizeInGB); + + var webResource = WebResource.post('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases'); + + webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, 'application/atom+xml;charset="utf-8"'); + webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, Buffer.byteLength(databaseXml, 'utf8')); + webResource.addOptionalHeader('Expect', '100-continue'); + + var processResponseCallback = function (responseObject, next) { + if (!responseObject.error && responseObject.response.body.entry) { + var atomHandler = new AtomHandler(); + responseObject.database = atomHandler.parse(responseObject.response.body.entry) + } + + var finalCallback = function (returnObject) { + callback(returnObject.error, returnObject.database, returnObject.response); + }; + + next(responseObject, finalCallback); + }; + + this._performRequestExtended(webResource, databaseXml, null, processResponseCallback); +}; + +/** +* Deletes a SQL Server database. +* +* @param {string} databaseId The database identifier. +* @param {function} callback function (err, response) The callback function called on completion. Required. +*/ +SqlService.prototype.deleteServerDatabase = function (databaseId, callback) { + validateCallback(callback); + + var webResource = WebResource.del('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases(' + databaseId + ')'); + + var processResponseCallback = function (responseObject, next) { + var finalCallback = function (returnObject) { + callback(returnObject.error, returnObject.response); + }; + + next(responseObject, finalCallback); + }; + + this._performRequestExtended(webResource, null, null, processResponseCallback); +}; + +/** +* Lists the SQL Server databases. +* +* @param {function} callback function (err, results, response) The callback function called on completion. Required. +*/ +SqlService.prototype.listServerDatabases = function (callback) { + validateCallback(callback); + + var webResource = WebResource.get('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases'); + + var processResponseCallback = function (responseObject, next) { + if (!responseObject.error) { + responseObject.databases = []; + + var entries = []; + if (responseObject.response.body.feed && responseObject.response.body.feed.entry) { + entries = responseObject.response.body.feed.entry; + } else if (responseObject.response.body.entry) { + entries = [responseObject.response.body.entry]; + } + + var atomHandler = new AtomHandler(); + _.each(entries, function (entry) { + responseObject.databases.push(atomHandler.parse(entry)); + }); + } + + var finalCallback = function (returnObject) { + callback(returnObject.error, returnObject.databases, returnObject.response); + }; + + next(responseObject, finalCallback); + }; + + this._performRequestExtended(webResource, null, null, processResponseCallback); +}; + +SqlService.prototype._performRequestExtended = function (webResource, rawData, options, callback) { + if (!webResource.headers || !webResource.headers[HeaderConstants.DATA_SERVICE_VERSION]) { + webResource.addOptionalHeader(HeaderConstants.DATA_SERVICE_VERSION, '1.0;NetFx'); + } + + if (!webResource.headers || !webResource.headers[HeaderConstants.MAX_DATA_SERVICE_VERSION]) { + webResource.addOptionalHeader(HeaderConstants.MAX_DATA_SERVICE_VERSION, '2.0;NetFx'); + } + + this.performRequest(webResource, rawData, options, callback); +}; + +/** +* Validates a callback function. +* +* @param {string} callback The callback function. +* @return {undefined} +*/ +function validateCallback(callback) { + if (!callback) { + throw new Error(TableService.incorrectCallbackErr); + } +} \ No newline at end of file diff --git a/lib/services/table/tableservice.js b/lib/services/table/tableservice.js index 8239692e54..d6c504fe11 100644 --- a/lib/services/table/tableservice.js +++ b/lib/services/table/tableservice.js @@ -24,7 +24,6 @@ var StorageServiceClient = require('../core/storageserviceclient'); var BatchServiceClient = require('./batchserviceclient'); var SharedKeyTable = require('./sharedkeytable'); var TableQuery = require('./tablequery'); -var AtomHandler = require('../../util/atomhandler'); var ServiceClient = require('../core/serviceclient'); var WebResource = require('../../http/webresource'); var Constants = require('../../util/constants'); diff --git a/lib/util/constants.js b/lib/util/constants.js index f7b20af5e8..39e0adebff 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -2511,6 +2511,10 @@ var Constants = { } }, + SqlAzureConstants: { + SQL_SERVER_MANAGEMENT_COOKIE: '.SQLSERVERMANAGEMENT' + }, + BlobErrorCodeStrings: { INVALID_BLOCK_ID: 'InvalidBlockId', BLOB_NOT_FOUND: 'BlobNotFound', diff --git a/test/services/serviceManagement/sqldatabaseservice-tests.js b/test/services/serviceManagement/sqlmanagementservice-tests.js similarity index 99% rename from test/services/serviceManagement/sqldatabaseservice-tests.js rename to test/services/serviceManagement/sqlmanagementservice-tests.js index 90a077ae9f..23f8a4c055 100644 --- a/test/services/serviceManagement/sqldatabaseservice-tests.js +++ b/test/services/serviceManagement/sqlmanagementservice-tests.js @@ -32,7 +32,7 @@ describe('SQL Server Management', function () { before(function () { var subscriptionId = process.env['AZURE_SUBSCRIPTION_ID']; var auth = { keyvalue: testutil.getCertificateKey(), certvalue: testutil.getCertificate() }; - service = azure.createSqlDatabaseService( + service = azure.createSqlManagementService( subscriptionId, auth, { serializetype: 'XML'}); }); diff --git a/test/services/sqlAzure/sqlservice-tests.js b/test/services/sqlAzure/sqlservice-tests.js new file mode 100644 index 0000000000..af481918d7 --- /dev/null +++ b/test/services/sqlAzure/sqlservice-tests.js @@ -0,0 +1,190 @@ +/** +* Copyright (c) Microsoft. 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. +*/ + +var should = require('should'); +var mocha = require('mocha'); +var uuid = require('node-uuid'); + +var testutil = require('../../util/util'); + +var azure = testutil.libRequire('azure'); + +var SERVER_ADMIN_USERNAME = 'azuresdk'; +var SERVER_ADMIN_PASSWORD = 'PassWord!1'; +var SERVER_LOCATION = 'West US'; + +var DATABASE_NAME = 'mydatabase'; + +describe('SQL Azure Database', function () { + var serverName; + + var service; + var serviceManagement; + + before(function (done) { + var subscriptionId = process.env['AZURE_SUBSCRIPTION_ID']; + var auth = { keyvalue: testutil.getCertificateKey(), certvalue: testutil.getCertificate() }; + serviceManagement = azure.createSqlManagementService( + subscriptionId, auth, + { serializetype: 'XML'}); + + serviceManagement.createServer(SERVER_ADMIN_USERNAME, SERVER_ADMIN_PASSWORD, SERVER_LOCATION, function (err, name) { + should.not.exist(err); + + serverName = name; + + // Create the SQL Azure service to test + service = azure.createSqlService(serverName, SERVER_ADMIN_USERNAME, SERVER_ADMIN_PASSWORD); + + // add firewall rule for all the ip range + serviceManagement.createServerFirewallRule(serverName, 'rule1', '0.0.0.0', '255.255.255.255', function () { + + // Wait for the firewall rule to be added (test different operations needed as it seems they dont go valid at the same time) + var checkIfRuleAdded = function () { + setTimeout(function () { + var databaseId; + + service.createServerDatabase(DATABASE_NAME, function (err, db) { + if (err) { + checkIfRuleAdded(); + } else { + databaseId = db.Id; + + var checkIfRuleDeleted = function () { + setTimeout(function () { + service.deleteServerDatabase(databaseId, function (err) { + if (err) { + checkIfRuleDeleted(); + } else { + var checkIfRuleLists = function () { + setTimeout(function () { + service.listServerDatabases(function (err) { + if (err) { + checkIfRuleLists(); + } else { + done(); + } + }) + }, 2000); + }; + + checkIfRuleLists(); + } + }); + }, 2000); + }; + + checkIfRuleDeleted(); + } + }); + }, 2000); + }; + + checkIfRuleAdded(); + }); + }); + }); + + after(function (done) { + serviceManagement.deleteServer(serverName, done); + }); + + describe('list SQL databases', function () { + describe('when only master database is defined', function () { + it('should return it', function (done) { + service.listServerDatabases(function (err, databases) { + should.not.exist(err); + should.exist(databases); + databases.should.have.length(1); + databases[0].Name.should.equal('master'); + done(err); + }); + }); + }); + + describe('when multiple databases are defined', function () { + var databaseId; + + before(function (done) { + service.createServerDatabase(DATABASE_NAME, function (err, database) { + should.not.exist(err); + databaseId = database.Id; + + done(err); + }); + }); + + after(function (done) { + service.deleteServerDatabase(databaseId, function (err) { + done(err); + }); + }); + + it('should return it', function (done) { + service.listServerDatabases(function (err, databases) { + should.not.exist(err); + should.exist(databases); + databases.should.have.length(2); + should.exist(databases.filter(function (database) { + return database.Name === DATABASE_NAME; + })[0]); + + done(err); + }); + }); + }); + }); + + describe('Delete SQL databases', function () { + var databaseId; + + before(function (done) { + service.createServerDatabase(DATABASE_NAME, function (err, database) { + should.not.exist(err); + databaseId = database.Id; + + done(err); + }); + }); + + it('should delete existing database', function (done) { + service.deleteServerDatabase(databaseId, function (err, databases) { + should.not.exist(err); + + service.listServerDatabases(function (err, databases) { + should.exist(databases); + should.not.exist(databases.filter(function (database) { + return database.Name === DATABASE_NAME; + })[0]); + + done(err); + }); + }); + }); + }); + + function deleteSqlDatabases(databases, callback) { + if (databases.length === 0) { return callback(); } + var numDeleted = 0; + databases.forEach(function (databaseId) { + service.deleteServerDatabase(databaseId, function (err) { + ++numDeleted; + if (numDeleted === databases.length) { + callback(); + } + }); + }); + } +}); \ No newline at end of file diff --git a/test/testlist.txt b/test/testlist.txt index 994692ea18..05e736fdc1 100644 --- a/test/testlist.txt +++ b/test/testlist.txt @@ -23,11 +23,12 @@ services/serviceBus/servicebusservice-tests.js services/serviceBus/wrapservice-tests.js services/serviceBus/wraptokenmanager-tests.js services/serviceManagement/servicemanagementservice-tests.js -services/serviceManagement/sqldatabaseservice-tests.js +services/serviceManagement/sqlmanagementservice-tests.js services/table/tablequery-tests.js services/table/tableservice-batch-tests.js services/table/tableservice-tablequery-tests.js services/table/tableservice-tests.js +services/sqlAzure/sqlservice-tests.js util/atomhandler-tests.js util/iso8061date-tests.js util/util-tests.js diff --git a/test/util/util.js b/test/util/util.js index ffe5ac540e..c3b562e2d9 100644 --- a/test/util/util.js +++ b/test/util/util.js @@ -80,4 +80,4 @@ exports.getCertificate = function () { } return null; -}; +}; \ No newline at end of file From 2ced2e8c9b43b4f71c538ae86c14877145dd374e Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 28 Jan 2013 13:45:25 -0800 Subject: [PATCH 2/3] Applying code review --- lib/services/sqlAzure/sqlservice.js | 6 +++--- lib/util/constants.js | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/services/sqlAzure/sqlservice.js b/lib/services/sqlAzure/sqlservice.js index 1230379045..70a167b82c 100644 --- a/lib/services/sqlAzure/sqlservice.js +++ b/lib/services/sqlAzure/sqlservice.js @@ -86,7 +86,7 @@ SqlService.prototype.createServerDatabase = function (databaseName, optionsOrCal var databaseXml = DatabaseResult.serialize(databaseName, options.collation, options.edition, options.maxSizeInGB); - var webResource = WebResource.post('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases'); + var webResource = WebResource.post(SqlAzureConstants.MANAGEMENT_SERVICE_URI + 'Server2(\'' + this.serverName + '\')/Databases'); webResource.addOptionalHeader(HeaderConstants.CONTENT_TYPE, 'application/atom+xml;charset="utf-8"'); webResource.addOptionalHeader(HeaderConstants.CONTENT_LENGTH, Buffer.byteLength(databaseXml, 'utf8')); @@ -117,7 +117,7 @@ SqlService.prototype.createServerDatabase = function (databaseName, optionsOrCal SqlService.prototype.deleteServerDatabase = function (databaseId, callback) { validateCallback(callback); - var webResource = WebResource.del('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases(' + databaseId + ')'); + var webResource = WebResource.del(SqlAzureConstants.MANAGEMENT_SERVICE_URI + 'Server2(\'' + this.serverName + '\')/Databases(' + databaseId + ')'); var processResponseCallback = function (responseObject, next) { var finalCallback = function (returnObject) { @@ -138,7 +138,7 @@ SqlService.prototype.deleteServerDatabase = function (databaseId, callback) { SqlService.prototype.listServerDatabases = function (callback) { validateCallback(callback); - var webResource = WebResource.get('/v1/ManagementService.svc/Server2(\'' + this.serverName + '\')/Databases'); + var webResource = WebResource.get(SqlAzureConstants.MANAGEMENT_SERVICE_URI + 'Server2(\'' + this.serverName + '\')/Databases'); var processResponseCallback = function (responseObject, next) { if (!responseObject.error) { diff --git a/lib/util/constants.js b/lib/util/constants.js index 39e0adebff..02e5e828ec 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -2512,7 +2512,8 @@ var Constants = { }, SqlAzureConstants: { - SQL_SERVER_MANAGEMENT_COOKIE: '.SQLSERVERMANAGEMENT' + SQL_SERVER_MANAGEMENT_COOKIE: '.SQLSERVERMANAGEMENT', + MANAGEMENT_SERVICE_URI: '/v1/ManagementService.svc/' }, BlobErrorCodeStrings: { From aeeae1b189efc51f0aac661e98059ce0719b9e0b Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 28 Jan 2013 15:41:40 -0800 Subject: [PATCH 3/3] Declaring missing constants --- lib/services/sqlAzure/sqlservice.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/services/sqlAzure/sqlservice.js b/lib/services/sqlAzure/sqlservice.js index 70a167b82c..82742bac87 100644 --- a/lib/services/sqlAzure/sqlservice.js +++ b/lib/services/sqlAzure/sqlservice.js @@ -29,6 +29,7 @@ var DatabaseResult = require('./models/databaseresult'); var Constants = require('../../util/constants'); var HttpConstants = Constants.HttpConstants; var HeaderConstants = Constants.HeaderConstants; +var SqlAzureConstants = Constants.SqlAzureConstants; // Expose 'SqlService'. exports = module.exports = SqlService;