Premier contact avec les différents types de stockage dans Azure

19. June 2009 by Leonard.LABAT

Vous avez peut être déjà effectués vos premiers pas dans le cloud en créant votre premier site ou service pour Windows Azure. Si ce n’est pas le cas, je vous invite à suivre ce billet de Julien Dollon :

http://blogs.dotnet-france.com/juliend/post/Azure-Introduction-Azure-Services-Platform-(Windows-Azure-NET-Services-Live-Services-SQL-Services).aspx

Dans ce billet, nous allons nous pencher sur l’utilisation des trois types de storage proposés par Azure, à savoir :

  • Les Queues;
  • Les Blobs;
  • Les Tables.

Pour vous présenter tout ça, je vais utiliser un petit exemple tiré du monde de la restauration. Imaginez que vous êtes au restaurant , le Web Role représente le serveur qui vient prendre votre commande. Bien évidemment, le serveur ne va préparer votre commande instantanément sous vos yeux, il va la transmettre au chef (qui est représenté par le Worker Role) qui la traitera dès qu’il en aura fini avec les commandes prises avant la votre.

La communication entre le Web Role (le serveur) et le Worker Role (le chef) se fait via la Queue. Ainsi, comme il s’agit d’une file, les commandes passées par les clients (d’une manière générale, comprenez des tâches), sont traitées par ordre chronologique par le chef.

Enfin, une fois que le chef a fini d’œuvrer, il renvoi le plat en salle. Les différents plats seront stockés dans les Blobs.

On pourra tenir à jour la liste des plats disponibles. Pour se faire, je vais utiliser les Tables.

Maintenant que vous avez pris connaissance de l’exemple que je vais utiliser, on va pouvoir attaquer. Commencez par créer un projet Web & Worker.

 I. Les Queues

Dans cette première partie, nous allons travailler sur la prise de commande (du côté du projet web) et la réception de la commande (du côté du worker).

Commencez par ajouter une page web.

Complétez le formulaire en lui ajoutant quelques petits contrôles :

  • Un champs pour la saisie du nom du plat;
  • Un bouton pour valider la commande;
  • Un label qui donne l'état du serveur (celui du restaurant hein).

<form id="form1" runat="server">
<div>
    <asp:Label ID="lOrderName" runat="server" Text="Order Name : " /><asp:TextBox ID="tbOrderName" runat="server" /><br />
    <asp:Button ID="bTakeOrder" runat="server" Text="OK"
        onclick="bTakeOrder_Click" /><br />
    <asp:Label ID="lWaiterStatus" runat="server" />
</div>
</form>

Avant de pouvoir utiliser la Queue, il faut configurer un peu vos rôles pour qu’ils puissent se connecter au service de stockage.

Ouvrez le fichier ServiceDefinition.csdef et renseignez le comme ci-dessous.

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole" enableNativeCodeExecution="false">
    <InputEndpoints>
      <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
    </InputEndpoints>
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WebRole>
  <WorkerRole name="WorkerRole" enableNativeCodeExecution="false">
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WorkerRole>
</ServiceDefinition>

Occupez vous ensuite du fichier ServiceConfiguration.cscfg. Remarquez deux choses :

  • - Ici, je travaille en local. Vous le savez surement si vous avez déjà eu une première expérience avec Azure, vous disposez en local des outils Development Fabric et Development Storage qui simulent le fonctionnement du cloud. C’est sur ce deuxième outil que vous pouvez récupérer l’adresse et le port du Queue Storage.
  • Vous pouvez utiliser si vous le voulez des configurations différentes pour les deux rôles.
<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Maintenant, ajoutez à votre solution le projet StorageClient présent dans les exemples du SDK d’Azure et référencez le dans le projet Web et le Worker.

Ajoutez ensuite dans le code behind de la page Order un champs de type StorageAccountInfo. Ce type est défini dans le projet que vous venez de récupérer. Utilisez ensuite la méthode GetDefaultQueueStorageAccountFromConfiguration() qui, comme son nom l’indique, va venir lire la configuration que vous avez complétée.

StorageAccountInfo storageAccountInfoQueue;

protected void Page_Load(object sender, EventArgs e)
{
    storageAccountInfoQueue = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
}

Chargeons nous ensuite de compléter la méthode bTakeOrder_Click. Il faut commencer par récupérer une instance de la classe QueueStorage en utilisant les infos de configuration que nous avons au préalablement récupérés. La QueueStorage est composée de différents MessageQueue identifiable par leur nom. Ainsi, récupérons un de ces MessageQueue que nous appelerons « orders ». Si il n’existe pas (méthode DoesQueueExists()), il faut le créer (méthode CreateQueue()).

