Azure Drive

If you have not yet read the excellent Azure Drive whitepaper by Brad Calder and Andrew Edwards of the Azure team I suggest you download it immediately and read it before continuing with this post. Maarten Balliauw also has a good post on Azure Drives.

Azure Drive is a feature of Azure providing access to data contained in an an NTFS-formatted virtual hard disk (VHD) persisted as a page blob in Azure Storage. A single Azure instance can mount a page blob for read/write access as an Azure Drive. However, multiple Azure instances can mount a snapshot of a page blob for read-only access as an Azure Drive. The Azure Storage blob lease facility is used to prevent more than one instance at a time mounting the page blob as an Azure Drive.

An appropriately created and formatted VHD can be uploaded from a remote, non Azure, system into a page blob from where it can be mounted as an Azure Drive by an instance of an Azure Service. It is not possible to mount an Azure Drive in an application not resident in the Azure cloud or development fabric. Similarly, the page blob can be downloaded and attached as a VHD in a remote, non Azure, system.

The Azure SDK provides three classes in the Microsoft.WindowsAzure.StorageClient namespace to support Azure Drives:

CloudDrive is a small class providing the core Azure Drive functionality. CloudDriveException allows Azure Drive errors to be caught. CloudStorageAccountCloudDriveExtensions, similar to the CloudStorageAccountStorageClientExtensions class, provides an extension method to CloudStorageAccount allowing a CloudDrive object to be constructed.

Note that Azure Drive requires that the osVersion attribute in the Service Configuration file be set to WA-GUEST-OS-1.1_201001-01 or a later version. For example:

<ServiceConfiguration serviceName="CloudDriveExample"
    xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
    osVersion="WA-GUEST-OS-1.1_201001-01">

VHD

The VHD for an Azure Drive must be a fixed hard disk image formatted as a single NTFS volume. It must be between 16MB and 1TB in size. A VHD is a single file comprising a data portion followed by a 512 byte footer. For example, a nominally 16MB VHD occupies 0x1000200 bytes comprising 0x1000000 bytes of data and 0x200 footer bytes. When uploading a VHD it is consequently important to remember to upload the footer. Furthermore, since pages of a page blob are initialized to 0 it is not necessary to upload pages in which all the bytes are 0. This could save a significant amount of time when uploading a large VHD.

The Disk Management component of the Windows Server Manager can be used to create and format a VHD by performing the following steps:

  • Right click on Disk Management and select Create VHD
  • Specify a fixed size VHD and provide a location and the size in MB
  • Right click on the disk icon and select Initialize Disk
  • Specify MBR (Master Boot Record)
  • Right click on the unallocated space and select New Simple Volume
  • Follow the Wizard choosing the defaults – specifically including the quick format with NTFS

The VHD is now created and accessible as a new drive using Windows Explorer. It can be used just like any other drive. The Detach VHD entry on the Disk Management right-click menu can be used to detach the VHD making it no longer accessible as a drive. This does not delete the VHD file. Similarly, the Attach VHD menu item can be used to once again make the VHD accessible as a file system.

Once created and detached, this VHD can be uploaded as a page blob to Azure Storage just as if it were an ordinary file. The following sample code uploads a VHD, specified by path, in 1MB chunks and then separately (for demonstration purposes) uploads the 512 byte VHD footer.

