From 8887fd37be0b206f4dfe342dcc68f24dc2814b62 Mon Sep 17 00:00:00 2001 From: Andre Rodrigues Date: Mon, 12 Mar 2012 18:10:28 -0700 Subject: [PATCH] Incremental work towards service runtime. --- lib/azure.js | 6 + lib/serviceruntime/fileinputchannel.js | 48 ++ lib/serviceruntime/goalstatedeserializer.js | 32 + lib/serviceruntime/namedpipeinputchannel.js | 69 ++ lib/serviceruntime/namedpipeoutputchannel.js | 29 + lib/serviceruntime/protocol1runtimeclient.js | 53 ++ .../protocol1runtimecurrentstateclient.js | 28 + .../protocol1runtimegoalstateclient.js | 85 +++ lib/serviceruntime/roleenvironment.js | 476 +++++++++++++ lib/serviceruntime/roleinstance.js | 21 + lib/serviceruntime/runtimekernel.js | 72 ++ lib/serviceruntime/runtimeversionmanager.js | 62 ++ .../runtimeversionprotocolclient.js | 52 ++ .../xmlcurrentstateserializer.js | 48 ++ .../xmlgoalstatedeserializer.js | 39 ++ .../xmlroleenvironmentdatadeserializer.js | 208 ++++++ lib/util/constants.js | 320 +++++++++ lib/util/js2xml.js | 122 ++++ lib/util/util.js | 27 + package.json | 77 +- projects/VS/Azure.csproj | 57 ++ test/serviceruntime/roleenvironment-tests.js | 655 ++++++++++++++++++ .../runtimeversionmanager-tests.js | 51 ++ .../runtimeversionprotocolclient-tests.js | 69 ++ .../blob/blobservice-longrunning-tests.js | 4 +- test/testlist.txt | 3 + test/util/util-tests.js | 20 + 27 files changed, 2693 insertions(+), 40 deletions(-) create mode 100644 lib/serviceruntime/fileinputchannel.js create mode 100644 lib/serviceruntime/goalstatedeserializer.js create mode 100644 lib/serviceruntime/namedpipeinputchannel.js create mode 100644 lib/serviceruntime/namedpipeoutputchannel.js create mode 100644 lib/serviceruntime/protocol1runtimeclient.js create mode 100644 lib/serviceruntime/protocol1runtimecurrentstateclient.js create mode 100644 lib/serviceruntime/protocol1runtimegoalstateclient.js create mode 100644 lib/serviceruntime/roleenvironment.js create mode 100644 lib/serviceruntime/roleinstance.js create mode 100644 lib/serviceruntime/runtimekernel.js create mode 100644 lib/serviceruntime/runtimeversionmanager.js create mode 100644 lib/serviceruntime/runtimeversionprotocolclient.js create mode 100644 lib/serviceruntime/xmlcurrentstateserializer.js create mode 100644 lib/serviceruntime/xmlgoalstatedeserializer.js create mode 100644 lib/serviceruntime/xmlroleenvironmentdatadeserializer.js create mode 100644 lib/util/js2xml.js create mode 100644 test/serviceruntime/roleenvironment-tests.js create mode 100644 test/serviceruntime/runtimeversionmanager-tests.js create mode 100644 test/serviceruntime/runtimeversionprotocolclient-tests.js diff --git a/lib/azure.js b/lib/azure.js index 0d4b3e380..084fa738d 100644 --- a/lib/azure.js +++ b/lib/azure.js @@ -97,6 +97,12 @@ exports.createServiceBusService = function (namespace, accessKey, issuer, acsNam return new ServiceBusService(namespace, accessKey, issuer, acsNamespace, host, authenticationProvider); }; +/** +* Service Runtime exports. +*/ + +exports.RoleEnvironment = require('./serviceruntime/roleenvironment'); + /** * Other exports. */ diff --git a/lib/serviceruntime/fileinputchannel.js b/lib/serviceruntime/fileinputchannel.js new file mode 100644 index 000000000..1c98b57bf --- /dev/null +++ b/lib/serviceruntime/fileinputchannel.js @@ -0,0 +1,48 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 fs = require('fs'); +var xml2js = require('xml2js'); + +// Expose 'FileInputChannel'. +exports = module.exports = FileInputChannel; + +function FileInputChannel() { } + +FileInputChannel.prototype.readInputChannel = function (name, parseXml, callback) { + this.readData(name, function (error, data) { + if (!error) { + if (parseXml) { + var parser = new xml2js.Parser(); + parser.on('error', function (e) { error = e; }); + parser.parseString(data); + + if (parser.resultObject) { + data = parser.resultObject; + } + } + + callback(error, data); + } else { + callback(error); + } + }); +}; + +FileInputChannel.prototype.readData = function(name, callback) { + var data = fs.readFileSync(name); + callback(undefined, data); +}; \ No newline at end of file diff --git a/lib/serviceruntime/goalstatedeserializer.js b/lib/serviceruntime/goalstatedeserializer.js new file mode 100644 index 000000000..c83c588ab --- /dev/null +++ b/lib/serviceruntime/goalstatedeserializer.js @@ -0,0 +1,32 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 XmlGoalStateDeserializer = require('./xmlgoalstatedeserializer'); + +// Expose 'GoalStateDeserializer'. +exports = module.exports = GoalStateDeserializer; + +function GoalStateDeserializer() { + this.deserializer = new XmlGoalStateDeserializer(); +} + +GoalStateDeserializer.prototype.deserialize = function (xml) { + if (!xml) { + throw new Error('Invalid goal state'); + } + + return this.deserializer.deserialize(xml); +}; \ No newline at end of file diff --git a/lib/serviceruntime/namedpipeinputchannel.js b/lib/serviceruntime/namedpipeinputchannel.js new file mode 100644 index 000000000..9564c03af --- /dev/null +++ b/lib/serviceruntime/namedpipeinputchannel.js @@ -0,0 +1,69 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 net = require('net'); +var xml2js = require('xml2js'); + +// Expose 'NamedPipeInputChannel'. +exports = module.exports = NamedPipeInputChannel; + +function NamedPipeInputChannel() { } + +NamedPipeInputChannel.prototype.readInputChannel = function (name, parseXml, callback) { + this.readData(name, function (error, data) { + if (!error) { + var parts = data.toString().split('\r\n'); + + var xml; + // If there is more than one part (and the \r\n wasnt the final marker). + if (parts.length > 1 && parts[1].length > 0) { + xml = parts[1]; + } else { + xml = parts[0]; + } + + if (parseXml) { + var parser = new xml2js.Parser(); + error = null; + parser.on('error', function (e) { error = e; }); + parser.parseString(xml); + + if (parser.resultObject) { + xml = parser.resultObject; + } + } + + callback(error, xml); + } else { + callback(error); + } + }); +}; + +NamedPipeInputChannel.prototype.readData = function(name, callback) { + var client = net.connect(name); + var data = ''; + client.on('data', function(chunk) { + data += chunk; + client.end(); + + callback(undefined, data); + }); + + client.on('error', function(error) { + callback(error); + }); +}; \ No newline at end of file diff --git a/lib/serviceruntime/namedpipeoutputchannel.js b/lib/serviceruntime/namedpipeoutputchannel.js new file mode 100644 index 000000000..433288b4d --- /dev/null +++ b/lib/serviceruntime/namedpipeoutputchannel.js @@ -0,0 +1,29 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 net = require("net"); + +// Expose 'NamedPipeOutputChannel'. +exports = module.exports = NamedPipeOutputChannel; + +function NamedPipeOutputChannel() { } + +NamedPipeOutputChannel.prototype.writeOutputChannel = function (name, data, callback) { + var client = net.connect(name); + client.end(data); + + callback(); +}; \ No newline at end of file diff --git a/lib/serviceruntime/protocol1runtimeclient.js b/lib/serviceruntime/protocol1runtimeclient.js new file mode 100644 index 000000000..8e7136138 --- /dev/null +++ b/lib/serviceruntime/protocol1runtimeclient.js @@ -0,0 +1,53 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 EventEmitter = require('events').EventEmitter; +var util = require('util'); + +var Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'Protocol1RuntimeClient'. +exports = module.exports = Protocol1RuntimeClient; + +function Protocol1RuntimeClient(goalStateClient, currentStateClient, endpoint) { + EventEmitter.call(this); + + this.goalStateClient = goalStateClient; + this.currentStateClient = currentStateClient; + this.goalStateClient.endpoint = endpoint; + + this.currentGoalState = null; + this.currentEnvironmentData = null; +} + +util.inherits(Protocol1RuntimeClient, EventEmitter); + +Protocol1RuntimeClient.prototype.getCurrentGoalState = function (callback) { + return this.goalStateClient.getCurrentGoalState(callback); +}; + +Protocol1RuntimeClient.prototype.getRoleEnvironmentData = function (callback) { + this.goalStateClient.on(ServiceRuntimeConstants.CHANGED, function (currentGoalState) { + self.emit(ServiceRuntimeConstants.CHANGED, currentGoalState); + }); + + return this.goalStateClient.getRoleEnvironmentData(callback); +}; + +Protocol1RuntimeClient.prototype.setCurrentState = function (state, callback) { + this.currentStateClient.setCurrentState(state, callback); +}; \ No newline at end of file diff --git a/lib/serviceruntime/protocol1runtimecurrentstateclient.js b/lib/serviceruntime/protocol1runtimecurrentstateclient.js new file mode 100644 index 000000000..e688ac103 --- /dev/null +++ b/lib/serviceruntime/protocol1runtimecurrentstateclient.js @@ -0,0 +1,28 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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. +*/ + +// Expose 'Protocol1RuntimeCurrentStateClient'. +exports = module.exports = Protocol1RuntimeCurrentStateClient; + +function Protocol1RuntimeCurrentStateClient(currentStateSerializer, outputChannel) { + this.currentStateSerializer = currentStateSerializer; + this.outputChannel = outputChannel; + this.endpoint = null; +} + +Protocol1RuntimeCurrentStateClient.prototype.setCurrentState = function (state, callback) { + var serializedState = this.currentStateSerializer.serialize(state); + this.outputChannel.writeOutputChannel(serializedState, callback); +}; \ No newline at end of file diff --git a/lib/serviceruntime/protocol1runtimegoalstateclient.js b/lib/serviceruntime/protocol1runtimegoalstateclient.js new file mode 100644 index 000000000..fbde5fda2 --- /dev/null +++ b/lib/serviceruntime/protocol1runtimegoalstateclient.js @@ -0,0 +1,85 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 EventEmitter = require('events').EventEmitter; +var util = require('util'); + +var Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'Protocol1RuntimeGoalStateClient'. +exports = module.exports = Protocol1RuntimeGoalStateClient; + +function Protocol1RuntimeGoalStateClient(currentStateClient, goalStateDeserializer, roleEnvironmentDataDeserializer, namedPipeInputChannel, fileInputChannel) { + EventEmitter.call(this); + + this.currentStateClient = currentStateClient; + this.goalStateDeserializer = goalStateDeserializer; + this.roleEnvironmentDataDeserializer = roleEnvironmentDataDeserializer; + this.namedPipeInputChannel = namedPipeInputChannel; + this.fileInputChannel = fileInputChannel; + this.endpoint = null; + + this.currentGoalState = null; + this.currentEnvironmentData = null; +} + +util.inherits(Protocol1RuntimeGoalStateClient, EventEmitter); + +Protocol1RuntimeGoalStateClient.prototype.getCurrentGoalState = function (callback) { + var self = this; + this.ensureGoalStateRetrieved(function (error) { + if (!error) { + callback(error, self.currentGoalState); + } else { + callback(error); + } + }); +}; + +Protocol1RuntimeGoalStateClient.prototype.getRoleEnvironmentData = function (callback) { + var self = this; + this.ensureGoalStateRetrieved(function (error) { + if (!error) { + if (!self.currentEnvironmentData) { + self.fileInputChannel.readInputChannel(self.currentGoalState.roleEnvironmentPath, true, function (readError, data) { + self.currentEnvironmentData = self.roleEnvironmentDataDeserializer.deserialize(data); + callback(readError, self.currentEnvironmentData); + }); + } else { + callback(undefined, self.currentEnvironmentData); + } + } else { + callback(error); + } + }); +}; + +Protocol1RuntimeGoalStateClient.prototype.ensureGoalStateRetrieved = function (callback) { + var self = this; + if (!self.currentGoalState) { + self.namedPipeInputChannel.readInputChannel(self.endpoint, true, function (error, data) { + if (error) { + callback(error); + } else { + self.currentGoalState = self.goalStateDeserializer.deserialize(data); + callback(); + } + }); + } else { + callback(); + } +}; \ No newline at end of file diff --git a/lib/serviceruntime/roleenvironment.js b/lib/serviceruntime/roleenvironment.js new file mode 100644 index 000000000..88c166251 --- /dev/null +++ b/lib/serviceruntime/roleenvironment.js @@ -0,0 +1,476 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 events = require('events'); +var uuid = require('node-uuid'); + +var RuntimeKernel = require('./runtimekernel'); +var Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +RoleEnvironment.EnvironmentVariables = { + VersionEndpointEnvironmentName: 'WaRuntimeEndpoint' +}; + +RoleEnvironment.VersionEndpointFixedPath = '\\\\.\\pipe\\WindowsAzureRuntime'; + +// Expose 'RoleEnvironment'. +exports = module.exports = RoleEnvironment; + +exports.clientId = uuid(); + +var runtimeClient = null; +var currentGoalState = null; +var currentEnvironmentData = null; +var lastState = null; +var maxDateTime = new Date('9999-12-31T23:59:59.9999999'); +var eventEmitter = new events.EventEmitter(); + +function RoleEnvironment() { } + +RoleEnvironment._initialize = function (callback) { + var getCurrentGoalState = function (error, rtClient) { + if (error) { + callback(error); + } else { + runtimeClient = rtClient; + currentGoalState = runtimeClient.getCurrentGoalState(getCurrentEnvironmentData); + } + }; + + var getCurrentEnvironmentData = function (error, goalState) { + if (error) { + callback(error); + } else { + currentGoalState = goalState; + runtimeClient.getRoleEnvironmentData(function (getRoleEnvironmentDataError, environmentData) { + if (getRoleEnvironmentDataError) { + callback(getRoleEnvironmentDataError); + } else { + currentEnvironmentData = environmentData; + + runtimeClient.on(ServiceRuntimeConstants.CHANGED, function (newGoalState) { + switch (newGoalState.expectedState) { + case ServiceRuntimeConstants.STARTED: + if (newGoalState.incarnation > currentGoalState.incarnation) { + self._processGoalStateChange(newGoalState); // NOTE: do we need a callback here ? + } + break; + case ServiceRuntimeConstants.STOPPED: + /* + raiseStoppingEvent(); + + CurrentState stoppedState = new AcquireCurrentState(clientId, + newGoalState.getIncarnation(), CurrentStatus.STOPPED, maxDateTime); + + runtimeClient.setCurrentState(stoppedState);*/ + break; + } + }); + + callback(); + } + }); + } + }; + + if (!runtimeClient) { + var endpoint = process.env[RoleEnvironment.EnvironmentVariables.VersionEndpointEnvironmentName]; + + if (!endpoint) { + endpoint = RoleEnvironment.VersionEndpointFixedPath; + } + + var kernel = RuntimeKernel.getKernel(); + kernel.getRuntimeVersionManager().getRuntimeClient(endpoint, getCurrentGoalState); + } else { + getCurrentGoalState(undefined, runtimeClient); + } +}; + +/** +* Returns a RoleInstance object that represents the role instance +* in which this code is currently executing. +* +* @param {function(error, roleInstance)} callback The callback function. +*/ +RoleEnvironment.getCurrentRoleInstance = function (callback) { + RoleEnvironment._initialize(function (error) { + var currentInstance = undefined; + if (!error) { + currentInstance = currentEnvironmentData.currentInstance; + } + + callback(error, currentInstance); + }); +}; + +/** +* Returns the deployment ID that uniquely identifies the deployment in +* which this role instance is running. +* +* @param {function(error, deploymentId)} callback The callback function. +*/ +RoleEnvironment.getDeploymentId = function (callback) { + RoleEnvironment._initialize(function (error) { + var id = undefined; + if (!error) { + id = currentEnvironmentData.id; + } + + callback(error, id); + }); +}; + +/** +* Indicates whether the role instance is running in the Windows Azure +* environment. +* +* @param {function(error, isAvailable)} callback The callback function. +*/ +RoleEnvironment.isAvailable = function (callback) { + try { + RoleEnvironment._initialize(function (initializeError) { + callback(initializeError, runtimeClient != null); + }); + } catch (error) { + callback(error, false); + } +}; + +/** +* Indicates whether the role instance is running in the development fabric. +* +* @param {function(error, isEmulated)} callback The callback function. +*/ +RoleEnvironment.isEmulated = function(callback) { + RoleEnvironment._initialize(function (error) { + var isEmulated = undefined; + if (!error) { + isEmulated = currentEnvironmentData.isEmulated; + } + + callback(error, isEmulated); + }); +}; + +/** +* Returns the set of Role objects defined for your service. +* Roles are defined in the service definition file. +* +* @param {function(error, roles)} callback The callback function. +*/ +RoleEnvironment.getRoles = function (callback) { + RoleEnvironment._initialize(function (error) { + var roles = undefined; + if (!error) { + roles = currentEnvironmentData.roles; + } + + callback(error, roles); + }); +}; + +/** +* Retrieves the settings in the service configuration file. +* A role's configuration settings are defined in the service definition file. Values for configuration settings are +* set in the service configuration file. +* +* @param {function(error, configurationSettings)} callback The callback function. +*/ +RoleEnvironment.getConfigurationSettings = function (callback) { + RoleEnvironment._initialize(function (error) { + var configurationSettings = undefined; + if (!error) { + configurationSettings = currentEnvironmentData.configurationSettings; + } + + callback(error, configurationSettings); + }); +}; + +/** +* Retrieves the set of named local storage resources. +* +* @param {function(error, localResources)} callback The callback function. +*/ +RoleEnvironment.getLocalResources = function (callback) { + RoleEnvironment._initialize(function (error) { + var localResources = undefined; + if (!error) { + localResources = currentEnvironmentData.localResources; + } + + callback(error, localResources); + }); +}; + +/** +* Requests that the current role instance be stopped and restarted. +* +* Before the role instance is recycled, the Windows Azure load balancer takes the role instance out of rotation. +* This ensures that no new requests are routed to the instance while it is restarting. +* +* A call to RequestRecycle initiates the normal shutdown cycle. Windows Azure raises the +* Stopping event and calls the OnStop method so that you can run the necessary code to +* prepare the instance to be recycled. +*/ +RoleEnvironment.requestRecycle = function (callback) { + RoleEnvironment._initialize(function (error) { + if (!error) { + var newState = { + clientId: exports.clientId, + incarnation: currentGoalState.incarnation, + status: ServiceRuntimeConstants.RoleStatus.RECYCLE, + expiration: maxDateTime + }; + + runtimeClient.setCurrentState(newState, callback); + } else { + callback(error); + } + }); +}; + +/** +* Sets the status of the role instance. +* +* An instance may indicate that it is in one of two states: Ready or Busy. If an instance's state is Ready, it is +* prepared to receive requests from the load balancer. If the instance's state is Busy, it will not receive +* requests from the load balancer. +* +* @param {string} status A value that indicates whether the instance is ready or busy. +* @param {date} expiration_utc A date value that specifies the expiration date and time of the status. +* +*/ +RoleEnvironment.setStatus = function (roleInstanceStatus, expirationUtc, callback) { + RoleEnvironment._initialize(function (error) { + if (!error) { + var currentStatus = ServiceRuntimeConstants.RoleStatus.STARTED; + + switch (roleInstanceStatus) { + case ServiceRuntimeConstants.RoleInstanceStatus.BUSY: + currentStatus = ServiceRuntimeConstants.RoleStatus.BUSY; + break; + case ServiceRuntimeConstants.RoleInstanceStatus.READY: + currentStatus = ServiceRuntimeConstants.RoleStatus.STARTED; + break; + } + + var newState = { + clientId: exports.clientId, + incarnation: currentGoalState.incarnation, + status: currentStatus, + expiration: expirationUtc + }; + + lastState = newState; + + runtimeClient.setCurrentState(newState, callback); + } else { + callback(error); + } + }); +}; + +/** +* Clears the status of the role instance. +* An instance may indicate that it has completed communicating status by calling this method. +*/ +RoleEnvironment.clearStatus = function (callback) { + RoleEnvironment._initialize(function (error) { + if (!error) { + var newState = { + clientId: exports.clientId + }; + + lastState = newState; + + runtimeClient.setCurrentState(newState, callback); + } else { + callback(error); + } + }); +}; + +RoleEnvironment.on = function (event, callback) { + eventEmitter.on(event, callback); +}; + +RoleEnvironment._processGoalStateChange = function (newGoalState, callback) { + var self = this; + var last = lastState; + + RoleEnvironment._calculateChanges(function (error, changes) { + if (!error) { + if (changes.length === 0) { + self._acceptLatestIncarnation(newGoalState, last); + } else { + eventEmitter.emit(ServiceRuntimeConstants.CHANGING, changes); + + // TODO: check for canceled ? + + RoleEnvironment._acceptLatestIncarnation(newGoalState, last); + + runtimeClient.getRoleEnvironmentData(function (getRoleEnvironmentDataError, environmentData) { + if (getRoleEnvironmentDataError) { + callback(getRoleEnvironmentDataError); + } else { + currentEnvironmentData = environmentData; + + eventEmitter.emit(ServiceRuntimeConstants.CHANGED, changes); + callback(); + } + }); + } + } else { + callback(error); + } + }); +}; + +RoleEnvironment._acceptLatestIncarnation = function (newGoalState, last) { + // TODO: implement +}; + +RoleEnvironment._calculateChanges = function (callback) { + var changes = []; + + var current = currentEnvironmentData; + var newData; + + runtimeClient.getRoleEnvironmentData(function (getRoleEnvironmentDataError, environmentData) { + if (!getRoleEnvironmentDataError) { + newData = environmentData; + + var currentConfig = current.configurationSettings; + var newConfig = newData.configurationSettings; + var currentRoles = current.roles; + var newRoles = newData.roles; + + var setting; + for (setting in currentConfig) { + if (!newConfig[setting] || + newConfig[setting] !== currentConfig[setting]) { + changes.push({ type: 'ConfigurationSettingChange', name: setting }); + } + } + + for (setting in newConfig) { + if (!currentConfig[setting]) { + changes.push({ type: 'ConfigurationSettingChange', name: setting }); + } + } + + var changedRoleSet = []; + var role; + var currentRole; + var newRole; + var instance; + var currentInstance; + var newInstance; + var endpoint; + var currentEndpoint; + var newEndpoint; + + for (role in currentRoles) { + if (newRoles[role]) { + currentRole = currentRoles[role]; + newRole = newRoles[role]; + + for (instance in currentRole) { + if (newRole[instance]) { + currentInstance = currentRole[instance]; + newInstance = newRole[instance]; + + if (currentInstance['faultDomain'] === newInstance['faultDomain'] && + currentInstance['updateDomain'] === currentInstance['updateDomain']) { + + for (endpoint in currentInstance['endpoints']) { + if (newInstance['endpoints'][endpoint]) { + currentEndpoint = currentInstance['endpoints'][endpoint]; + newEndpoint = newInstance['endpoints'][endpoint]; + + if (currentEndpoint['protocol'] !== newEndpoint['protocol'] || + currentEndpoint['address'] !== newEndpoint['address'] || + currentEndpoint['port'] !== newEndpoint['port']) { + changedRoleSet.push(role); + } + } else { + changedRoleSet.push(role); + } + } + } else { + changedRoleSet.push(role); + } + } else { + changedRoleSet.push(role); + } + } + } else { + changedRoleSet.push(role); + } + } + + for (role in newRoles) { + if (currentRoles[role]) { + currentRole = currentRoles[role]; + newRole = newRoles[role]; + + for (instance in newRole) { + if (currentRole[instance]) { + currentInstance = currentRole[instance]; + newInstance = newRole[instance]; + + if (currentInstance['faultDomain'] === newInstance['faultDomain'] && + currentInstance['updateDomain'] === currentInstance['updateDomain']) { + + for (endpoint in newInstance['endpoints']) { + if (currentInstance['endpoints'][endpoint]) { + currentEndpoint = currentInstance['endpoints'][endpoint]; + newEndpoint = newInstance['endpoints'][endpoint]; + + if (currentEndpoint['protocol'] !== newEndpoint['protocol'] || + currentEndpoint['address'] !== newEndpoint['address'] || + currentEndpoint['port'] !== newEndpoint['port']) { + changedRoleSet.push(role); + } + } else { + changedRoleSet.push(role); + } + } + } else { + changedRoleSet.push(role); + } + } else { + changedRoleSet.push(role); + } + } + } else { + changedRoleSet.push(role); + } + } + + for (var changedRole in changedRoleSet) { + changes.push({ type: 'TopologyChange', name: changedRoleSet[changedRole] }); + } + + callback(undefined, changes); + } else { + callback(getRoleEnvironmentDataError); + } + }); +}; \ No newline at end of file diff --git a/lib/serviceruntime/roleinstance.js b/lib/serviceruntime/roleinstance.js new file mode 100644 index 000000000..bf779056a --- /dev/null +++ b/lib/serviceruntime/roleinstance.js @@ -0,0 +1,21 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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. + +// Expose 'RoleInstance'. +exports = module.exports = RoleInstance; + +function RoleInstance() { } diff --git a/lib/serviceruntime/runtimekernel.js b/lib/serviceruntime/runtimekernel.js new file mode 100644 index 000000000..673eb1c0d --- /dev/null +++ b/lib/serviceruntime/runtimekernel.js @@ -0,0 +1,72 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 FileInputChannel = require('./fileinputchannel'); + +var NamedPipeInputChannel = require('./namedpipeinputchannel'); +var NamedPipeOutputChannel = require('./namedpipeoutputchannel'); + +var Protocol1RuntimeCurrentStateClient = require('./protocol1runtimecurrentstateclient'); +var Protocol1RuntimeGoalStateClient = require('./protocol1runtimegoalstateclient'); + +var RuntimeVersionProtocolClient = require('./runtimeversionprotocolclient'); +var RuntimeVersionManager = require('./runtimeversionmanager'); + +var GoalStateDeserializer = require('./goalstatedeserializer'); +var XmlRoleEnvironmentDataDeserializer = require('./xmlroleenvironmentdatadeserializer'); +var XmlCurrentStateSerializer = require('./xmlcurrentstateserializer'); + +// Expose 'RuntimeKernel'. +exports = module.exports = RuntimeKernel; + +var theKernel; + +function RuntimeKernel() { + this.currentStateSerializer = new XmlCurrentStateSerializer(); + this.goalStateDeserializer = new GoalStateDeserializer(); + this.namedPipeOutputChannel = new NamedPipeOutputChannel(); + this.namedPipeInputChannel = new NamedPipeInputChannel(); + this.fileInputChannel = new FileInputChannel(); + + this.protocol1RuntimeCurrentStateClient = new Protocol1RuntimeCurrentStateClient(this.currentStateSerializer, this.namedPipeOutputChannel); + + this.roleEnvironmentDataDeserializer = new XmlRoleEnvironmentDataDeserializer(); + this.protocol1RuntimeGoalStateClient = new Protocol1RuntimeGoalStateClient(this.protocol1RuntimeCurrentStateClient, + this.goalStateDeserializer, this.roleEnvironmentDataDeserializer, this.namedPipeInputChannel, this.fileInputChannel); + + this.runtimeVersionProtocolClient = new RuntimeVersionProtocolClient(this.namedPipeInputChannel); + this.runtimeVersionManager = new RuntimeVersionManager(this.runtimeVersionProtocolClient, this); +} + +RuntimeKernel.getKernel = function () { + if (!theKernel) { + theKernel = new RuntimeKernel(); + } + + return theKernel; +}; + +RuntimeKernel.prototype.getGoalStateClient = function () { + return this.protocol1RuntimeGoalStateClient; +}; + +RuntimeKernel.prototype.getCurrentStateClient = function () { + return this.protocol1RuntimeCurrentStateClient; +}; + +RuntimeKernel.prototype.getRuntimeVersionManager = function () { + return this.runtimeVersionManager; +}; \ No newline at end of file diff --git a/lib/serviceruntime/runtimeversionmanager.js b/lib/serviceruntime/runtimeversionmanager.js new file mode 100644 index 000000000..008123685 --- /dev/null +++ b/lib/serviceruntime/runtimeversionmanager.js @@ -0,0 +1,62 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 Protocol1RuntimeClient = require('./protocol1runtimeclient'); + +// Expose 'RuntimeVersionManager'. +exports = module.exports = RuntimeVersionManager; + +function RuntimeVersionManager(runtimeVersionProtocolClient, runtimeKernel) { + this.protocolClient = runtimeVersionProtocolClient; + this.runtimeKernel = runtimeKernel; + + var self = this; + var runtimeClientFactory = { + getVersion: function () { + return '2011-03-08'; + }, + + createRuntimeClient: function (path) { + return new Protocol1RuntimeClient( + self.runtimeKernel.getGoalStateClient(), + self.runtimeKernel.getCurrentStateClient(), + path); + } + }; + + this.supportedVersionList = [ runtimeClientFactory ]; +} + +RuntimeVersionManager.prototype.getRuntimeClient = function (versionEndpoint, callback) { + var self = this; + + self.protocolClient.getVersionMap(versionEndpoint, function (error, versionMap) { + if (error) { + callback(error); + } else { + for (var i in self.supportedVersionList) { + var factory = self.supportedVersionList[i]; + + if (versionMap[factory.getVersion()]) { + callback(undefined, factory.createRuntimeClient(versionMap[factory.getVersion()])); + return; + } + } + + callback('Server does not support any known protocol versions.'); + } + }); +}; \ No newline at end of file diff --git a/lib/serviceruntime/runtimeversionprotocolclient.js b/lib/serviceruntime/runtimeversionprotocolclient.js new file mode 100644 index 000000000..476006671 --- /dev/null +++ b/lib/serviceruntime/runtimeversionprotocolclient.js @@ -0,0 +1,52 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'RuntimeVersionProtocolClient'. +exports = module.exports = RuntimeVersionProtocolClient; + +function RuntimeVersionProtocolClient(inputChannel) { + this.inputChannel = inputChannel; +} + +RuntimeVersionProtocolClient.prototype.getVersionMap = function (connectionPath, callback) { + this.inputChannel.readInputChannel(connectionPath, true, function (error, data) { + if (error) { + callback(error); + } else { + var versions = {}; + + if (data[ServiceRuntimeConstants.RUNTIME_SERVER_ENDPOINTS] && + data[ServiceRuntimeConstants.RUNTIME_SERVER_ENDPOINTS][ServiceRuntimeConstants.RUNTIME_SERVER_ENDPOINT]) { + + var endpoints = data[ServiceRuntimeConstants.RUNTIME_SERVER_ENDPOINTS][ServiceRuntimeConstants.RUNTIME_SERVER_ENDPOINT]; + if (!Array.isArray(endpoints)) { + endpoints = [endpoints]; + } + + for (var endpoint in endpoints) { + var currentEndpoint = endpoints[endpoint]; + versions[currentEndpoint[Constants.ATOM_METADATA_MARKER].version] = currentEndpoint[Constants.ATOM_METADATA_MARKER].path; + } + } + + callback(undefined, versions); + } + }); +}; \ No newline at end of file diff --git a/lib/serviceruntime/xmlcurrentstateserializer.js b/lib/serviceruntime/xmlcurrentstateserializer.js new file mode 100644 index 000000000..be8f6afaa --- /dev/null +++ b/lib/serviceruntime/xmlcurrentstateserializer.js @@ -0,0 +1,48 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 js2xml = require('../util/js2xml'); +var Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'XmlCurrentStateSerializer'. +exports = module.exports = XmlCurrentStateSerializer; + +function XmlCurrentStateSerializer() { } + +XmlCurrentStateSerializer.prototype.serialize = function (currentState) { + var currentStateXml = {}; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE] = {}; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE] = {}; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][Constants.ATOM_METADATA_MARKER] = {}; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][Constants.ATOM_METADATA_MARKER][ServiceRuntimeConstants.CLIENT_ID] = currentState.clientId; + + // If it is a request for a change of status + if (currentState.status) { + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][ServiceRuntimeConstants.ACQUIRE] = {}; + + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][ServiceRuntimeConstants.ACQUIRE][ServiceRuntimeConstants.EXPIRATION] = currentState.expiration; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][ServiceRuntimeConstants.ACQUIRE][ServiceRuntimeConstants.INCARNATION] = currentState.incarnation; + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][ServiceRuntimeConstants.ACQUIRE][ServiceRuntimeConstants.STATUS] = currentState.status; + } else { + currentStateXml[ServiceRuntimeConstants.CURRENT_STATE][ServiceRuntimeConstants.STATUS_LEASE][ServiceRuntimeConstants.RELEASE] = {}; + } + + // Serialize JSON object to XML + var xml = js2xml.serialize(currentStateXml); + + return xml; +}; \ No newline at end of file diff --git a/lib/serviceruntime/xmlgoalstatedeserializer.js b/lib/serviceruntime/xmlgoalstatedeserializer.js new file mode 100644 index 000000000..584848bd6 --- /dev/null +++ b/lib/serviceruntime/xmlgoalstatedeserializer.js @@ -0,0 +1,39 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'XmlGoalStateDeserializer'. +exports = module.exports = XmlGoalStateDeserializer; + +function XmlGoalStateDeserializer() { } + +XmlGoalStateDeserializer.prototype.deserialize = function (xml) { + if (!xml) { + throw new Error('Invalid goal state'); + } + + var goalState = {}; + + goalState.incarnation = xml[ServiceRuntimeConstants.INCARNATION]; + goalState.expectedState = xml[ServiceRuntimeConstants.EXPECTED_STATE]; + goalState.roleEnvironmentPath = xml[ServiceRuntimeConstants.ROLE_ENVIRONMENT_PATH]; + goalState.deadline = xml[ServiceRuntimeConstants.DEADLINE]; + goalState.currentStateEndpoint = xml[ServiceRuntimeConstants.CURRENT_STATE_ENDPOINT]; + + return goalState; +}; \ No newline at end of file diff --git a/lib/serviceruntime/xmlroleenvironmentdatadeserializer.js b/lib/serviceruntime/xmlroleenvironmentdatadeserializer.js new file mode 100644 index 000000000..5d0d1cd72 --- /dev/null +++ b/lib/serviceruntime/xmlroleenvironmentdatadeserializer.js @@ -0,0 +1,208 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 Constants = require('../util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +// Expose 'XmlRoleEnvironmentDataDeserializer'. +exports = module.exports = XmlRoleEnvironmentDataDeserializer; + +function XmlRoleEnvironmentDataDeserializer() { } + +XmlRoleEnvironmentDataDeserializer.prototype.deserialize = function (xml) { + var configurationSettings = this._translateConfigurationSettings(xml); + var localResources = this._translateLocalResources(xml); + var currentInstance = this._translateCurrentInstance(xml); + var isEmulated = xml[ServiceRuntimeConstants.DEPLOYMENT][Constants.ATOM_METADATA_MARKER][ServiceRuntimeConstants.EMULATED] === 'true'; + var deploymentId = xml[ServiceRuntimeConstants.DEPLOYMENT][Constants.ATOM_METADATA_MARKER][ServiceRuntimeConstants.DEPLOYMENT_ID]; + var roles = this._translateRoles(xml, currentInstance, currentInstance.roleName); + + var roleEnvironmentData = { + id: deploymentId, + isEmulated: isEmulated, + configurationSettings: configurationSettings, + localResources: localResources, + currentInstance: currentInstance, + roles: roles + }; + + return roleEnvironmentData; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateConfigurationSettings = function (xml) { + var configurationSettingsMap = {}; + + if (xml[ServiceRuntimeConstants.CURRENT_INSTANCE] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.CONFIGURATION_SETTINGS] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.CONFIGURATION_SETTINGS][ServiceRuntimeConstants.CONFIGURATION_SETTING]) { + + var configurationSettings = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.CONFIGURATION_SETTINGS][ServiceRuntimeConstants.CONFIGURATION_SETTING]; + if (!Array.isArray(configurationSettings)) { + configurationSettings = [configurationSettings]; + } + + for (var configurationSetting in configurationSettings) { + var currentConfigurationSetting = configurationSettings[configurationSetting]; + configurationSettingsMap[currentConfigurationSetting[Constants.ATOM_METADATA_MARKER]['name']] = currentConfigurationSetting[Constants.ATOM_METADATA_MARKER]['value']; + } + } + + return configurationSettingsMap; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateLocalResources = function (xml) { + var localResourcesMap = {}; + + if (xml[ServiceRuntimeConstants.CURRENT_INSTANCE] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.LOCAL_RESOURCES] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.LOCAL_RESOURCES][ServiceRuntimeConstants.LOCAL_RESOURCE]) { + + var localResources = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.LOCAL_RESOURCES][ServiceRuntimeConstants.LOCAL_RESOURCE]; + if (!Array.isArray(localResources)) { + localResources = [localResources]; + } + + for (var localResource in localResources) { + var currentLocalResource = localResources[localResource]; + var currentLocalResourceName = currentLocalResource[Constants.ATOM_METADATA_MARKER]['name']; + delete currentLocalResource[Constants.ATOM_METADATA_MARKER]['name']; + + localResourcesMap[currentLocalResourceName] = currentLocalResource[Constants.ATOM_METADATA_MARKER]; + } + } + + return localResourcesMap; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateCurrentInstance = function (xml) { + var currentInstance = {}; + + currentInstance.id = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][Constants.ATOM_METADATA_MARKER]['id']; + currentInstance.roleName = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][Constants.ATOM_METADATA_MARKER]['roleName']; + currentInstance.faultDomain = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][Constants.ATOM_METADATA_MARKER]['faultDomain']; + currentInstance.updateDomain = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][Constants.ATOM_METADATA_MARKER]['updateDomain']; + currentInstance.endpoints = this._translateRoleInstanceEndpoints(xml); + + return currentInstance; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateRoleInstanceEndpoints = function(xml) { + var endpointsMap = { }; + + if (xml[ServiceRuntimeConstants.CURRENT_INSTANCE] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.ENDPOINTS] && + xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.ENDPOINTS][ServiceRuntimeConstants.ENDPOINT]) { + + var endpoints = xml[ServiceRuntimeConstants.CURRENT_INSTANCE][ServiceRuntimeConstants.ENDPOINTS][ServiceRuntimeConstants.ENDPOINT]; + if (!Array.isArray(endpoints)) { + endpoints = [endpoints]; + } + + for (var endpoint in endpoints) { + var currentEndpoint = endpoints[endpoint]; + var currentEndpointName = currentEndpoint[Constants.ATOM_METADATA_MARKER]['name']; + + delete currentEndpoint[Constants.ATOM_METADATA_MARKER]['name']; + endpointsMap[currentEndpointName] = currentEndpoint[Constants.ATOM_METADATA_MARKER]; + } + } + + return endpointsMap; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateRoles = function (xml, currentInstance, currentRole) { + var rolesMap = {}; + var roleInstances; + + if (xml[ServiceRuntimeConstants.ROLES] && + xml[ServiceRuntimeConstants.ROLES][ServiceRuntimeConstants.ROLE]) { + + var roles = xml[ServiceRuntimeConstants.ROLES][ServiceRuntimeConstants.ROLE]; + if (!Array.isArray(roles)) { + roles = [roles]; + } + + for (var role in roles) { + var currentIterationRole = roles[role]; + var currentIterationRoleName = currentIterationRole[Constants.ATOM_METADATA_MARKER]['name']; + roleInstances = this._translateRoleInstances(currentIterationRole); + + if (currentIterationRoleName === currentRole) { + roleInstances[currentInstance.id] = currentInstance; + } + + rolesMap[currentIterationRoleName] = roleInstances; + } + } + + if (!rolesMap[currentRole]) { + roleInstances = {}; + + roleInstances[currentInstance.id] = currentInstance; + + rolesMap[currentRole] = roleInstances; + } + + return rolesMap; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateRoleInstances = function (xml) { + var roleInstancesMap = {}; + + if (xml[ServiceRuntimeConstants.INSTANCES] && + xml[ServiceRuntimeConstants.INSTANCES][ServiceRuntimeConstants.INSTANCE]) { + + var instances = xml[ServiceRuntimeConstants.INSTANCES][ServiceRuntimeConstants.INSTANCE]; + if (!Array.isArray(instances)) { + instances = [instances]; + } + + for (var instance in instances) { + var currentIterationInstance = instances[instance]; + var currentIterationInstanceId = currentIterationInstance[Constants.ATOM_METADATA_MARKER]['id']; + delete currentIterationInstance[Constants.ATOM_METADATA_MARKER]['id']; + + currentIterationInstance[Constants.ATOM_METADATA_MARKER].endpoints = this._translateRoleInstanceEndpoints(currentIterationInstance[ServiceRuntimeConstants.ENDPOINTS]); + + roleInstancesMap[currentIterationInstanceId] = currentIterationInstance[Constants.ATOM_METADATA_MARKER]; + } + } + + return roleInstancesMap; +}; + +XmlRoleEnvironmentDataDeserializer.prototype._translateRoleInstanceEndpoints = function (endpointsXml) { + var endpointsMap = {}; + + if (endpointsXml && + endpointsXml[ServiceRuntimeConstants.ENDPOINT]) { + + var endpoints = endpointsXml[ServiceRuntimeConstants.ENDPOINT]; + if (!Array.isArray(endpoints)) { + endpoints = [endpoints]; + } + + for (var endpoint in endpoints) { + var currentEndpoint = endpoints[endpoint]; + var currentEndpointName = currentEndpoint[Constants.ATOM_METADATA_MARKER]['name']; + delete currentEndpoint[Constants.ATOM_METADATA_MARKER]['name']; + + endpointsMap[currentEndpointName] = currentEndpoint[Constants.ATOM_METADATA_MARKER]; + } + } + + return endpointsMap; +}; \ No newline at end of file diff --git a/lib/util/constants.js b/lib/util/constants.js index 51a245acf..ed3077e98 100644 --- a/lib/util/constants.js +++ b/lib/util/constants.js @@ -1195,6 +1195,326 @@ var Constants = { WRAP_ACCESS_TOKEN_EXPIRES_IN: 'wrap_access_token_expires_in' }, + /** + * Defines constants for use with Service Runtime. + * + * @const + * @type {string} + */ + ServiceRuntimeConstants: { + /** + * The runtime server endpoint element. + * + * @const + * @type {string} + */ + RUNTIME_SERVER_ENDPOINT: 'RuntimeServerEndpoint', + + /** + * The runtime server endpoints element. + * + * @const + * @type {string} + */ + RUNTIME_SERVER_ENDPOINTS: 'RuntimeServerEndpoints', + + /** + * The current instance element. + * + * @const + * @type {string} + */ + CURRENT_INSTANCE: 'CurrentInstance', + + /** + * The configuration settings element. + * + * @const + * @type {string} + */ + CONFIGURATION_SETTINGS: 'ConfigurationSettings', + + /** + * The configuration setting element. + * + * @const + * @type {string} + */ + CONFIGURATION_SETTING: 'ConfigurationSetting', + + /** + * The local resources element. + * + * @const + * @type {string} + */ + LOCAL_RESOURCES: 'LocalResources', + + /** + * The local resource element. + * + * @const + * @type {string} + */ + LOCAL_RESOURCE: 'LocalResource', + + /** + * The deployment element. + * + * @const + * @type {string} + */ + DEPLOYMENT: 'Deployment', + + /** + * The emulated element. + * + * @const + * @type {string} + */ + EMULATED: 'Emulated', + + /** + * The id element. + * + * @const + * @type {string} + */ + DEPLOYMENT_ID: 'id', + + /** + * The endpoints element. + * + * @const + * @type {string} + */ + ENDPOINTS: 'Endpoints', + + /** + * The endpoint element. + * + * @const + * @type {string} + */ + ENDPOINT: 'Endpoint', + + /** + * The roles element. + * + * @const + * @type {string} + */ + ROLES: 'Roles', + + /** + * The role element. + * + * @const + * @type {string} + */ + ROLE: 'Role', + + /** + * The instances element. + * + * @const + * @type {string} + */ + INSTANCES: 'Instances', + + /** + * The instance element. + * + * @const + * @type {string} + */ + INSTANCE: 'Instance', + + /** + * The role environment path element. + * + * @const + * @type {string} + */ + ROLE_ENVIRONMENT_PATH: 'RoleEnvironmentPath', + + /** + * The role environment changed event. + * + * @const + * @type {string} + */ + CHANGED: 'changed', + + /** + * The role environment changing event. + * + * @const + * @type {string} + */ + CHANGING: 'changing', + + /** + * The ready event. + * + * @const + * @type {string} + */ + READY: 'ready', + + /** + * The expected state element. + * + * @const + * @type {string} + */ + EXPECTED_STATE: 'ExpectedState', + + /** + * The incarnation element. + * + * @const + * @type {string} + */ + INCARNATION: 'Incarnation', + + /** + * The current state endpoint element. + * + * @const + * @type {string} + */ + CURRENT_STATE_ENDPOINT: 'CurrentStateEndpoint', + + /** + * The deadline element. + * + * @const + * @type {string} + */ + DEADLINE: 'Deadline', + + /** + * The current state element. + * + * @const + * @type {string} + */ + CURRENT_STATE: 'CurrentState', + + /** + * The status lease element. + * + * @const + * @type {string} + */ + STATUS_LEASE: 'StatusLease', + + /** + * The status lease element. + * + * @const + * @type {string} + */ + STATUS: 'Status', + + /** + * The expiration element. + * + * @const + * @type {string} + */ + EXPIRATION: 'Expiration', + + /** + * The client identifier element. + * + * @const + * @type {string} + */ + CLIENT_ID: 'ClientId', + + /** + * The acquire element. + * + * @const + * @type {string} + */ + ACQUIRE: 'Acquire', + + /** + * The release element. + * + * @const + * @type {string} + */ + RELEASE: 'Release', + + /** + * The different role instance target statuses. + * + * @const + * @type {string} + */ + RoleInstanceStatus: { + /** + * The ready status. + * + * @const + * @type {string} + */ + READY: 'ready', + + /** + * The busy status. + * + * @const + * @type {string} + */ + BUSY: 'busy' + }, + + /** + * The different role statuses. + * + * @const + * @type {string} + */ + RoleStatus: { + /** + * The started event. + * + * @const + * @type {string} + */ + STARTED: 'started', + + /** + * The stopped event. + * + * @const + * @type {string} + */ + STOPPED: 'stopped', + + /** + * The busy event. + * + * @const + * @type {string} + */ + BUSY: 'busy', + + /** + * The recycle event. + * + * @const + * @type {string} + */ + RECYCLE: 'recycle' + } + }, + /** * Defines constants for use with HTTP headers. */ diff --git a/lib/util/js2xml.js b/lib/util/js2xml.js new file mode 100644 index 000000000..ba82c98dd --- /dev/null +++ b/lib/util/js2xml.js @@ -0,0 +1,122 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 xmlbuilder = require('xmlbuilder'); + +var azureutil = require('./util'); +var Constants = require('./constants'); + +exports = module.exports; + +exports.serialize = function (entity) { + if (azureutil.objectKeysLength(entity) !== 1) { + throw new Error('Invalid XML root element.'); + } + + var doc = xmlbuilder.create(); + + var rootElementName = azureutil.objectFirstKey(entity); + + doc = doc.begin(rootElementName, { version: '1.0', encoding: 'utf-8', standalone: 'yes' }); + + if (entity[rootElementName][Constants.ATOM_METADATA_MARKER]) { + for (var metadata in entity[rootElementName][Constants.ATOM_METADATA_MARKER]) { + doc.att(metadata, entity[rootElementName][Constants.ATOM_METADATA_MARKER][metadata]); + } + } + + for (var attribute in entity[rootElementName]) { + doc = _writeAtomEntryValue(doc, attribute, entity[rootElementName][attribute]); + } + + doc = doc.doc(); + + return doc.toString(); +}; + +/* +* Writes a single property for an entry or complex type. +* +* @param {object} parentElement Parent DOM element under which the property should be added. +* @param {string} name Property name. +* @param {object} value Property value. +* @return {object} The current DOM element. +*/ +function _writeAtomEntryValue (parentElement, name, value) { + var ignored = false; + var propertyTagName = name; + + if (!azureutil.stringIsEmpty(value) && + typeof value === 'object' && + !_isDate(value)) { + + if (Array.isArray(value) && value.length > 0) { + for (var i in value) { + parentElement = _writeAtomEntryValue(parentElement, name, value[i]); + } + + // For an array no element was actually added at this level, so skip uping level. + ignored = true; + } else if (typeof value === 'object') { + parentElement = parentElement.ele(propertyTagName); + for (var propertyName in value) { + if (propertyName !== Constants.ATOM_METADATA_MARKER) { + parentElement = _writeAtomEntryValue(parentElement, propertyName, value[propertyName]); + } + } + } else { + // Ignoring complex elements + ignored = true; + } + } else { + parentElement = parentElement.ele(propertyTagName); + if (!azureutil.stringIsEmpty(value)) { + parentElement = parentElement.txt(formatToString(value)); + } + } + + if (value && value[Constants.ATOM_METADATA_MARKER]) { + // include the metadata + var attributeList = value[Constants.ATOM_METADATA_MARKER]; + for (var attribute in attributeList) { + parentElement = parentElement.att(attribute, attributeList[attribute]); + } + } + + if (!ignored) { + parentElement = parentElement.up(); + } + + return parentElement; +}; + +/* +* Checks whether the specified value is a Date object. +* @param {string} value Value to check. +* @return {bool} true if the value is a Date object; false otherwise. +*/ +function _isDate (value) { + return Object.prototype.toString.call(value) === "[object Date]"; +}; + +/** +* Formats a value by invoking .toString() on it. +* @param {object} value Value to format. +* @return {string} The formatted text. +*/ +function formatToString(value) { + return value.toString(); +} \ No newline at end of file diff --git a/lib/util/util.js b/lib/util/util.js index f0bd8611f..26946a435 100644 --- a/lib/util/util.js +++ b/lib/util/util.js @@ -31,6 +31,33 @@ exports.encodeUri = function (uri) { .replace(/\*/g, '%2A'); }; +/** +* Returns the number of keys (properties) in an object. +* +* @param {object} value The object which keys are to be counted. +* @return {number} The number of keys in the object. +*/ +exports.objectKeysLength = function (value) { + return _.keys(value).length; +}; + +/** +* Returns the name of the first property in an object. +* +* @param {object} value The object which key is to be returned. +* @return {number} The name of the first key in the object. +*/ +exports.objectFirstKey = function (value) { + if (value) { + for (var key in value) { + return key; + } + } + + // Object has no properties + return null; +}; + /** * Checks if a value is null or undefined. * diff --git a/package.json b/package.json index 274717825..ba8f08390 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,40 @@ { - "name": "azure", - "author": "Microsoft Corporation", - "version": "0.5.2", - "description": "Windows Azure Client Library for node", - "tags" : ["azure", "sdk"], - "keywords": [ "node", "azure" ], - "main": "./lib/azure.js", - "engines": { "node": ">= 0.4.7" }, - "licenses": [ { "type": "Apache", "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], - "dependencies": { - "xml2js" : ">= 0.1.11", - "sax": ">= 0.1.1", - "qs": ">= 0.3.1", - "log": ">= 1.2.0", - "xmlbuilder": ">= 0.3.1", - "mime": ">= 1.2.4", - "dateformat": "1.0.2-1.2.3", - "underscore": ">= 1.3.1", - "underscore.string": ">= 2.0.0" - }, - "devDependencies": { - "mocha": "*", - "jshint": "*" - }, - "homepage": "http://github.com/WindowsAzure/azure-sdk-for-node", - "repository": { - "type": "git", - "url": "git@github.com:WindowsAzure/azure-sdk-for-node.git" - }, - "bugs" : { - "url" : "http://github.com/WindowsAzure/azure-sdk-for-node/issues" - }, - "scripts": { - "test": "node test/runtests.js", - "jshint": "node test/runjshint.js", - "extendedtests": "node test/runextendedtests.js" - } -} + "name": "azure", + "author": "Microsoft Corporation", + "version": "0.5.2", + "description": "Windows Azure Client Library for node", + "tags" : ["azure", "sdk"], + "keywords": [ "node", "azure" ], + "main": "./lib/azure.js", + "engines": { "node": ">= 0.4.7" }, + "licenses": [ { "type": "Apache", "url": "http://www.apache.org/licenses/LICENSE-2.0" } ], + "dependencies": { + "xml2js" : ">= 0.1.11", + "sax": ">= 0.1.1", + "qs": ">= 0.3.1", + "log": ">= 1.2.0", + "xmlbuilder": ">= 0.3.1", + "mime": ">= 1.2.4", + "dateformat": "1.0.2-1.2.3", + "underscore": ">= 1.3.1", + "underscore.string": ">= 2.0.0", + "node-uuid": "> = 1.2.0" + }, + "devDependencies": { + "mocha": "*", + "jshint": "*" + }, + "homepage": "http://github.com/WindowsAzure/azure-sdk-for-node", + "repository": { + "type": "git", + "url": "git@github.com:WindowsAzure/azure-sdk-for-node.git" + }, + "bugs" : { + "url" : "http://github.com/WindowsAzure/azure-sdk-for-node/issues" + }, + "scripts": { + "test": "node test/runtests.js", + "jshint": "node test/runjshint.js", + "extendedtests": "node test/runextendedtests.js" + } +} \ No newline at end of file diff --git a/projects/VS/Azure.csproj b/projects/VS/Azure.csproj index 3b20b5405..2b1f3d1bf 100644 --- a/projects/VS/Azure.csproj +++ b/projects/VS/Azure.csproj @@ -117,6 +117,51 @@ lib\http\webresource.js + + lib\serviceruntime\fileinputchannel.js + + + lib\serviceruntime\goalstatedeserializer.js + + + lib\serviceruntime\namedpipeinputchannel.js + + + lib\serviceruntime\namedpipeoutputchannel.js + + + lib\serviceruntime\protocol1runtimeclient.js + + + lib\serviceruntime\protocol1runtimecurrentstateclient.js + + + lib\serviceruntime\protocol1runtimegoalstateclient.js + + + lib\serviceruntime\roleenvironment.js + + + lib\serviceruntime\roleinstance.js + + + lib\serviceruntime\runtimekernel.js + + + lib\serviceruntime\runtimeversionmanager.js + + + lib\serviceruntime\runtimeversionprotocolclient.js + + + lib\serviceruntime\xmlcurrentstateserializer.js + + + lib\serviceruntime\xmlgoalstatedeserializer.js + + + lib\serviceruntime\xmlroleenvironmentdatadeserializer.js + lib\services\blob\blobservice.js @@ -255,6 +300,9 @@ lib\util\iso8061date.js + + lib\util\js2xml.js + lib\util\rfc1123date.js @@ -288,6 +336,15 @@ test\runtests.js + + test\serviceruntime\roleenvironment-tests.js + + + test\serviceruntime\runtimeversionmanager-tests.js + + + test\serviceruntime\runtimeversionprotocolclient-tests.js + test\services\blob\blobservice-longrunning-tests.js diff --git a/test/serviceruntime/roleenvironment-tests.js b/test/serviceruntime/roleenvironment-tests.js new file mode 100644 index 000000000..0eb289ff8 --- /dev/null +++ b/test/serviceruntime/roleenvironment-tests.js @@ -0,0 +1,655 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 assert = require('assert'); + +var RuntimeKernel = require('../../lib/serviceruntime/runtimekernel'); +var NamedPipeInputChannel = require('../../lib/serviceruntime/namedpipeinputchannel'); +var RuntimeVersionProtocolClient = require('../../lib/serviceruntime/runtimeversionprotocolclient'); +var RuntimeVersionManager = require('../../lib/serviceruntime/runtimeversionmanager'); + +var Constants = require('../../lib/util/constants'); +var ServiceRuntimeConstants = Constants.ServiceRuntimeConstants; + +var azure = require('../../lib/azure'); + +suite('roleenvironment-tests', function () { + test('IsAvailable', function (done) { + azure.RoleEnvironment.isAvailable(function (error1, isAvailable1) { + assert.notEqual(error1, null); + assert.equal(isAvailable1, false); + + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + azure.RoleEnvironment.isAvailable(function (error2, isAvailable2) { + assert.equal(error2, null); + assert.equal(isAvailable2, true); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + + done(); + }); + }); + }); + + test('GetLocalResourcesNoGoalStateNamedPipe', function (done) { + assert.throws( + azure.RoleEnvironment.getLocalResources(function () { }), + Error + ); + + done(); + }); + + test('GetDeploymentId', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + azure.RoleEnvironment.getDeploymentId(function (error, id) { + assert.equal(error, null); + assert.equal(id, 'mydeploymentid'); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + + done(); + }); + }); + + test('GetLocalResources', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + azure.RoleEnvironment.getLocalResources(function (error, localResources) { + assert.equal(error, null); + assert.notEqual(localResources, null); + assert.notEqual(localResources['DiagnosticStore'], null); + assert.notEqual(localResources['DiagnosticStore']['path'], null); + assert.notEqual(localResources['DiagnosticStore']['sizeInMB'], null); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + + done(); + }); + }); + + test('GetRoles', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + azure.RoleEnvironment.getRoles(function (error, roles) { + assert.equal(error, null); + assert.notEqual(roles, null); + assert.notEqual(roles['role1'], null); + assert.notEqual(roles['role1']['deployment16(191).test.role1_IN_0'], null); + assert.notEqual(roles['role1']['geophotoapp_IN_0'], null); + + assert.notEqual(roles['role2'], null); + assert.notEqual(roles['role2']['deployment16(191).test.role2_IN_0'], null); + assert.notEqual(roles['role2']['deployment16(191).test.role2_IN_1'], null); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + + done(); + }); + }); + + test('CalculateChanges', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + // do first call to get environment data + azure.RoleEnvironment.getRoles(function (error, roles) { + assert.equal(error, null); + assert.notEqual(roles, null); + + // explicitly call calculate changes to reread and compare to previous environment data + azure.RoleEnvironment._calculateChanges(function (calculateChangesError, changes) { + assert.equal(calculateChangesError, null); + assert.notEqual(changes, null); + assert.equal(changes.length, 0); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + + done(); + }); + }); + }); + + test('testRequestRecycle', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalNamedPipeOutputChannelWriteOutputChannel = runtimeKernel.namedPipeOutputChannel.writeOutputChannel; + var writtenData; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = function (data, callback) { + writtenData = data; + callback(); + }; + + azure.RoleEnvironment.requestRecycle(function (error) { + assert.equal(error, null); + + assert.equal(writtenData, 'Fri Dec 31 9999 15:59:59 GMT-0800 (Pacific Standard Time)1recycle'); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = originalNamedPipeOutputChannelWriteOutputChannel; + + done(); + }); + }); + + test('testClearStatus', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalNamedPipeOutputChannelWriteOutputChannel = runtimeKernel.namedPipeOutputChannel.writeOutputChannel; + var writtenData; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = function (data, callback) { + writtenData = data; + callback(); + }; + + azure.RoleEnvironment.clearStatus(function (error) { + assert.equal(error, null); + + assert.equal(writtenData, ''); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = originalNamedPipeOutputChannelWriteOutputChannel; + + done(); + }); + }); + + test('testSetStatus', function (done) { + var runtimeKernel = RuntimeKernel.getKernel(); + var originalNamedPipeInputChannelReadData = runtimeKernel.namedPipeInputChannel.readData; + runtimeKernel.namedPipeInputChannel.readData = function (name, callback) { + if (name === '\\\\.\\pipe\\WindowsAzureRuntime') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + } else if (name === 'SomePath.GoalState') { + callback(undefined, + "" + + "" + + "1" + + "Started" + + "C:\\file.xml" + + "\\.\pipe\WindowsAzureRuntime.CurrentState" + + "9999-12-31T23:59:59.9999999" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalFileInputChannelReadData = runtimeKernel.fileInputChannel.readData; + runtimeKernel.fileInputChannel.readData = function (name, callback) { + if (name === 'C:\\file.xml') { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""); + } else { + callback('wrong file'); + } + }; + + var originalNamedPipeOutputChannelWriteOutputChannel = runtimeKernel.namedPipeOutputChannel.writeOutputChannel; + var writtenData; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = function (data, callback) { + writtenData = data; + callback(); + }; + + azure.RoleEnvironment.setStatus(ServiceRuntimeConstants.RoleInstanceStatus.READY, new Date(2012, 1, 1, 12, 10, 10, 0), function (error1) { + assert.equal(error1, null); + + assert.equal(writtenData, 'Wed Feb 01 2012 12:10:10 GMT-0800 (Pacific Standard Time)1started'); + + azure.RoleEnvironment.setStatus(ServiceRuntimeConstants.RoleInstanceStatus.BUSY, new Date(2012, 1, 1, 10, 10, 10, 0), function (error2) { + assert.equal(error2, null); + + assert.equal(writtenData, 'Wed Feb 01 2012 10:10:10 GMT-0800 (Pacific Standard Time)1busy'); + + runtimeKernel.namedPipeInputChannel.readData = originalNamedPipeInputChannelReadData; + runtimeKernel.fileInputChannel.readData = originalFileInputChannelReadData; + runtimeKernel.protocol1RuntimeGoalStateClient.currentGoalState = null; + runtimeKernel.protocol1RuntimeGoalStateClient.currentEnvironmentData = null; + runtimeKernel.namedPipeOutputChannel.writeOutputChannel = originalNamedPipeOutputChannelWriteOutputChannel; + + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/serviceruntime/runtimeversionmanager-tests.js b/test/serviceruntime/runtimeversionmanager-tests.js new file mode 100644 index 000000000..71b4638dc --- /dev/null +++ b/test/serviceruntime/runtimeversionmanager-tests.js @@ -0,0 +1,51 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 assert = require('assert'); + +var NamedPipeInputChannel = require('../../lib/serviceruntime/namedpipeinputchannel'); + +var RuntimeVersionProtocolClient = require('../../lib/serviceruntime/runtimeversionprotocolclient'); +var RuntimeVersionManager = require('../../lib/serviceruntime/runtimeversionmanager'); + +suite('runtimeversionmanager-tests', function () { + test('GetRuntimeClient', function (done) { + var inputChannel = new NamedPipeInputChannel(); + inputChannel.readData = function (name, callback) { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + }; + + var runtimeVersionProtocolClient = new RuntimeVersionProtocolClient(inputChannel); + var runtimeKernel = { + getGoalStateClient: function () { return {}; }, + getCurrentStateClient: function () { return {}; } + }; + + var runtimeVersionManager = new RuntimeVersionManager(runtimeVersionProtocolClient, runtimeKernel); + runtimeVersionManager.getRuntimeClient('', function (error, runtimeClient) { + assert.equal(error, undefined); + assert.notEqual(runtimeClient, null); + + done(); + }); + }); +}); diff --git a/test/serviceruntime/runtimeversionprotocolclient-tests.js b/test/serviceruntime/runtimeversionprotocolclient-tests.js new file mode 100644 index 000000000..3df51e178 --- /dev/null +++ b/test/serviceruntime/runtimeversionprotocolclient-tests.js @@ -0,0 +1,69 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* 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 assert = require('assert'); + +var NamedPipeInputChannel = require('../../lib/serviceruntime/namedpipeinputchannel'); +var RuntimeVersionProtocolClient = require('../../lib/serviceruntime/runtimeversionprotocolclient'); + +suite('runtimeversionprotocolclient-tests', function () { + test('GetVersionMapSingle', function (done) { + var inputChannel = new NamedPipeInputChannel(); + inputChannel.readData = function (name, callback) { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + ""); + }; + + var runtimeVersionProtocolClient = new RuntimeVersionProtocolClient(inputChannel); + runtimeVersionProtocolClient.getVersionMap('', function (error, versions) { + assert.equal(error, null); + assert.notEqual(versions, null); + assert.equal(versions["2011-03-08"], "SomePath.GoalState"); + + done(); + }); + }); + + test('GetVersionMapArray', function (done) { + var inputChannel = new NamedPipeInputChannel(); + inputChannel.readData = function (name, callback) { + callback(undefined, + "" + + "" + + "" + + "" + + "" + + "" + + ""); + }; + + var runtimeVersionProtocolClient = new RuntimeVersionProtocolClient(inputChannel); + runtimeVersionProtocolClient.getVersionMap('', function (error, versions) { + assert.equal(error, null); + assert.notEqual(versions, null); + assert.equal(versions["2011-03-08"], "SomePath.GoalState"); + assert.equal(versions["2011-08-08"], "SomeOtherPath.GoalState"); + + done(); + }); + }); +}); diff --git a/test/services/blob/blobservice-longrunning-tests.js b/test/services/blob/blobservice-longrunning-tests.js index 0ddc97dbc..f8d556213 100644 --- a/test/services/blob/blobservice-longrunning-tests.js +++ b/test/services/blob/blobservice-longrunning-tests.js @@ -40,13 +40,13 @@ var mockServerClient; var currentTestName; suite('blobservice-longrunning-tests', function () { - setup: function (done) { + setup(function (done) { blobService = azure.createBlobService(ServiceClient.DEVSTORE_STORAGE_ACCOUNT, ServiceClient.DEVSTORE_STORAGE_ACCESS_KEY, ServiceClient.DEVSTORE_BLOB_HOST); done(); }); - teardown: function (done) { + teardown(function (done) { var deleteFiles = function () { // delete test files var list = fs.readdirSync('./'); diff --git a/test/testlist.txt b/test/testlist.txt index c17bd8157..4ed7c2cb2 100644 --- a/test/testlist.txt +++ b/test/testlist.txt @@ -1,3 +1,6 @@ +serviceruntime/roleenvironment-tests.js +serviceruntime/runtimeversionmanager-tests.js +serviceruntime/runtimeversionprotocolclient-tests.js services/blob/blobservice-tests.js services/blob/sharedaccesssignature-tests.js services/blob/sharedkey-tests.js diff --git a/test/util/util-tests.js b/test/util/util-tests.js index cf6c3c24b..dcd8d8f52 100644 --- a/test/util/util-tests.js +++ b/test/util/util-tests.js @@ -142,4 +142,24 @@ suite('util-tests', function() { done(); }); + + test('keys counting works', function (done) { + // int positives + assert.equal(util.objectKeysLength({ }), 0); + assert.equal(util.objectKeysLength(null), 0); + assert.equal(util.objectKeysLength({ prop1: 1 }), 1); + assert.equal(util.objectKeysLength({ prop1: 1, prop2: 2 }), 2); + + done(); + }); + + test('first key works', function (done) { + // int positives + assert.equal(util.objectFirstKey({}), null); + assert.equal(util.objectFirstKey(null), null); + assert.equal(util.objectFirstKey({ prop1: 1 }), 'prop1'); + assert.equal(util.objectFirstKey({ prop1: 1, prop2: 2 }), 'prop1'); + + done(); + }); }); \ No newline at end of file