Hello Web Role! – Parte 2: Alla scoperta dello storage service

Nel post precedente abbiamo visto come realizzare la nostra prima soluzione cloud con un web role e come controllare il suo funzionamento utilizzando Windows Azure Diagnostics. In questo post vedremo come è possibile utilizzare lo storage service per memorizzare e pubblicare dati ed informazioni. I web role ed i worker role vengono eseguiti su istanze di macchine virtuali che mettono a disposizione uno spazio per la memorizzazione locale di file (local storage) volatile e quindi non utilizzabile per la memorizzazione persistente di file. Per la memorizzazione stabile di informazioni è possibile usare lo storage service o un database relazionale come SQL Azure.

Gli storage services di Windows Azure forniscono spazio di memorizzazione per dati testuali e binari, messaggi e dati strutturati.

I servizi offerti includono:

  • Il Blob service, per la memorizzazione di file
  • Il Queue service, per la memorizzazione di messaggi in una coda
  • Il Table service, per la memorizzazione di dati in forma strutturata non relazionale
  • I Windows Azure Drives, che permettono il mount di volumi NTFS da parte di una singola istanza

I servizi sono accessibili attraverso API REST che permettono l’accesso da una soluzione in esecuzione su Windows Azure o direttamente da qualunque applicazione in grado di inviare richieste e ricevere risposte HTTP/HTTPS su internet. L’accesso agli storage service avviene attraverso l’account. L’account è il livello più elevato nello spazio dei nomi per l’accesso ai servizi ed è alla base del processo di autenticazione. Nelle API REST l’account è esposto dai servizi come una risorsa.

Il blob service

Il Blob service fornisce lo storage per la memorizzazione di entità come file binari e di testo.Le API REST del blob service espongono due risorse: i container e i blob. Un container è un set di blob e ogni blob appartiene ad un container. Il blob service definisce due tipi di blob: i block blob, ottimizzati per lo streaming, ed i page blob. Questi ultimi sono ottimizzati per le operazioni di lettura e scrittura random, permettendo la scrittura di un range di byte in un blob. Sia ai container che ai blob è possibile associare metadati come coppie nome-valore definiti dall’utente.

Sebbene la struttura sia essenzialmente orizzontale, con i blob contenuti in un container, è possibile creare una struttura gerarchica simile ad un file system. I nomi dei blob possono contenere la codifica della gerarchia usando un separatore del percorso configurabile. Ad esempio i nomi di blob Gruppo/Blob1 e Gruppo/Blob2 implicano una organizzazione gerarchica virtuale. Ad agevolare questo genere di approccio, le API espongono dei metodi che permettono la navigazione delle gerarchie in modo simile a come siamo soliti fare sui file system consentendo, ad esempio, il recupero dei blob appartenenti ad un gruppo. Sull’MSDN è disponibile la reference delle API del blob service.

Il queue service

Il queue service fornisce un sistema affidabile per lo scambio di messaggi all’interno di un servizio e tra servizi differenti. Le API REST del queue service espongono due risorse: code e messaggi. Le code supportano la definizione di metadati come coppie nome-valore. Ogni account può contenere un numero illimitato di code di messaggi con nome univoco. Ogni coda può contenere un numero illimitato di messaggi con dimensione massima pari a 8 KB.

Quando un messaggio viene letto dalla coda, viene reso invisibile agli altri consumatori per un certo periodo di tempo. Se il messaggio non viene cancellato entro la scadenza dell’intervallo, viene ripristinata la sua visibilità in modo da permettere ad un altro consumatore di processarlo. Per approfondire l’argomento sulle code, la reference sulle API del queue service disponibile sull’MSDN è un ottimo inizio.

Il table service

Il table service fornisce uno storage strutturato sotto forma di tabelle che supporta le API REST ed è compatibile con le API REST di ADO.NET Data Services. All’interno dello storage account è possibile creare tabelle con nome univoco, all’interno delle quali vengono memorizzate informazioni come entità. Le entità sono collezioni di proprietà con i relativi valori, in modo similare ad una riga. Le tabelle sono partizionate al fine di supportare il load balancing tra i nodi e ogni tabella ha la prima proprietà utilizzata come chiave (partition key) per identificare la partizione a cui appartiene. La seconda proprietà identifica la riga (row key) che identifica un’identità all’interno della partizione. La combinazione tra la partition key e la row key forma la primary key che identifica l’entità in modo univoco all’interno della tabella. La tabella non impone nessuno schema, di conseguenza è lo sviluppatore a implementare e imporre lo schema lato client. Come per gli altri servizi, anche le API del table service sono ben descritte in una sezione dell’MSDN.

Al lavoro!

Adesso che abbiamo fatto amicizia con gli storage service, riprenderemo il progetto del post precedente e vedremo come è possibile effettuare l’upload di un file sul local storage e la sua successiva memorizzazione sullo storage service. Parallelamente, memorizzeremo un messaggio che notifica l’avvenuto upload.

Come prima cosa assicuriamoci della correttezza della configurazione del local storage sul web role:

Annotiamo il nome del local storage, utilizzato per l’acquisizione della risorsa. Apriamo la pagina default.aspx ed inseriamo un campo fileupload ed un pulsante. Per evitare confusione lasciamo i nomi predefiniti. Andiamo sull’handler del click e inseriamo il codice per l’upload sul local storage:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.WindowsAzure.ServiceRuntime;
using System.IO;