static public void UploadCloudDrive(CloudBlobClient cloudBlobClient,
    String containerName, String blobName, string path)
{
    CloudBlobContainer cloudBlobContainer = cloudBlobClient.GetContainerReference(containerName);
    CloudPageBlob cloudPageBlob = cloudBlobContainer.GetPageBlobReference(blobName);
    cloudPageBlob.Properties.ContentType = "binary/octet-stream";
    const Int32 uploadSize = 0x100000; // 1MB
    const Int32 numberFooterBytes = 0x200; //512B
    Int32 countBytesUploaded = 0;
    Int32 countBytesRead = 0;
    using (FileStream fileStream = new FileStream(path, FileMode.Open))
    {
        Int32 blobSize = (Int32)fileStream.Length;
        Int32 offset = 0;
        cloudPageBlob.Create(blobSize); Int32 numberIterations = blobSize / uploadSize;
        for (Int32 i = 0; i &lt; numberIterations; i++)
        {
            Byte[] bytes = new Byte[uploadSize];
            countBytesRead += fileStream.Read(bytes, 0, uploadSize);
            using (MemoryStream memoryStream = new MemoryStream(bytes))
            {
                cloudPageBlob.WritePages(memoryStream, offset);
                offset += uploadSize;
                countBytesUploaded += uploadSize;
            }
        }

        Byte[] footerBytes = new Byte[numberFooterBytes];
        countBytesRead += fileStream.Read(footerBytes, 0, numberFooterBytes);
        using (MemoryStream memoryStream = new MemoryStream(footerBytes))
        {
            cloudPageBlob.WritePages(memoryStream, offset);
            offset += numberFooterBytes;
            countBytesUploaded += numberFooterBytes;
        }
    }
}

CloudDrive

CloudDrive is declared:

public class CloudDrive {
    // Constructors
    public CloudDrive(Uri uri, StorageCredentials credentials);

    // Properties
    public StorageCredentials Credentials { get; }
    public String LocalPath { get; }
    public Uri Uri { get; }

    // Methods
    public void CopyTo(Uri destination);
    public void Create(Int32 sizeInMB);
    public void Delete();
    public static IDictionary<String, Uri> GetMountedDrives();
    public static void InitializeCache( String cachePath, Int32 totalCacheSize);
    public String Mount(Int32 cacheSize, DriveMountOptions options);
    public Uri Snapshot();
    public void Unmount();
}

A CloudDrive object can be created using either the constructor or the CreateCloudDrive extension method to CloudStorageAccount. For example, the following creates a CloudDrive object for the VHD contained in the page blob resource identified by the URI in cloudDriveUri:

CloudStorageAccount cloudStorageAccount =
  CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
CloudDrive cloudDrive = new CloudDrive(cloudDriveUri, cloudStorageAccount.Credentials);

or

CloudStorageAccount cloudStorageAccount =
  CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
CloudDrive cloudDrive = cloudStorageAccount.CreateCloudDrive(cloudDriveUri.AbsoluteUri);

These assume that the CloudStorageAccount.SetConfigurationSettingPublisher() as has been invoked previously as discussed in this post. Note that these only create an in-memory representation of the Azure Drive which still needs to be mounted before it can be used.

Create() physically creates a VHD of the specified size and stores it as page blob. Note that Microsoft charges only for initialized pages of a page blob. Since most of the pages in this empty VHD are unused there should only be minimal charges for this newly created page blob even when the VHD is nominally of a large size. The Delete() method can be used to delete the VHD from Azure Storage. Snapshot() makes a snapshot of the VHD page blob containing the VHD while CopyTo() makes a physical copy of it at the specified URL.

Just as a VHD must be attached to a server to expose its contents as a file system so a VHD page blob must be mounted on an Azure instance to make its contents accessible. A VHD page blob can be mounted on only one instance at a time. However, a VHD snapshot can be mounted as a read-only drive to an unlimited number of instances simultaneously. A snapshot therefore provides a convenient way to share large amounts of information among several instances. For example, one instance could have write access to a VHD page blob while other instances have read-only access to snapshots of it – including snapshots made periodically to ensure the other instances have up-to-date data.

Before a VHD page blob can be mounted it is necessary to allocate some cache space in the local storage of the instance. This is necessary even if caching is not going to be used. This caching is a read cache. Local storage must be defined in the Azure Service Definition file as explained in this post and should be configured with cleanOnRoleRecycle set to false. The following extract from a Service Definition file shows the definition of a 50MB local storage named CloudDrives.

<LocalResources>
      <LocalStorage name="CloudDrives" cleanOnRoleRecycle="false" sizeInMB="50">
</LocalResources>

InitializeCache() must be invoked to initialize the cache with a specific size and location. The following shows the Azure Drive cache being initialized to the maximum size of the local storage named CloudDrives:

