Doxygen generated view of CodePlex download: Source Documentation
Introduction
This article presents an approach for providing Windows Azure Drives (a.k.a. XDrive) to any cloud-based web applications through RoleEntryPoint callback methods and exposing successful mounting results within an environment variable through Global.asax callback method derived from the HttpApplication base class. The article will demonstrate how to use this approach in mounting XDrives before a .NET (C#) cloud-base web application is running.
The next article will take this approach and apply it to PHP cloud-based web applications.
Part 1: WebRole mounting XDrives in RoleEntryPoint.OnStart(): C#
This article implements RoleEntryPoint.OnStart() callback to perform the following:
- Read ServiceConfiguration.cscfg and ServiceDefinition.csdef files that is configured for mounting zero (if none is provided) or more XDrives.
- Create LocalStorage that will be used as local read cache when mounting XDrives.
- Mount XDrives having an access mode of either write or read-only.
- Expose successful XDrive mounts by populating an environment variable X_DRIVES, and provide the following information:
- Friendly XDrive Label
- Drive Letter
- Drive Access Mode
In addition:
- How to configure mounting of XDrives within ServiceConfiguration.cscfg and ServiceDefinition.csdef files.
- Demonstrate using created environment variable X_DRIVES within a web role application.
Part 2: Cloud-Based PHP Web Application using XDrives
The next article presents how to create and deploy a cloud-based PHP web application to be deployed upon Windows Azure platform, and be able to leverage Windows Azure Drives.
It will include a discussion of how add this feature using either of the following PHP Developer Tools that are integrated with Windows Azure platform support:
Windows Azure Drive Overview
Most, if not all applications, require a durable data store. Windows Azure Drives is an infrastructure solution that provides a durable NTFS drive within the Windows Azure platform.
One of the challenges is taking existing Windows applications and running them in the cloud while making sure their data is durable while using the standard Windows NTFS APIs. With Windows Azure Drive, Windows Azure applications running in the cloud can use existing NTFS APIs to access a durable drive. This can significantly ease the migration of existing Windows applications to the cloud. The Windows Azure application can read from or write to a drive letter (e.g., X:\) that represents a durable NTFS volume for storing and accessing data.
The durable drive is implemented as a Windows Azure Page Blob containing an NTFS-formatted Virtual Hard Drive (VHD). An additional benefit of this approach is that the VHD can be downloaded from the Blob store at any time, thus providing customers easy access to their data.
For additional information on Windows Azure drives, please refer to the Windows Azure Drive Whitepaper.
About Web Applications needing Durable Volumes
Existing or new web applications, whether running in Windows or not, many will require a durable location to hold content that are essential in running the site. This content could include files of any type: site configuration, images, style sheets, logs, databases, etc... Access modes to these files will either be read-only or writable, and access location will either be unbuffered on-disc or cached.
One of the challenges of migrating these aforementioned web applications and running them in cloud is making sure their data needs are durable. Windows Azure Drives eases the migration of web applications running on Windows platform to the cloud because their durable needs are still accessible because any of their file operations are still using standard Windows NTFS APIs.
Configuring Web Applications access to Durable Volumes
The implementation question in using XDrives are how they are integrated into a migrated web application that expect a durable location while in the cloud.
When a web application is installed on local Windows host, the installation administrator is chooses a hard drive (e.g., HOMEDRIVE C:\) that will be expected to hold the durable data based upon available disc space and assigning what amount of local on-disk cache of the drive’s data. With this chosen hard drive, known by its drive letter, the administrator then builds out the directories, sets access permissions, and copies in both the web application and its data.
When establishing XDrives as the durable storage for a migrated web application to the cloud, the installation administrator is still given choice to in assigning how disc space and assigning what amount of local on-disk cache of the drive’s data will be that will be required. However, what is different from the standard local host installation is that the location of the drive is not known until runtime in the cloud. In other words, the web application defines its durable needs when creating and mount an XDrive, then Windows Azure assigns a drive letter for this mounted XDrive to the web application. With this runtime assigned drive letter to an XDrive, the web application now has a known location in the cloud to a durable NTFS volume for storing and accessing file-based data.
So the twist in providing durable volumes to a web application in the cloud is for it be malleable to receive XDrives' location via drive letters which will only occur at runtime.
Approaches to Mounting XDrives
There are two approaches in mounting XDrives and providing their locations to a cloud-based web applications. Both approaches require that the web applications have been started as a process within Windows Azure Web instance host. The differences are how web applications at runtime (i.e., started) are informed as to the status of all successfully mounted XDrives: volume location or drive letter, and established access mode.
- Mounting XDrives during the loading of the web application's assembly before it starts.
The location (drive letters) of the successfully mounted XDrives have to be provided indirectly only after the web application has started. - Mounting XDrives after web application has started and anytime during it runtime.
The web application can acquire the location (drive letters) of the successfully mounted XDrives directly since it was performed during its runtime.
Providing XDrives to any Cloud-based Web Applications
If a web application is mounting XDrives during its runtime, then this web application has to be written in a .NET supported languages (VB, C#, C++, F#, and JScript) to access the Windows Azure Cloud functionality within Microsoft.WindowsAzure.StorageClient namespace.
However, if XDrives are being mounted before a web application has started, then the web application can be any programming language, including PHP and Java. What has to be provided is shared binary (DLL) that is used during loading of the web application's assembly. This DLL will have to perform the following functionality in this order, as for example to service a PHP web application, which will be approach established by this article:
- While Windows Azure is initializing the WebRole instance of web application
- Read configuration expectation for one or more XDrives, which will include:
- Drive size
- Size of local on-disk cache of the drive’s data
- Access mode
- Mounting Windows Azure Drives
- Provide the following information for each successfully mounted XDrives to a location that is accessible by any web application. For example, by setting an environment variable
- friendly label
- drive letters
- established access mode
- During web application runtime (after startup)
- Read environment variable set during the initiation of WebRole.
- Web application will define any directory path that is expected to reference an XDrive by its drive letter, will instead reference by association to a friendly label.
- Durable NTFS volumes are provided to web application.
- While Windows Azure is stopping the WebRole instance of PHP web applicationspan>
- Unmounting Windows Azure Drives
Web Role Lifecycle in Windows Azure
To fill this void between XDrives and PHP applications, the approach of this article is to create a WebRole DLL in C# that would manage all XDrives' mounting before the PHP web application starts. To implement this, first one needs to understand the lifecycle of a web role.
Web Roles
The architecture of a service hosted in Windows Azure is based on discrete scalable components built with managed code. These components are termed roles.
A web role is a role that is customized for web application programming.
Lifecycle Management
A web role's lifecycle is managed by callback methods that handles its startup and stopping sequences.
- WaWebHost.exe (Windows Azure Web instance Host) starts a process and web role instance within it.
- Internet Information Services (IIS) 7.0 Hosted Web Core is activated and web role runs in integrated pipeline mode upon it.
- Web role application's assembly is loaded and RoleEntryPoint.OnStart() is called.
- Global.Application_Start() is called on the start of the web service.
- Fired when the first instance of the HttpApplication class is created.
- It allows you to create objects that are accessible by all HttpApplication instances.
- Web role application runs.
- Global.Application_End() is called as the web application's end event.
- Fired when the last instance of an HttpApplication class is destroyed.
- It's fired only once during an application's lifetime.
- RoleEntryPoint.OnStop() is called.
- Internet Information Services (IIS) 7.0 Hosted Web Core is deactivated.
- WaWebHost.exe process is stopped.
Sample Cloud-Based C# Web Application using Windows Azure Drives
Setup Prerequisites
- Windows 7, Windows Server 2008 or Windows Vista SP1
- Windows Azure Software Development Kit (February 2010 or later): Download
- Visual Studio 2008 or above
CodePlex Download Contents
This download site is from the open source project hosted upon CodePlex: Windows Azure Command-line Tools for PHP Developers.
The Windows Azure Command-line Tools for PHP enable developers to easily package and deploy PHP applications to Windows Azure using a simple command-line tool. The tools allow creating new applications or converting existing PHP applications to Windows Azure and by creating the deployment package (.cspkg) and Configuration file (.cscfg).
From the the zip file within this page contains the WebRole sources that deals with mounting Windows Azure Drives: Download
After download and unzipping azurephptools-*.zip, the discussion of this article pertains sub-directory \azurephptools-*\web_role_dll_zip. Its contents contain a Visual Studio 2008 project that creates a WebRole DLL and C# Web Application that mounts XDrives based upon provided Service Configuration.
Doxygen generated view of these C# sources in CodePlex are available here: Documentation
The download contains the following files:
Web Role DLL
- WebRole.cs - Uses RoleEntryPoint event handlers are used to mount and unmount XDrives.
- XDrives.cs - class XDrives reads requested XDrives configuration and mounts them.
- Global application class (Global.asax & Global.asax.cs) - Gathers information of successfully mounted XDrives and establishes Windows environment variable X_DRIVES with this information.
Example Service Configuration
- ServiceDefinition.csdef - Declares LocalStorage and defines settings for XDrives
- ServiceConfiguration.cscfg - Provides example configuration setting on how to mount XDrives at runtime by aforementioned Web Role DLL.
Example Windows Azure C# Web Application
- XDrivesTest.cs - Gathers mounted XDrives information from X_DRIVES, and based upon each mounted XDrives' access mode, either creates & writes files or read files.
- Default page (Default.asap, Default.asap.cs) - Presents results from testing as performed by class XDrivesTest.
Added Reference
Added to solution's references is required Microsoft.WindowsAzure.CloudDrive:
How It Works
Implementing Web Role Event Handlers
When implementing this Web Role, only the following web role lifecycle event handlers were implemented:
WebRole.cs
public class WebRole : RoleEntryPoint
{ private XDrives m_XDrives = new XDrives();
public override bool OnStart()
{
DiagnosticMonitor.Start("DiagnosticsConnectionString");
m_XDrives.MountXDrives();
RoleEnvironment.Changing += RoleEnvironmentChanging;
return base.OnStart();
}
public override void OnStop()
{
m_XDrives.UnmountXDrives();
base.OnStop();
}
}<}
Global.asax.cs
public class Global : System.Web.HttpApplication
{
private XDrives m_XDrives = new XDrives();
protected void Application_Start(object sender, EventArgs e)
{
m_XDrives.SetXDrivesEnvironmentVariable();
}
}</pre}< />
RoleEntryPoint.OnStart()
The event handler mounts XDrives based upon provided configuration settings by calling XDrives.MountXDrives().
Code: XDrives.MountXDrives()
- Reads configurations from ServiceConfiguration.cscfg. More later. RoleEnvironment.GetConfigurationSettingValue()
- If there are not any XDrives configurations within ServiceConfiguration.cscfg, then it exits.
- Parses the XDrive configurations into a dictionary.
- Reads Local Storage configuration from ServiceDefinition.csdef: RoleEnvironment.GetLocalResource()
- Get Cloud Storage Account.
- Initialized mount read cache to be used by XDrives within this VM: CloudDrive.InitializeCache()
- Gathers a listing of currently mounted XDrives: CloudDrive.GetMountedDrives()
- Create a new Blob service client: CloudStorageAccountStorageClientExtensions.CreateCloudBlobClient()
- Loop through configuration and mount XDrive: XDrives.MountXDrive()
public void MountXDrives()
{
try
{
if (!GetXDrivesConfig())
{
return;
}
Trace.TraceInformation("Mounting X-Drives");
if (!InitializeXDrives())
{
return;
}
Trace.TraceInformation("Mounting {0} X-Drives Configurations",
m_aXDriveConfigs.Count
);
foreach (IDictionary<string, string> dicXDriveConfig in m_aXDriveConfigs)
{
MountXDrive(dicXDriveConfig);
}
}
catch (Exception ex)
{
Trace.TraceError("Unexpected Exception: Failed to mount X-drives: {0): {1)",
ex.GetBaseException(),
ex.Message
);
}
}
Code: XDrives.GetXDrivesConfig()
private bool GetXDrivesConfig()
{
string sXDrivesConfig = ReadXDrivesConfig();
if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
{
return false;
}
m_aXDriveConfigs = ParseXDriveConfigs(sXDrivesConfig);
if (null == m_aXDriveConfigs || 0 == m_aXDriveConfigs.Count)
{
return false;
}
return true;
}
Code: XDrives.ReadXDrivesConfig()
public string ReadXDrivesConfig()
{
string sXDrivesConfig = string.Empty;
try
{
sXDrivesConfig = RoleEnvironment.GetConfigurationSettingValue(XDRIVES_CONFIG_SETTING);
if (null != sXDrivesConfig || string.Empty != sXDrivesConfig)
{
sXDrivesConfig = sXDrivesConfig.Trim();
}
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drives: Unexpected Exception: Failed Read X-Drives Config: {0}",
ex.Message
);
}
return sXDrivesConfig;
}
Code: XDrives.InitializeXDrives()
private bool InitializeXDrives()
{
Trace.TraceInformation("Initializing for mounting X-Drives");
if (!GetCloudStorageAccount() || (null == m_oCloudStorageAccount) )
{
return false;
}
string sXDrivesLocalCacheName
= RoleEnvironment.GetConfigurationSettingValue(XDRIVES_CONFIG_LOCAL_CACHE_SETTING).Trim();
if (null == sXDrivesLocalCacheName || 0 == sXDrivesLocalCacheName.Length)
{
Trace.TraceWarning("No Local Cache for X-Drives was defined in Service Configuration file.");
return false;
}
LocalResource localCache = RoleEnvironment.GetLocalResource(sXDrivesLocalCacheName);
Char[] backSlash = { '\\' };
String localCachePath = localCache.RootPath.TrimEnd(backSlash);
CloudDrive.InitializeCache(localCachePath, localCache.MaximumSizeInMegabytes);
m_iMaximumSizeInMegabytes = localCache.MaximumSizeInMegabytes;
int iSumAllMountReadCacheSizeMB = XDrives.GetSumXDrivesMountCache(m_aXDriveConfigs);
if (iSumAllMountReadCacheSizeMB > localCache.MaximumSizeInMegabytes)
{
Trace.TraceWarning(
"Sum of all X-Drives Mount Read Cache size {0} MB exceeds requested Local Cache size {1} MB",
iSumAllMountReadCacheSizeMB,
localCache.MaximumSizeInMegabytes
);
return false;
}
m_aXDrivesPathMounted = GetMountedDrivesPaths();
if (MAX_XDRIVES_MOUNTS_PER_INSTANCE < m_aXDriveConfigs.Count)
{
Trace.TraceWarning(
"Number of X-Drives Mount request exceeds maximum {0}",
MAX_XDRIVES_MOUNTS_PER_INSTANCE
);
return false;
}
m_blobClient
= m_oCloudStorageAccount.CreateCloudBlobClient();
return true;
}
Code: XDrives.MountXDrive()
- Validates provided XDrive configuration
- Using previously created Blob service client, create Blob container, which will hold page blob VHDs: CloudBlobClient.GetContainerReference()
- Create a new instance of a CloudDrive object from a storage account: CloudStorageAccountCloudDriveExtensions.CreateCloudDrive()
- If requested by XDrive configuration, then create a NTFS-formatted Windows Azure drive and its associated page blob: CloudDrive.Create()
- Attempt mounting XDrive: XDrives.DoCloudDriveMount()
- If first attempt fails mounting XDrive with access_mode write, but it is determined to try redoing mounting XDrive with access_mode readonly, a second attempt is performed: XDrives.DoCloudDriveMount()
private bool MountXDrive(
IDictionary<string, string> dicXDriveConfig
)
{
try
{
string sXDriveLabel = string.Empty;
string sBlobContainerName = string.Empty;
string sPageBlobVhdName = string.Empty;
AccessMode eAccessMode = AccessMode.None;
bool bXDriveCreateIfNotExist = false;
int iXDriveSizeMB = 0;
int iXDriveMountCacheSizeMB = 0;
if (!GetXDriveConfig(
dicXDriveConfig,
ref sXDriveLabel,
ref sBlobContainerName,
ref sPageBlobVhdName,
ref eAccessMode,
ref bXDriveCreateIfNotExist,
ref iXDriveSizeMB,
ref iXDriveMountCacheSizeMB
)
) {
return false;
}
if (!CreateXDriveBlobContainer(
sXDriveLabel,
sBlobContainerName
)
) {
return false;
}
CloudDrive oCloudDrive
= m_oCloudStorageAccount.CreateCloudDrive(
m_blobClient
.GetContainerReference(sBlobContainerName)
.GetPageBlobReference(sPageBlobVhdName)
.Uri.ToString()
);
if (bXDriveCreateIfNotExist)
{
if (!CreateXDrive(
oCloudDrive,
sXDriveLabel,
sPageBlobVhdName,
iXDriveSizeMB
)
)
{
return false;
}
}
bool bReDo = false;
if (!DoCloudDriveMount(oCloudDrive, sXDriveLabel, iXDriveMountCacheSizeMB, eAccessMode, ref bReDo))
{
return false;
}
if (bReDo && !DoCloudDriveMount(oCloudDrive, sXDriveLabel, iXDriveMountCacheSizeMB, AccessMode.ReadOnly, ref bReDo))
{
return false;
}
}
catch (NullReferenceException ex)
{
Trace.TraceError("NullReferenceException: Failed to mount X-Drive: {0}",
ex.Message
);
return false;
}
catch (Exception ex)
{
Trace.TraceError("Unexpected Exception: Failed to mount X-Drive: {0}",
ex.Message
);
return false;
}
return true;
}
Code: XDrives.CreateXDriveBlobContainer()
private bool CreateXDriveBlobContainer(
string sXDriveLabel,
string sBlobContainerName
) {
try
{
m_blobClient
.GetContainerReference(sBlobContainerName)
.CreateIfNotExist();
}
catch (StorageClientException ex)
{
Trace.TraceError(
"X-Drive {0}: StorageClientException: Failed to create Page Blob Container {1}: {2}",
sXDriveLabel,
sBlobContainerName,
ex.Message
);
return false;
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drive {0}: Unexpected Exception: Failed to create Page Blob Container {1}: {2}",
sXDriveLabel,
sBlobContainerName,
ex.Message
);
return false;
}
return true;
}
Code: XDrives.CreateXDrive()
private bool CreateXDrive(
CloudDrive oCloudDrive,
string sXDriveLabel,
string sPageBlobVhdName,
int iXDriveSizeMB
) {
bool bSuccess = false;
try
{
Trace.TraceInformation(
"X-Drive {0}: Creating drive and its associated page blob {1}",
sXDriveLabel,
sPageBlobVhdName
);
oCloudDrive.Create(iXDriveSizeMB);
bSuccess = true;
}
catch (CloudDriveException)
{
bSuccess = true;
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drive {0}: Unexpected Exception: Failed to create drive: {1}",
sXDriveLabel,
ex.Message
);
}
return bSuccess;
}
Code: XDrives.DoCloudDriveMount()
- If mount request for XDrive is readonly, then create a readonly snapshot: CloudDrive.Snapshot(), and create a new instance of a CloudDrive object with this snapshot.
- Mounting XDrive is performed and results evaluated as follows: CloudDrive.Mount()
- If access_mode none (default), then attempt write mount first with DriveMountOptions.None:
- If mount succeeds, then it exits with a success condition.
- If mount fails because CloudDriveException is thrown and its error message is ERROR_LEASED_LOCKED, then page blob VHD currently has a lease locked by another web role instance that has write access mode, then it will recommend that the next mount attempt should be readonly.
- If mount fails for any other condition, then there is not another mount attempt.
- If access_mode write, then attempt write mount first with DriveMountOptions.Force:
- The terms for how to proceed will be the same as if access_mode is none, with this option it will try to break the lease lock if it exists on page blob.
- If access_mode readonly, then there is only one attempt with created snapshot in mounting.
private bool DoCloudDriveMount(
CloudDrive oCloudDrive,
string sXDriveLabel,
int iXDriveMountCacheSizeMB,
AccessMode eAccessMode,
ref bool bReDo
)
{
if (AccessMode.ReadOnly == eAccessMode)
{
CloudDrive oCloudDriveSnapshot = null;
if (!CreateXDriveSnapshot(
oCloudDrive,
sXDriveLabel,
ref oCloudDriveSnapshot
))
{
return false;
}
oCloudDrive = oCloudDriveSnapshot;
}
if (!DoAttemptXDriveMount(
oCloudDrive,
sXDriveLabel,
iXDriveMountCacheSizeMB,
eAccessMode,
ref bReDo
)
) {
return false;
}
return true;
}
Code: XDrives.CreateXDriveSnapshot()
private bool CreateXDriveSnapshot(
CloudDrive oCloudDrive,
string sXDriveLabel,
ref CloudDrive oCloudDriveSnapshot
) {
try
{
Trace.TraceInformation(
"X-Drive {0}: Creating snapshot",
sXDriveLabel
);
Uri uriSnapshot = oCloudDrive.Snapshot();
oCloudDriveSnapshot = m_oCloudStorageAccount.CreateCloudDrive(
uriSnapshot.ToString()
);
}
catch (CloudDriveException ex)
{
Trace.TraceError(
"X-Drive {0}: CloudDriveException: Failed to create snapshot: {1}",
sXDriveLabel,
ex.Message
);
return false;
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drive {0}: Unexpected Exception: Failed to create snapshot: {1}",
sXDriveLabel,
ex.Message
);
return false;
}
return true;
}
Code: XDrives.DoAttemptXDriveMount()
private bool DoAttemptXDriveMount(
CloudDrive oCloudDrive,
string sXDriveLabel,
int iXDriveMountCacheSizeMB,
AccessMode eAccessMode,
ref bool bReDo
) {
try
{
string sAccessMode = string.Empty;
DriveMountOptions option = DriveMountOptions.None;
switch (eAccessMode)
{
case AccessMode.ReadOnly:
sAccessMode = "ReadOnly";
break;
case AccessMode.Write:
sAccessMode = "Write";
option = DriveMountOptions.Force;
break;
case AccessMode.None:
default:
sAccessMode = "Default";
break;
}
Trace.TraceInformation("X-Drive {0}: Mounting: Mode \"{1}\"",
sXDriveLabel,
sAccessMode
);
string sDriveLetter
= oCloudDrive.Mount(iXDriveMountCacheSizeMB, option);
Trace.TraceInformation(
"X-Drive {0}: Mounted: Letter \"{1}\": Mode \"{2}\", Uri \"{3}\"",
sXDriveLabel,
sDriveLetter,
sAccessMode,
oCloudDrive.Uri.ToString()
);
}
catch (CloudDriveException ex)
{
if (ex.Message.Equals("ERROR_LEASE_LOCKED"))
{
if (AccessMode.ReadOnly != eAccessMode)
{
Trace.TraceInformation("X-Drive {0}: Mount attempt had lease locked. Redo in mode ReadOnly.",
sXDriveLabel
);
bReDo = true;
return true;
}
return false;
}
else
{
Trace.TraceError(
"X-Drive {0}: CloudDriveException: Failed to mount: {1}",
sXDriveLabel,
ex.Message
);
return false;
}
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drive {0}: Unexpected Exception: Failed to mount: {1}",
sXDriveLabel,
ex.Message
);
return false;
}
return true;
}
Global.Application_Start()
After event handler RoleEntryPoint.OnStart() has completed mounting the XDrives, event handler Global.Application_Start() proceeds by calling
XDrives.SetXDrivesEnvironmentVariable():
- If there are not any XDrives configurations within ServiceConfiguration.cscfg, then it exits.
- Parses the XDrive configurations into a dictionary.
- Get all successfully mounted XDrives: CloudDrive.GetMountedDrives(). If there are none. then exit.
- Match XDrive configuration's requested path (blob container name + page blob VHD name) with one of the mounted XDrives.
- Create setting for environment variable X_DRIVES.
Code: XDrives.SetXDrivesEnvironmentVariable()
public void SetXDrivesEnvironmentVariable()
{
string sXDrivesConfig = ReadXDrivesConfig();
if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
{
return;
}
ICollection<IDictionary<string, string>> aXDriveConfigs = ParseXDriveConfigs(sXDrivesConfig);
if (null == aXDriveConfigs || 0 == aXDriveConfigs.Count)
{
Trace.TraceInformation("X-Drives: None are currently defined to be mounted.");
return;
}
IDictionary<String, Uri> listDrives = CloudDrive.GetMountedDrives();
if (0 == listDrives.Count)
{
Trace.TraceInformation("X-Drives: None are currently mounted");
return;
}
string[] aXDrives = new string[listDrives.Count];
int i = 0;
foreach (KeyValuePair<String, Uri> kvpDrive in listDrives)
{
string sXDriveLetter = XDrives.GetDriveLetter(kvpDrive.Key);
Uri uriXDrivePath = kvpDrive.Value;
string sXDriveLabel;
string sPageBlobContainer = String.Empty;
string sPageBlobVHD = String.Empty;
bool bReadOnly = false;
XDrives.GetPathPageBlobVHD(
uriXDrivePath,
ref sPageBlobContainer,
ref sPageBlobVHD,
ref bReadOnly
);
sXDriveLabel
= XDrives.GetXDriveLabel(
aXDriveConfigs,
sPageBlobContainer,
sPageBlobVHD
);
string sAccess = bReadOnly ? "r" : "w";
aXDrives[i] = String.Format("{0}=[{1},{2}]", sXDriveLabel, sXDriveLetter, sAccess);
i++;
}
if (0 < aXDrives.Length)
{
string sXDriveEnv = String.Join(";", aXDrives);
Trace.TraceInformation("{0} = {1}", XDRIVES_ENV_VAR, sXDriveEnv);
Environment.SetEnvironmentVariable(XDRIVES_ENV_VAR, sXDriveEnv);
}
}
Code: XDrives.GetPathPageBlobVHD()
public static void GetPathPageBlobVHD(
Uri uri,
ref string sPageBlobContainer,
ref string sPageBlobVHD,
ref bool bReadOnly
)
{
sPageBlobContainer = String.Empty;
sPageBlobVHD = String.Empty;
string sPath;
string sPathAndQuery = uri.PathAndQuery;
string sQuery = uri.Query;
if (null != sQuery && sQuery.Length > 0)
{
sPath = sPathAndQuery.Substring(0, sPathAndQuery.Length - sQuery.Length);
bReadOnly = sQuery.StartsWith("?snapshot");
}
else
{
sPath = sPathAndQuery;
bReadOnly = false;
}
char[] aPathDelims = { '/' };
sPath = sPath.TrimStart(aPathDelims);
if (sPath.StartsWith(DEV_STORAGE_ACCT_PREFIX))
{
sPath = sPath.Substring(DEV_STORAGE_ACCT_PREFIX.Length);
sPath = sPath.TrimStart(aPathDelims);
}
int iPageBlobContainerDelim = sPath.IndexOf('/');
sPageBlobContainer = sPath.Substring(0, iPageBlobContainerDelim);
sPageBlobVHD = sPath.Substring(iPageBlobContainerDelim + 1);
}
RoleEntryPoint.OnStop()
Finally, called by Windows Azure when the role instance is to be stopped,
XDrives.UnmountXDrives():
- If there are not any XDrives configurations within ServiceConfiguration.cscfg, then it exits.
- Get all successfully mounted XDrives: CloudDrive.GetMountedDrives(). If there are none. then exit.
- Loop through the page blob VHD path for each mounted XDrive, and create CloudDrive object: CloudStorageAccountCloudDriveExtensions.CreateCloudDrive()
- Unmount each: CloudDrive.Unmount()
public void UnmountXDrives()
{
try
{
string sXDrivesConfig = ReadXDrivesConfig();
if (null == sXDrivesConfig || 0 == sXDrivesConfig.Length)
{
return;
}
if (null == m_oCloudStorageAccount)
{
return;
}
IDictionary<String, Uri> listDrives = CloudDrive.GetMountedDrives();
if (0 == listDrives.Count)
{
return;
}
Trace.TraceInformation("Unmounting X-Drives");
foreach (KeyValuePair<String, Uri> kvpDrive in listDrives)
{
string sXDriveLetter = GetDriveLetter(kvpDrive.Key);
Uri uriXDrivePath = kvpDrive.Value;
try
{
CloudDrive myCloudDrive = m_oCloudStorageAccount.CreateCloudDrive(
uriXDrivePath.ToString()
);
myCloudDrive.Unmount();
Trace.TraceInformation(
"X-Drive {0}: Unmounted {1}",
sXDriveLetter,
uriXDrivePath.ToString()
);
}
catch (CloudDriveException ex)
{
Trace.TraceError(
"X-Drive {0}: CloudDriveException: Failed to unmount {1}: {2}",
sXDriveLetter,
uriXDrivePath.ToString(),
ex.Message
);
}
catch (Exception ex)
{
Trace.TraceError(
"X-Drive {0}: Unexpected Exception: Failed to unmount {1}: {2}",
sXDriveLetter,
uriXDrivePath.ToString(),
ex.Message
);
}
}
}
catch (Exception ex)
{
Trace.TraceError(
"Unexpected Exception: Failed to unmount X-Drives: {0): {1)",
ex.GetBaseException(),
ex.Message
);
}
}
Service Configuration Settings
To access XDrives functionality with XDrives.cs, there are three required configurations that need to be performed within trong>ServiceConfiguration.cscfg and
ServiceDefinition.csdef files:
- Define one LocalStorage for XDrives.
- Define which LocalStorage will be used by XDrives.
- Define configuration for each XDrive to be mounted.
Local Storage for XDrive Mount Read Cache
In a new or existing cloud service, make sure you have a LocalStorage defined in ServiceDefinition.csdef. This local storage will be used by the Windows Azure Drive API to cache virtual hard disks on the virtual machine that is running, enabling faster access times. For XDrives, provide only one reference to LocalStorage in order to define the read cache for all subsequently mounted drives associated with the role instance. For example, a LocalStorage was added with name MyXDrivesLocalCache with a size of 1000 MB.
Since there can be multiple LocalStorages added to LocalResources, class XDrives will need to know the name of which LocalStorage that was specifically created for XDrives' local cache. For this example, configuration setting XDrivesLocalCache will reference the name of the LocalStorage MyXDrivesLocalCache.
New Service Setting: "XDrives"
This next setting is used for defining how to configure all XDrives should be mounted. Its value contains a listing of all mounted XDrives to be associated with this web role instance. Here is an example of a single XDrive configuration, explanation will follow later in this article:
[xdrive_label=MyDriveA,blob_container=MyDrives,page_blob_vhd=MyDriveA.vhd,xdrive_sizeMB=40,mount_cache_sizeMB=10,create=Yes];
Example Modification of ServiceDefinition.csdef and ServiceConfiguration.cscfg
The following setting will modify >
ServiceDefinition.csdef as follows:
<ServiceDefinition >
<WebRole name="WebRole">
<ConfigurationSettings>
</ConfigurationSettings>
<LocalResources>
<LocalStorage name="MyXDrivesLocalCache"
cleanOnRoleRecycle="false"
sizeInMB="1000" />
</LocalResources>
</WebRole>
</ServiceDefinition>
The last two settings will modify
ServiceConfiguration.cscfg as follows:
<ConfigurationSettings>
<Setting name="XDrives" value="
[xdrive_label=MyDriveA,blob_container=MyDrives,page_blob_vhd=MyDriveA.vhd,xdrive_sizeMB=40,mount_cache_sizeMB=10,create=Yes];"/>
<Setting name="XDrivesLocalCache" value="MyXDrivesLocalCache" />
<ConfigurationSettings>
Configuring Windows Azure Drives
At this point, defining web role to handle mounting XDrives is complete. Next is need to understand how to configure XDrive mounting within value of setting
XDrives.
Configuration Structure
The value for setting
XDrives is semi-colon delimited string, and each value is a XDrive configuration request.
"[x_drive_config];[x_drive_config];[x_drive_config];..." Each
x_drive_config request is a bracked set of comma delimited configuration settings, and each setting is a key value pair.
[key=value,key=value,key=value,...]
Configuration Legend
This a legend of all the available keys and their expected values that can be used to define an XDrive configuration request.
- xdrive_label
- Required: Friendly Label to Identify XDrive configuration with XDrive's letter assigned after mounting.
- blob_container
- Required: Blob Container name.
- Values: 3 through 63 lower-case characters long. See Naming Containers, Blobs, and Metadata.
- page_blob_vhd
- Required: Page Blob name.
- Values: 1 to 1024 any combination of characters long. See Naming Containers, Blobs, and Metadata.
- xdrive_sizeMB
- Required if Creating XDrive:
- Values: Integer from 16 to 1000000 MB
- mount_cache_sizeMB
- Optional: Mount Read Cache Size.
- Values: Integer from 0 to xdrive_sizeMB
- Default: 0 - No read cache is used during mount.
- create
- Optional: Create XDrive if it does not exist.
- Values: Yes|No
- Default: No
- access_mode
- Optional: Forces XDrive to mount a specific access mode versus getting available access mode.
- Values:
- none - Assign currently available access. If first mount, then assign XDrive with write access mode, else read-only.
- write - Force lease lock to break of a another XDrive mount having write access mode, and assign this mount with write access mode. Note: Not functional at this time. See Current Bugs section below.
- readonly - Assign this mount with read-only access mode, regardless if mount is available with write access mode.
- Default: none
Configuration Examples
To illustrate how source
XDrives.cs services XDrives, it will be presented by an assortment of XDrive configurations.
- [xdrive_label=MyDriveA,blob_container=mydrives,page_blob_vhd=MyDriveA.vhd]
- XDrive expects to mount upon an existing page blob path mydrives/MyDriveA.vhd, and it will succeed mounting if it exists.
- By not providing mount_cache_sizeMB value, thereby set mount for unbuffered access to the drive.
- If this page blob exists and is not mounted by another role instance, then the XDrive will have write access, else read-only.
- [xdrive_label=MyDriveA,blob_container=mydrives,page_blob_vhd=MyDriveA.vhd,xdrive_sizeMB=10,create=Yes]
- XDrive expects to mount upon a page blob path mydrives/MyDriveA.vhd, and if it does not exist then it will be created before mounting.
- When requesting to create an page blob to be associated with XDrive, key xdrive_sizeMB is required.
- The size of the XDrive to create is 10 MBs. The minimum size permitted is 16 MB.
- If this page blob exists and is not mounted by another role instance, then the XDrive will have write access, else read-only.
- If this page blob does not exist, then the XDrive will create this page blob and have write access.
- [xdrive_label=MyDriveA,blob_container=mydrives,page_blob_vhd=MyDriveA.vhd,mount_cache_sizeMB=10,readonly=Yes]
- XDrive expects to mount upon an existing page blob path mydrives/MyDriveA.vhd, and it will succeed mounting if it exists.
- If this page blob exists, then the XDrive will have read-only access, regardless if it is mounted by another role instance or not.
- The size of the read cache for this mounted XDrive is 10 MBs.
Code: XDrives.ParseXDriveConfigs()
This following code is used for parsing the XDrives setting from ServiceConfiguration.cscfg.
public ICollection<IDictionary<string, string>> ParseXDriveConfigs(string sXDrivesConfigs)
{
ICollection<IDictionary<string, string>> aXDrivesConfigs = new Collection<IDictionary<string, string>>();
char[] aCloudDrivesConfigsDelims = { ';' };
string[] aXDrivesConfigsSplit = sXDrivesConfigs.Split(aCloudDrivesConfigsDelims);
foreach (string sXDriveConfigRaw in aXDrivesConfigsSplit)
{
string sXDriveConfigTrim = " []";
char[] aXDriveConfigTrim = sXDriveConfigTrim.ToCharArray();
string sXDriveConfig = sXDriveConfigRaw.Trim(aXDriveConfigTrim);
if (0 == sXDriveConfig.Length)
{
continue;
}
char[] aXDriveConfigKeyValuesDelims = { ',' };
string[] aXDriveConfigKeyValues = sXDriveConfig.Trim().Split(aXDriveConfigKeyValuesDelims);
IDictionary<string, string> dicXDriveConfigKeyValue = new Dictionary<string, string>();
foreach (string sXDriveConfigKeyValue in aXDriveConfigKeyValues)
{
string sXDriveConfigKeyValueDelimChars = "=";
char[] aXDriveConfigKeyValueDelims = sXDriveConfigKeyValueDelimChars.ToCharArray();
string[] aXDriveConfigKeyValue = sXDriveConfigKeyValue.Trim().Split(aXDriveConfigKeyValueDelims);
string sXDriveConfigKey = aXDriveConfigKeyValue[0].Trim();
string sXDriveConfigValue = aXDriveConfigKeyValue[1].Trim();
if (0 == sXDriveConfigKey.Length)
{
Trace.TraceWarning("XDrive configuration has an undefined key",
sXDriveConfigKey
);
continue;
}
if (dicXDriveConfigKeyValue.ContainsKey(sXDriveConfigKey))
{
Trace.TraceWarning("XDrive configuration has a duplicate key {0}",
sXDriveConfigKey
);
continue;
}
if (0 == sXDriveConfigValue.Length)
{
Trace.TraceWarning("XDrive configuration has undefined value for key {0}",
sXDriveConfigKey
);
continue;
}
dicXDriveConfigKeyValue.Add(sXDriveConfigKey, sXDriveConfigValue);
}
aXDrivesConfigs.Add(dicXDriveConfigKeyValue);
}
return 0 == aXDrivesConfigs.Count ? null : aXDrivesConfigs;
}
Packaging a Web Application to access Windows Azure Drives
For this XDrive access solution to work, the deployment will need to be packaged with the following files:
- WebRole.dll - Build using VS 2008 solution WindowsAzure_WebRole_XDrives, required for mounting and unmounting XDrives.
- ServiceDefinition.csdef - Defining LocalStorage to be used by XDrives.
- ServiceConfiguration.cscfg - Setting configuration for all XDrives to be mounted by WebRole.dll
- Global.asax - Global.Application_Start() event handler is specified within this file, which it is called to sets environment variable X_DRIVES within this VM, the XDrives status link for the web application within this deployment before it started.
- Web Application - Download provides two web applications (PHP and C#) that demonstrates access to mounted XDrives based upon provided value within environment variable X_DRIVES.
Testing access to Windows Azure Drives
Following the guidelines set for packaging this solution for mounting and accessing XDrives, this section will present an example run of two web application deployments using the same service configuration settings for mounting XDrives.
Sample Web Applications accessing XDrives
As stated, within download there two web applications (C# and PHP) for validating that XDrives are being mounted on start of Web Role DLL Both web applications do the following:
- Read X_DRIVES environment variable that should have been set by Web Role DLL.
- Parse it into individual XDrive mounting information
- If a provided XDrive is declared as writable, then create and write files within root directory of provided drive letter.
- Read contents of all files within root directory of provided drive letter.
Sample Configuration of XDrives
Here we will attempt to mount XDrives using the same configuration setting in two different web application deployments. The example setting
XDrives within
ServiceConfiguration.cscfg is to mount two XDrives labeled
MyDriveA and
MyDriveB:
[xdrive_label=MyDriveA,blob_container=mydrives,page_blob_vhd=MyDriveA.vhd,xdrive_sizeMB=40,create=Yes];
[xdrive_label=MyDriveB,blob_container=mydrives,page_blob_vhd=MyDriveB.vhd,xdrive_sizeMB=40,mount_cache_sizeMB=20,create=Yes];
First Deployment
Packaged web application is deployed to run within Development Fabric using the aforementioned XDrives configuration value to mount two XDrives. Viewing the progress of the first deployment within
Development Fabric UI, the deployment's log console shows that during
RoleEntryPoint.OnStart() the mounting for both XDrives were successful:
[runtime] Role entrypoint . CALLING OnStart()
[WaWebHost.exe] RoleEntryPoint.OnStart()
[WaWebHost.exe] Get XDrives Configuration
[WaWebHost.exe] Mounting XDrives
[WaWebHost.exe] XDrive MyDriveA: Reading configuration
[WaWebHost.exe] XDrive MyDriveA: Mounted: Letter "a:": Mode "Default", Uri "http://127.0.0.1:10000/devstoreaccount1/myxdrives/MyDriveA.vhd"
[WaWebHost.exe] XDrive MyDriveB: Reading configuration
[WaWebHost.exe] XDrive MyDriveB: Mounted: Letter "b:": Mode "Default", Uri "http://127.0.0.1:10000/devstoreaccount1/myxdrives/MyDriveB.vhd"
[runtime] Role entrypoint . COMPLETED OnStart()
[runtime] Role entrypoint . CALLING Run()
[WaWebHost.exe] X_DRIVES = MyDriveA=[a,w];MyDriveB=[b,w]
[fabric] Role state Started
And
Global.Application_Start() posts the following environment variable setting within the VM of the first deployment:
X_DRIVES = MyDriveA=[a,w];MyDriveB=[b,w] Explanation of what occurred is that neither of requested page blob VHD paths existed (
mydrives/MyDriveA.vhd and
mydrives/MyDriveB.vhd), then both were created and mounted with writable access.
- MyDriveA - Drive Letter: a, Access Mode: Writable (w)
- MyDriveB - Drive Letter: b, Access Mode: Writable (w)
For first deployment, this is the default page results after creating, writing, and reading files for both XDrives:
Second Deployment
While, the first deployment is still running, the same packaged web application is deployed again to run within Development Fabric using the same XDrives configuration value to mount two XDrives. Viewing the progress of the first deployment within Development Fabric UI, the deployment's log console shows that during
RoleEntryPoint.OnStart() the mounting for both XDrives were successful, but the mounting results are different from the first deployment:
[runtime] Role entrypoint . CALLING OnStart()
[WaWebHost.exe] Get XDrives Configuration
[WaWebHost.exe] Mounting XDrives
[WaWebHost.exe] XDrive MyDriveA: Reading configuration
[WaWebHost.exe] XDrive MyDriveA: Mount attempt had lease locked. Redo in mode ReadOnly.
[WaWebHost.exe] XDrive MyDriveA: Creating snapshot
[WaWebHost.exe] XDrive MyDriveA: Mounting: Mode "ReadOnly"
[WaWebHost.exe] XDrive MyDriveA: Mounted: Letter "e:": Mode "ReadOnly", Uri "http://127.0.0.1:10000/devstoreaccount1/myxdrives/MyDriveA.vhd?snapshot=2010-04-17T03:33:08.8070000Z"
[WaWebHost.exe] XDrive MyDriveB: Reading configuration
[WaWebHost.exe] XDrive MyDriveB: Mount attempt had lease locked. Redo in mode ReadOnly.
[WaWebHost.exe] XDrive MyDriveB: Creating snapshot
[WaWebHost.exe] XDrive MyDriveB: Mounting: Mode "ReadOnly"
[WaWebHost.exe] XDrive MyDriveB: Mounted: Letter "f:": Mode "ReadOnly", Uri "http://127.0.0.1:10000/devstoreaccount1/myxdrives/MyDriveB.vhd?snapshot=2010-04-17T03:33:11.9620000Z"
[runtime] Role entrypoint . COMPLETED OnStart()
[runtime] Role entrypoint . CALLING Run()
[WaWebHost.exe] X_DRIVES = MyDriveA=[e,r];MyDriveB=[f,r]
[fabric] Role state Started
And
Global.Application_Start() posts the following environment variable setting within the VM of the second deployment, which has a different setting from the first deployment:
X_DRIVES = MyDriveA=[f,r];MyDriveB=[g,r] Explanation of what has occurred is that both of the requested page blob VHD paths existed (
mydrives/MyDriveA.vhd and
mydrives/MyDriveB.vhd) and were both already mounted with leases locked (write access) by the previous and still running deployment, thereby this deployment will be provide with read-only access to both XDrives by creating snapshots of page blob VHD paths and then mounting them.
- MyDriveA - Drive Letter: f, Access Mode: Readonly (r)
- MyDriveB - Drive Letter: g, Access Mode: Readonly (r)
For second deployment while first deployment is still running, this is the default page results after only reading files for both XDrives:
Final Result of Two Deployments
In the end, there are two concurrent deployments:
- First deployment can write to page blob VHDs represented by MyDriveA and MyDriveB.
- Second deployment can read a snapshot of what First deployment wrote within page blob VHDs represented by MyDriveA and MyDriveB up to the point of its read-only mounting. Afterwards, because it is a snapshot, no matter what the First deployment writes within these page blob VHDs, the Second deployment will not see these changes unless it performs another snapshot.
If the First deployment had ended and it successfully unmounted the XDrives it had a lease to write to page blob VHDs, then the lease lock would be broken and these page blobs would be available to be modified by the next mount request. If this action occurred before the Second deployment had started, then the Second would have write access to these page blobs instead of a snapshot.
Points of Interest
Initial Posting
Within
Windows Azure Forum in March 2010, I started a discussion that pertains to this article:
Proposal: Defining Multiple X-Drives within ServiceConfiguration.cscfg.
Another Approach
Maarten Balliauw is Technical Consultant at
RealDolmen in Belgium. We have been working remotely together on PHP solutions for Windows Azure platform. Unbeknownst to either of us, we were writting articles that addresses a similar solution. To his credit, this is his blog article:
Configuring and using Windows Azure Drive.
Windows Azure Command-line Tools for PHP Developers
The approach presented in this article in providing access to Windows Azure Drives was adopted within the following open source project hosted upon
CodePlex:
Windows Azure Command-line Tools for PHP Developers. More about this tool will be presented in Part 2 of this article.
Development Fabric Limitation
Only within Development fabric,
DriveMountOptions.Force is currently not implemented to allow a mounting of an XDrive to break a lease lock of another mounting that has write access. In
XDrives.cs, method
XDrives.DoAttemptXDriveMount() will attempt to break lease lock with DriveMountOptions.Force when calling
CloudDrive.Mount(), if provided XDrive's configuration specifically requests
access_mode to be
write.
Problems Not Unmounting XDrives Properly
At the time of writing this article, I have experienced that if XDrives were not properly unmounted at the end of a deployment, this can cause problems with the next deployment trying to mount the same page blob VHDs; like hanging indefinitely when calling
CloudDrive.Mount(). As would be expected, this problem occurred a lot during the development of this solution, and my resolution was to restart both the Development Fabric and especially the Development Storage. And to my suprise during development, this problem would also occur when removing deployments manually within Development Fabric UI. If I had right-clicked a running deployment and selected
Remove, then the next deployment hangs. The proper action is to first select
Suspend, wait till it is finished suspending and deployment's dot goes
red, and then select
Remove. Only by selecting
Suspend is the callback
RoleEntryPoint.OnStop() initiated, thereby calling this solution's method
XDrives.UnmountCloudDrives() within the
WebRole.dll.
Conclusion
Windows Azure Drives is a great tool for the Windows Azure Platform in building its story in providing Infrastructure as a Service (IaaS) solutions. However, to truly understand it is to read
Windows Azure Drive Whitepaper, and read the threads within
Windows Azure Forum and blog of
Maarten Balliauw.
Recognition
Windows Azure Forum: There were several Microsoft moderators and contributors of this forum who provided answers to my many questions pertaining to Windows Azure Drives:
Allen Chen, Andrew Edwards, Yi-Lun Luo, and
Neil Mackenzie.