Ensuite, nous pouvons simplement ajouter des messages à la Queue grâce à la méthode PutMessage. Pour finir, on met à jour le label du status du serveur.

protected void bTakeOrder_Click(object sender, EventArgs e)
{
    QueueStorage queueStorage = QueueStorage.Create(storageAccountInfoQueue);

    MessageQueue messageQueue = queueStorage.GetQueue("orders");

    if (!messageQueue.DoesQueueExist())
        messageQueue.CreateQueue();

    messageQueue.PutMessage(new Message(tbOrderName.Text));

    lWaiterStatus.Text = "Order sent to chef";
}

Avant de s’attaquer à l’écriture du Worker, on va vérifier que la prise de commande fonctionne correctement. Lancez l’application et passez quelques commandes de test.

 

Nous pouvons voir le contenu de la Queue en utilisant un petit utilitaire disponible sur CodePlex : Azure Storage Explorer (http://azurestorageexplorer.codeplex.com/)

 

Nos commandes ont bien été ajoutées à la Queue !

Passons maintenant au Worker. Je ne reviens pas sur l’initialisation qui est la même que pour le projet Web. Vous pouvez récupérer un message en utilisant simplement la méthode GetMessage(). Son contenu est ensuite accessible via la méthode ContentAsString(). Enfin, on enlève de le message de la Queue avec DeleteMessage(). J’ai ajouté un petit message de log et une temporisation d’une seconde.

public override void Start()
{
    RoleManager.WriteToLog("Information", "Worker Process entry point called");

    StorageAccountInfo storageAccountInfoQueue = StorageAccountInfo.GetDefaultQueueStorageAccountFromConfiguration();
    QueueStorage queueStorage = QueueStorage.Create(storageAccountInfoQueue);
    MessageQueue messageQueue = queueStorage.GetQueue("orders");

    if (!messageQueue.DoesQueueExist())
        messageQueue.CreateQueue();

    while (true)
    {
        Message message = messageQueue.GetMessage();

        if (message != null)
        {
            RoleManager.WriteToLog("Information", "Chef must cook ... " + message.ContentAsString());
            messageQueue.DeleteMessage(message);
        }

        Thread.Sleep(1000);
    }
}

On lance et on jette un coup d’œil au moniteur du Worker dans la Development Fabric.

 

Un petit coup d’œil dans l’Azure Storage Explorer et vous constaterez que la Queue s’est bien vidée de ses messages. Vous pouvez continuer à tester l’application en prenant de nouvelles commandes du côté Web et en constatant qu’elles sont bien traitées une par une du côté du Worker.

Vous pouvez tester au passage le fonctionnement du multi instance des Worker. Modifiez le fichier ServiceConfiguration.cscfg de la manière suivante :

<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="2"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Lancez l’application et passez rapidement plusieurs commandes. Du côté de la Development Fabric, vous constaterez que les deux instances du Worker se répartissent bien le travail.

 

  II. Les Blobs

Je rappelle que nous utiliserons les blobs pour stocker les plats préparés par le chef. Ces plats seront simplement représentés par une chaine du caractère correspondant au nom du plat.

La récupération du conteneur de blobs se fait de la même manière que pour récupérer une Queue. N’oubliez pas de compléter le fichier de configuration pour pouvoir vous connecter au service de stockage.

StorageAccountInfo storageAccountInfoBlob = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();
BlobStorage blobStorage = BlobStorage.Create(storageAccountInfoBlob);
BlobContainer blobContainer = blobStorage.GetBlobContainer("dishes");

if(!blobContainer.DoesContainerExist())
    blobContainer.CreateContainer();

Pour ajouter un blob au conteneur, vous devez utiliser la méthode CreateBlob(). Nous devons passer à cette méthode un objet de type BlobProperties et un autre de type BlobContents. Le premier contiendra le nom du blob et le second son contenu que nous devons lui passer en un tableau de byte.

if (message != null)
{
    RoleManager.WriteToLog("Information", "Chef must cook ... " + message.ContentAsString());

    BlobProperties blobProperties = new BlobProperties(message.ContentAsString());
    ASCIIEncoding encoding = new ASCIIEncoding();
    BlobContents blobContents = new BlobContents(encoding.GetBytes(message.ContentAsString()));
    blobContainer.CreateBlob(blobProperties, blobContents, true);
   
    messageQueue.DeleteMessage(message);

    RoleManager.WriteToLog("Information", "Order ready");
}

 

Intéressons nous un peu à la surcharge de la méthode CreateContainer() qui utilise deux paramètres. Le premier concerne l’association de meta data à votre blob. Le second vous permet de rendre le container accessible depuis une url. Essayez la valeur suivante :

blobContainer.CreateContainer(null, ContainerAccessControl.Public);

Si vous vous rendez sur l’URL suivante, vous pourrez voir le contenu du blob : http://127.0.0.1:10000/devstoreaccount1/dishes/Salade
"Salade"

Essayez maintenant en utilisant la valeur Private.

blobContainer.CreateContainer(null, ContainerAccessControl.Private);

Si vous essayez à présent d’accéder à l’URL, vous pourrez lire :

<Error>
<Code>ResourceNotFound</Code>
<Message>The specified resource does not exist.</Message>
</Error>

Par défaut, la valeur utilisée est Private.

Pour en finir avec les blobs, nous allons voir comment les extraire du conteneur. Dans le projet Web, ajoutez une page appelée GetDish. Il faut utiliser la méthode GetBlob() à laquelle il faut passer le nom du blob ainsi que le BlobContents qui devra être complétée.

protected void Page_Load(object sender, EventArgs e)
{
    StorageAccountInfo storageAccountInfoBlob = StorageAccountInfo.GetDefaultBlobStorageAccountFromConfiguration();

    BlobStorage blobStorage = BlobStorage.Create(storageAccountInfoBlob);
    BlobContainer blobContainer = blobStorage.GetBlobContainer("dishes");

    if (!blobContainer.DoesContainerExist())
        blobContainer.CreateContainer(null, ContainerAccessControl.Private);

    string dish = Request.QueryString["dish"];
    BlobContents blobContents =new BlobContents(Response.OutputStream);

    try
    {
        blobContainer.GetBlob(dish, blobContents, false);
    }
    catch(Exception ex)
    {
        Response.StatusCode = 404;
        Response.End();
    }
}

Essayez d’accéder à l’url suivante : http://127.0.0.1:85/GetDish.aspx?dish=Salade. Le contenu du blob s’affiche bien dans la page.

III. Les Tables

Il est temps à présent de parler du dernier type de stockage mis à votre disposition : les tables. Le schéma de fonctionnement des tables est le suivant : chaque table contient une collection d’entités (on peut les assimiler à des lignes dans la table), chacune de ces entités est composée d’une collection de propriétés (on peut les assimiler à des colonnes) et chacune de ses propriétés porte un nom, un type et une valeur. Les types utilisables sont les suivants :

  • Binary
  • Bool
  • DateTime
  • Double
  • GUID
  • Int
  • Long
  • String

Chose importante : une entité doit obligatoirement porter trois propriétés, une partition key, une row key et un timestamp. Les deux premiers forment un identifiant unique pour l’entité. Dans l’exemple suivant, nous utiliserons l’API fournie par le projet StorageClient. Cette API prend en charge le timestamp, nous n’aurons pas à nous en occuper.

Commencez par modifier le fichier de définition et le fichier de configuration.

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole" enableNativeCodeExecution="false">
    <InputEndpoints>
      <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
      <InputEndpoint name="HttpIn" protocol="http" port="80" />
    </InputEndpoints>
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="BlobStorageEndpoint"/>
      <Setting name="QueueStorageEndpoint"/>
      <Setting name="TableStorageEndpoint"/>
    </ConfigurationSettings>
  </WebRole>
  <WorkerRole name="WorkerRole" enableNativeCodeExecution="false">
    <ConfigurationSettings>
      <Setting name="AccountName"/>
      <Setting name="AccountSharedKey"/>
      <Setting name="BlobStorageEndpoint"/>
      <Setting name="QueueStorageEndpoint"/>
    </ConfigurationSettings>
  </WorkerRole>
</ServiceDefinition>

<?xml version="1.0"?>
<ServiceConfiguration serviceName="CloudStorageDemo" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration">
  <Role name="WebRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="BlobStorageEndpoint" value="http://127.0.0.1:10000"/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
      <Setting name="TableStorageEndpoint" value="http://127.0.0.1:10002"/>
    </ConfigurationSettings>
  </Role>
  <Role name="WorkerRole">
    <Instances count="1"/>
    <ConfigurationSettings>
      <Setting name="AccountName" value="devstoreaccount1"/>
      <Setting name="AccountSharedKey" value="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="/>
      <Setting name="BlobStorageEndpoint" value="http://127.0.0.1:10000"/>
      <Setting name="QueueStorageEndpoint" value="http://127.0.0.1:10001"/>
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Ensuite, il faut créer la classe qui représentera l’entité plat. On oublie pas de définir les deux propriétés partition key et row key. La classe doit dériver de la classe TableStorageEntity, classe fournie par le projet StorageClient.

public class Dish : TableStorageEntity
{
    public string DishName { get; set; }

    public Dish()
    {
        PartitionKey = "Dishes";
        RowKey = DateTime.Now.Ticks.ToString();
    }
}

Ajoutez maintenant une référence à l’assembly System.Data.Services.Client. Créez une classe RestaurantContext qui dérivera de TableStorageDataServiceContext (qui nous vient encore et toujours de StorageClient). On ajoute la déclaration de la table Dishes.

public class RestaurantContext : TableStorageDataServiceContext
{
    public IQueryable<Dish> Dishes
    {
        get
        {
            return CreateQuery<Dish>("Dishes");
        }
    }
}

Il est maintenant temps de générer les tables représentant les entités que vous venez de définir. Pour se faire, Visual Studio dispose d’un petit bouton magique. Faîtes un clic droit sur le projet Cloud et choisissez « Create Test Storage Tables ».

 

Si vous ouvrez Microsoft SQL Server Management, vous pourrez constater que la table a bien été générée :

 

Attention ! Cette manière de faire ne marche que pour créer les tables en local. Pour pouvoir créer une table sur le cloud, vous devrez passer par le code.

TableStorage.CreateTablesFromModel(typeof(RestaurantContext),
    StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());

On va maintenant ajouter une petite méthode statique à la classe RestaurantContext histoire de nous faciliter l’ajout d’un plat à la table Dishes.

public static void AddDish(Dish dish)
{
    RestaurantContext restaurantContext = new RestaurantContext();

    restaurantContext.AddObject("Dishes", dish);

    restaurantContext.SaveChanges();
}

On ajoute une page AddDish.aspx. Sur cette page on place une textbox, un bouton et un label.

<asp:TextBox ID="tbDishName" runat="server" />
<asp:Button ID="bAddDish" runat="server" Text="Add" onclick="bAddDish_Click" />
<asp:Label ID="lStatus" runat="server" />

Dans le code behind, on crée un objet de type Dish et on l’insère dans la table.

protected void bAddDish_Click(object sender, EventArgs e)
{
    Dish dish = new Dish()
    {
        DishName = tbDishName.Text
    };

    RestaurantContext.AddDish(dish);

    lStatus.Text = tbDishName.Text + " added";
}

Testez ce nouveau formulaire et admirez le contenu de la table Dishes via Azure Storage Explorer.

 

Dernière chose à faire : récupérer le contenu de la table pour l’afficher dans une liste déroulante sur la page Order.aspx. On ajoute un composant DropDownList et on profite du databinding pour le remplir.

<asp:Label ID="lOrderName" runat="server" Text="Order Name : " /><asp:DropDownList ID="ddlOrderName" runat="server" /><br />
<asp:Button ID="bTakeOrder" runat="server" Text="OK"
    onclick="bTakeOrder_Click" /><br />
<asp:Label ID="lWaiterStatus" runat="server" />

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        RestaurantContext restaurantContext = new RestaurantContext(StorageAccountInfo.GetDefaultTableStorageAccountFromConfiguration());
        ddlOrderName.DataSource = restaurantContext.Dishes;
        ddlOrderName.DataValueField = "DishName";
        ddlOrderName.DataBind();
    }
}

Un petit coup d’œil sur la page Order.aspx et on constate que le contenu de la table est correctement récupéré.

 

Voila, j’espère que ce billet saura vous guider dans vos premiers pas avec les systèmes de stockage mis à disposition par Windows Azure. A bientôt !

C#, Azure , , , ,

Comments

6/25/2009 3:53:23 AM #
Superbe article !
Cependant il est dommage que le code-source ne soit pas colorié, cela rend sa lecture assez ardu.
6/25/2009 6:50:23 AM #
Merci Smile

Ouaip ça facilite vraiment pas la lecture... J'essayerais de corriger ça.

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading

captcha

*