public static void InitializeCache()
{
    LocalResource localCache = RoleEnvironment.GetLocalResource("CloudDrives");
    Char[] backSlash = { '\' };
    String localCachePath = localCache.RootPath.TrimEnd(backSlash);
    CloudDrive.InitializeCache(localCachePath, localCache.MaximumSizeInMegabytes);
}

Note that there is a tweak in which trailing back slashes are removed from the path to the cache. This is to workaround a bug described by Andrew Edwards in this Azure Forum thread.

UPDATE 3/25/2010: Note that on this Azure Forum thread Andrew Edwards points out that InitializeCache() can be called multiple times pointing to different local storage resources. Subsequent calls to Mount() appear to be associated with the read cache initialized by the most recent call to InitializeCache(). Edwards suggests there is not really much reason to do this.

An instance mounts a writeable Azure Drive by invoking Mount() on a VHD page blob. The Azure Storage Service uses the page blob leasing functionality to guarantee exclusive access to the VHD page blob. An instance mounts a read-only Azure Drive by invoking mount() on a VHD snapshot. Since it is read-only it is possible for multiple instances to mount the VHD snapshot simultaneously. An instance invokes the Unmount() method to release the Azure Drive and for VHD page blobs allow other instances to mount the blob for write access.

The cacheSize parameter to Mount() specifies how much of the cache is dedicated to this Azure Drive. The cacheSize should be set to 0 if caching is not desired for the drive. Different Azure Drives mounted on the same instance can specify different cache sizes and care must be taken that the total cache size allocated for the drives does not exceed the amount of cache available in local storage.

The options parameter takes an DriveMountOptions flag enumeration that can be used to force the mounting of a drive – for example, when an instance has crashed while holding the lease to a VHD page blob – or to fix the file system. Mount() returns the drive letter, or LocalPath, to the Azure Drive – for example, “d:” – which can be used to access any path on the drive.

The following example shows an Azure Drive being mounted from a VHD page blob specified by cloudDriveUri, before being used and then unmounted:

public void WriteToDrive(Uri cloudDriveUri)
{
    CloudStorageAccount cloudStorageAccount =
    CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
    cloudDrive = cloudStorageAccount.CreateCloudDrive(cloudDriveUri.AbsoluteUri);
    String driveLetter = cloudDrive.Mount( cacheSizeInMegabytes, DriveMountOptions.None);
    String path = String.Format("{0}\\Pippo.txt", driveLetter);
    FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate);
    StreamWriter streamWriter = new StreamWriter( fileStream);
    streamWriter.Write( "that you have but slumbered here");
    streamWriter.Close();
    cloudDrive.Unmount();
}

GetMountedDrives() provides access to a list of drive letters for all Azure Drives mounted in the instance. It is used as follows:

public static void EnumerateDrives()
{
    IDictionary<String,Uri> listDrives = CloudDrive.GetMountedDrives();
    foreach (KeyValuePair<String,Uri> drive in listDrives)
    {
        String driveInformation = String.Format( "drive: {0} - uri: {1}",
          drive.Key, drive.Value.AbsoluteUri);
        Trace.WriteLine(driveInformation, "Information");
    }
}

UPDATE 3/25/2010: Note that the DriveInfo class can be used to retrieve information about a mounted Azure Drive. The entry point to this information is the static method DriveInfo.GetDrives() which returns an array of DriveInfo objects representing all mounted drives on the instance.

Development Environment

The Development Environment simulates Azure Drives in a manner that differs from their implementation in the cloud. Furthermore, the Development Storage simulation is unaware of the Azure Drive simulation with the consequence that the standard blob manipulation methods in the Storage Client API do not work with the VHD page blobs and VHD snapshots used by the Azure Drive simulation. Instead, the blob management methods in the CloudDrive class must be used.Azure Drives are not mounted as attached VHDs retrieved from VHD page blobs but through the use of subst against a folder, e.g. drivename, in a subfolder, e.g. drivecontainername, of a well-known directory:

%LOCALAPPDATA%\dftmp\wadd\devstoreaccount1

The full path to the folder that is the subst representation of the Azure Drive is:

%LOCALAPPDATA%\dftmp\wadd\devstoreaccount1\drivecontainername\drivename