namespace WebRole1
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            LocalResource LocalStorage =
                  RoleEnvironment.GetLocalResource("LocalStorage1");
            if (FileUpload1.FileContent.Length > 0){
                FileUpload1.SaveAs(
                      LocalStorage.RootPath + FileUpload1.FileName);
            }
        }
    }
}

Utilizzando il metodo RoleEnvironment.GetLocalResource(“LocalStorage1”) (vedi MSDN) otteniamo l’accesso all’oggetto LocalResource che contiene i dettagli sul local storage LocalStorage1: Name, MaximumSizeInMegabytes e RootPath. Quest’ultimo contiene il percorso fisico alla directory assegnata al local storage, che utilizzeremo per salvare il file.

Lanciamo il progetto ed effettuiamo l’upload di un file. Se apriamo il l’interfaccia utente del Compute Emulator possiamo verificare l’effettivo upload. Basta cliccare col tasto destro sull’istanza (se sono due potremmo dover ritentare con l’altra) e scegliere “Open local store”. I file del LocalStorage1 sono contenuti in \directory\LocalStorage1.

Prima di passare alla scrittura del codice relativo alle operazioni sullo storage service, è necessario impostare il settaggio relativo alla stringa di connessione all’account. Nel nostro caso useremo il development storage e aggiungeremo, immediatamente sotto la stringa di connessione impostata per la memorizzazione dei dati diagnostici, la voce “BlobConnectionString” di tipo connection string con valore “UseDevelopmentStorage=true”.
Per maggiori informazioni sulla configurazione delle stringhe di connessione, c’è una sezione ad hoc sull’MSDN.

Riprendiamo l’handler dell’evento click di Button1 e iniziamo a scrivere il codice che ci permetterà di copiare il file su un blob. Per prima cosa è necessario creare un oggetto CloudStorageAccount inizializzato utilizzando la stringa di connessione che abbiamo inserito in precedenza. Per recuperarne il valore utilizziamo la classe RoleEnvironment che permette l’accesso all’ambiente di esecuzione del role e che abbiamo già utilizzato per avere accesso al local storage (vedi MSDN).
Adesso, disponendo dell’oggetto CloudStorageAccount e quindi l’indirizzo dell’endpoint Blob e delle credenziali, possiamo inizializzare il CloudBlobClient, l’oggetto che rappresenta il client di accesso al blob service:

CloudStorageAccount Storage = CloudStorageAccount.Parse(
RoleEnvironment.GetConfigurationSettingValue("BlobConnectionString"));
CloudBlobClient Client =
     new CloudBlobClient(Storage.BlobEndpoint, Storage.Credentials);

A questo punto possiamo passare alla definizione del container utilizzando il metodo GetContainerReference. E’ bene ricordare che il nome dei container deve eseguire le regole  per qualunque nome DNS valido:

  1. Il nome deve iniziare per lettera o numero e contenere solo lettere, numeri ed il carattere – (trattino).
  2. Il carattere – deve tassativamente essere preceduto e seguito da una lettera o da un numero.
  3. Tutte le lettere del nome devono essere minuscole.
  4. Il nome deve avere una lunghezza compresa tra 3 e 63 caratteri.

Il metodo SetPermissions permette di impostare il livello di accesso al container. Nel nostro caso il container non ha restrizioni d’accesso. Come di consueto, l’MSDN potrà fornirvi maggiori informazioni in proposito. L’ultima operazione da fare sul container è chiamare il metodo CreateIfNotExist, dal nome più che autodescrittivo.

CloudBlobContainer Container = Client.GetContainerReference("uploads");
Container.SetPermissions(new BlobContainerPermissions
 { PublicAccess = BlobContainerPublicAccessType.Container });
Container.CreateIfNotExist();

In modo analogo a come fatto in precedenza per il container, creiamo il riferimento al blob usando il metodo GetBlobReference del container. Come detto in precedenza, ai blob è possibile associare dei metadati sotto forma di coppie nome-valore. In questo caso creeremo il metadato “MIME” contenente il content type del file. Il metodo UploadFile del container ci permette di uploadare il contenuto, creando il blob o sovrascrivendo l’eventuale blob omonimo.

   CloudBlob Blob = Container.GetBlobReference(FileUpload1.FileName);
   Blob.Metadata["MIME"] = FileUpload1.PostedFile.ContentType;
   Blob.UploadFile(LocalStorage.RootPath + FileUpload1.FileName);

A questo punto, con un processo assolutamente analogo, definiamo una coda chiamata “filequeue”. E accodiamo un messaggio contenente il nome del file appena uploadato sul cloud storage. Maggiori informazioni sullla gestione delle code è disponibile in questa pagina dell’MSDN.

   CloudQueueClient QueueClient = Storage.CreateCloudQueueClient();
   CloudQueue Queue = QueueClient.GetQueueReference("filequeue");
   Queue.CreateIfNotExist();
   CloudQueueMessage Message = new CloudQueueMessage(FileUpload1.FileName);
   Queue.AddMessage(Message);

A questo punto non resta che lanciare il progetto e uploadare un file. Giocando con i breakpoint,  il Compute Emulator ed il Server Explorer è possibile vedere il file apparire prima sul local storage e poi sul blob service.

Alla prossima!

Questa voce è stata pubblicata in Programmazione, Windows Azure Tutorial e contrassegnata con , , , , , , , , , . Contrassegna il permalink.

Lascia un commento

Questo sito utilizza Akismet per ridurre lo spam. Scopri come vengono elaborati i dati derivati dai commenti.