Invoking CloudDrive.Create() or CloudDrive.Snapshot() causes a folder with the name of the VHD page blob or VHD snapshot to be created in this directory. CloudDrive.Delete() can be used to delete the VHD page blob or VHD snapshot. Note that, although visible in the Azure fabric, VHD page blobs and VHD snapshots do not appear in blob listings in Development Storage because they are not stored as blobs. Consequently, a VHD file uploaded to Development Storage cannot be mounted as an Azure Drive. The workaround is to create a folder in the well-known directory and copy the file system of the VHD into the folder.Note that subst can be invoked in a command window to view the list of currently mounted Azure Drives.Azure Drives are mounted and unmounted in the Development Environment just as they are in the cloud. UPDATE 4/1/2010: note that the DriveMountOptions.Force is not implemented in the Development Environment.It is important to remember that Azure Drives are available only inside the Azure Fabric – cloud or development – and that they are not mountable in an ordinary Windows application.UPDATE 4/1/2010: A VHD can be attached to an empty folder in the well-known directory and mounted exactly as it would be in the cloud. The Disk Management component of the Windows Server Manager is used to attach a VHD to an empty folder, e.g. drivename, in a subdirectory, e.g. drivecontainername, of the well known directory so that the VHD can be mounted precisely as it would be in the cloud:

  • Right click on Disk Management and select Attach VHD
  • Specify the location of the VHD
  • Right click on the disk icon and select Change Drive Letter and Paths
  • Click on Add and browse to an empty folder in the well-known directory”:
    %LOCALAPPDATA%\dftmp\wadd\devstoreaccount\drivecontainername\drivename
  • Click OK to confirm

The Azure Drive API can then be used to mount the Azure Drive as if it were backed by a VHD page blob named, drivename, located in a container named drivecontainername. There is no need to invoke the Create() method. Note that there is no entry in Development Storage for this blob.UPDATE 2/27/2010
Added paragraph on requirement to use at least the Windows Azure Guest OS 1.1 (Release 201001-01) Azure guest operating system.

UPDATE 3/8/2010
Clarified section on the Development Environment.

UPDATE 3/25/2010
Added a comment on InitializeCache() as well as information on the use of the DriveInfo class to retrieve information about mounted drives.

UPDATE 4/1/2010
Clarified section on Development Environment specifically adding material on attaching to a VHD.

UPDATE 1/15/2011
Fixed Spaces – WordPress formatting glitches

UPDATE 4/19/2011
Syntax-hilited code.

About Neil Mackenzie

Cloud Solutions Architect. Microsoft
This entry was posted in Windows Azure. Bookmark the permalink.

4 Responses to Azure Drive

  1. Brent says:

    So Neil, one thing I’ve been pondering is possible use cases for this. The fact that it can only be mounted for read/write by a single instance seems to limit its usefulness as a virtualized cloud based drive. If you have need for more static forms of storage that can be easily replicated across multiple instances, then it seems to make more sense. But I’m not sure how common that dynamic is. I personally haven’t encountered much of a need for it.

  2. Neil says:

    Brent -I haven’t thought much about use cases but my first step is to look at what people say about Amazon Elastic Block Storage (EBS) which Azure Drives are essentially a clone. The primary use case with EBS appears to be in mounting snapshots across multiple instances. There are a number of good posts on EBS and I think you can pretty much swap the names:http://www.allthingsdistributed.com/2008/08/amazon_ebs_elastic_block_store.htmlhttp://www.allthingsdistributed.com/2008/04/persistent_storage_for_amazon.htmlhttp://blog.rightscale.com/2008/08/20/amazon-ebs-explained/http://blog.rightscale.com/2008/04/13/amazon-takes-ec2-to-the-next-level-with-persistent-storage-volumes/I kind of wonder if Azure Drive is not a first step towards supporting IaaS with bootable VHDs – but that is pure speculation.

  3. Brent says:

    It may be speculation but its a good guess. I can’t help but also wonder if it isn’t an extension of what MSFT is already doing with Azure internally. It would make sense to have VHD snapshots that are loaded and configured for use by the fabric controllers. Either way, they continue to blur the line between SaaS and IaaS. :)

  4. Pingback: Azure FAQ: Can I write to the file system on Windows Azure? « Coding Out Loud

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s