dinsdag 26 maart 2013

Elastic Computing

Onlangs kreeg ik een mailtje van een hostingprovider die zijn clouddiensten wilde aanbieden. Het was zo'n ronkende tekst over hoe geweldig hun dienst wel niet was en hoe de kosten nog verder gereduceerd konden worden door "elastic computing". Elastic Computing? Geen idee wat ze daarmee bedoelen, maar het klinkt ingewikkeld genoeg om serieus te nemen. Vooral dat kostenreducerende aspect spreekt mij als oerhollander wel aan. Op Wikipedia vind ik het volgende over "elastic computing":

Elastic computing is the use of computer resources which vary dynamically to meet a variable workload. This is a feature of cloud computing such as the Amazon Elastic Compute Cloud.

Aha! Dus als de systeemresources, zoals processorkracht en geheugengebruik, dreigen op te raken, worden die resources geheel automatisch opgeschaald. Bij overvloed wordt de hele zaak weer afgeschaald. 
Dat moet in Windows Azure natuurlijk ook mogelijk zijn en inderdaad, er is zelfs al software op de markt, zoals Enterprise Library en AzureWatch, die dit schalen al voor je doet. Voor een betere begripsvorming ga ik er nu zelf eentje schrijven.

Om te kunnen schalen moeten we eerst weten hoe het met de computerresources gesteld is. Dit kan via het tooltje "perfmon.exe" dat op ieder Windows systeem te vinden is (afbeelding 1). Erg handig programmaatje dat het resourcegebruik tot in de goorste details inzichtelijk maakt.


Afbeelding 1. perfmon.exe met het processorgebruik en het vrije geheugen

Perfmon doet niets anders dan performance counters, die door de softwaremakers zijn geïmplementeerd, uitlezen en in een fraai grafiekje presenteren. Als deze software die performance counters kan uitlezen, dan kunnen wij dat natuurlijk ook. Voor Windows Azure is er een bibliotheek gemaakt waarin alle benodigdheden verzameld zijn (Microsoft.WindowsAzure.Diagnostics.dll). Het probleem doet zich nu voor dat we in een cloud omgeving meerdere windowssystemen hebben met ieder zijn eigen perfomance monitor. Om die data te verzamelen zouden we al die omgevingen moeten bezoeken. Afgezien van toegangsperikelen is dat eigenlijk onbegonnen werk. Een betere benadering is om die instanties hun eigen performance data te laten posten in de table storage. Die table storage kunnen we zelf dan weer uitlezen en zo krijgen we inzicht in het resourcegebruik. Nadeel is wel dat die storage snel vol kan lopen. Nu is er in de cloud storage aan ruimte geen gebrek, maar er moet uiteindelijk wel voor worden betaald.

Wanneer je de data geanalyseerd hebt, zul je eventueel actie moeten ondernemen. Wij doen dit via de Azure Portal, maar als we dit willen automatiseren, zullen we het anders aan moeten pakken. Gelukkig biedt Windows Azure, naast de portal, ook een ReST (Representational State Transfer) API aan waarmee we instructies aan Windows Azure kunnen geven.

Performance data verzamelen en opslaan in de Table Storage

De eerste stap is het verzamelen van de performance data. Deze data wordt lokaal op de instantie verzameld en op gezette tijden naar de table storage geduwd. Dat stuk moeten we eerst configureren. Dat doe je in de WebRole class (in het geval van een Web Role) of in de WorkerRole class (bij een Worker Role). Beide classen erfen van RoleEntryPoint. Wanneer je van een bestaande website uit gaat, zal die WebRole class in het algemeen niet aanwezig zijn. Geen nood, gewoon alsnog zelf toevoegen.
In die class overschrijf je de OnStart() methode waarin je de gewenste performance counters configureert. Zo'n implementatie kan er als volgt uitzien:

public class WebRole : RoleEntryPoint
{
    private string configString =  "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";

    public override bool OnStart()
    {
        CloudStorageAccount cloudStorageAccount = CloudStorageAccount.Parse(
                RoleEnvironment.GetConfigurationSettingValue(configString ));

        RoleInstanceDiagnosticManager roleInstanceDiagnosticManager = cloudStorageAccount.CreateRoleInstanceDiagnosticManager(
            RoleEnvironment.DeploymentId,
            RoleEnvironment.CurrentRoleInstance.Role.Name,
            RoleEnvironment.CurrentRoleInstance.Id);

        DiagnosticMonitorConfiguration config = roleInstanceDiagnosticManager.GetCurrentConfiguration() ?? new DiagnosticMonitorConfiguration();

        config.PerformanceCounters.DataSources.Add(
            new PerformanceCounterConfiguration
            {
                CounterSpecifier = @"\Processor(_Total)\% Processor Time",
                SampleRate = TimeSpan.FromSeconds(5)
            });
        config.PerformanceCounters.DataSources.Add(
            new PerformanceCounterConfiguration
            {
                CounterSpecifier = @"\Memory\Available MBytes",
                SampleRate = TimeSpan.FromSeconds(5)
            });
        config.PerformanceCounters.ScheduledTransferPeriod = TimeSpan.FromMinutes(15.0);
        config.PerformanceCounters.BufferQuotaInMB = 512;

        DiagnosticMonitor.Start(configString , config);
        return base.OnStart();
    }
}

Het eerste dat hier gebeurt, is het doorgeven van de cloud storage credentials. Deze informatie komt uit het configuratiebestand en staat onder de naam "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString", die verschijnt wanneer je "Enable Diagnostics" aan hebt staan (afbeelding 1 en 2)


Afbeelding 1 & 2. Diagnose aanzetten.

Op basis van deze storage informatie, die de DiagnosticManager nodig heeft om toegang te krijgen tot de table storage, genereren we de RoleInstanceDiagnosticManager waarmee we een handvat krijgen naar de DiagnosticMonitorConfiguration. Hierop geven we aan welke performance counters we willen hebben ("\Processor(_Total)\% Processor Time" en 
"\Memory\Available MBytes"). Deze kreten kun je zo uit perfmon.exe plukken.
Vervolgens stellen we in na hoeveel minuten de data naar de table storage wordt gestuurd, hoe groot de interne buffers maximaal mogen worden en tenslotte roepen we de Start() methode van de DiagnosticManager aan. De zaak is aan het lopen. Nu wordt om de 15 minuten de performance data in de table storage gezet (tabelnaam: WADPerformanceCountersTable. Zie afbeelding 3)


Afbeelding 3. De performance data vanuit de table storage

De performance data uit de table storage lezen

Om de data uit de storage te lezen maak je eerst een CloudStorageAccount object aan. Hiermee kun je een TableServiceContext object initialiseren (vergelijkbaar met de ObjectContext in Entity Framework) waarmee je queries op de table storage kunt definiëren. Merk op dat we de query omzetten naar een CloudTableQuery object, die je krijgt met de extensiemethode AsTableServiceQuery(). Standaard  zal een query op een storage table maximaal 1000 records teruggeven. De rest  moet je via een continuation token eruit trekken. CloudQueryTable zal dit alles voor  jou doen. 
Nadat je alle data uit de table storage hebt getrokken en verwerkt, is het zaak om de table storage weer leeg te maken. Hierbij moet je wel letten op het feit dat iedere actie geld kost, dus gooi de records niet één voor één weg, maar knikker de hele tabel weg of probeer de te verwijderen records in een batch te stoppen (is aanzienlijk lastiger). Een mogelijke implementatie is hieronder weergegeven.


class Program
{
    static void Main(string[] args)
    {
        StorageCredentialsAccountAndKey account =
            new StorageCredentialsAccountAndKey("storage_naam""storage_key");
        CloudStorageAccount cloudStorageAccount = new CloudStorageAccount(account, true);
        TableServiceContext serviceContext =
            new TableServiceContext(cloudStorageAccount.TableEndpoint.AbsoluteUri, cloudStorageAccount.Credentials);
        var query = serviceContext.CreateQuery<PerformanceCountersEntity>("WADPerformanceCountersTable");

        CloudTableQuery<PerformanceCountersEntity> cpu_query = 
                 query.Where(n => n.CounterName == @"\Processor(_Total)\% Processor Time").AsTableServiceQuery();
        CloudTableQuery<PerformanceCountersEntity> mem_query = 
                  query.Where(n => n.CounterName == @"\Memory\Available MBytes").AsTableServiceQuery();
        
        List<PerformanceCountersEntity> cpuList = cpu_query.ToList();
        List<PerformanceCountersEntity> memList = mem_query.ToList();
        if (cpuList.Count > 0)
        {
            Console.WriteLine(cpuList.Average(e=>double.Parse(e.CounterValue)));
        }
        if (memList.Count > 0)
        {
            Console.WriteLine(memList.Average(e => double.Parse(e.CounterValue)));
        }
        cloudStorageAccount.CreateCloudTableClient().DeleteTable("WADPerformanceCountersTable");
    }
}

public class PerformanceCountersEntity : TableServiceEntity
{
    public long EventTickCount { getset; }
    public string DeploymentId { getset; }
    public string Role { getset; }
    public string RoleInstance { getset; }
    public string CounterName { getset; }
    public string CounterValue { getset; }
}



Werken met de Azure ReST api.

Tot dusver hebben we alles via de Azure Portal kunnen afregelen, maar als we zaken willen automatiseren, hebben we weinig aan die grafische interface. Gelukkig biedt Microsoft hiervoor een ReST API aan. ReST is een op http gebaseerde manier om te kunnen communiceren met andere computers. ReST is bijzonder eenvoudig en licht in gebruik en kan in combinatie met xml, json of wat dan ook gebruikt worden. Ideaal dus voor communicatie over internet. Windows Azure gebruikt ReST in combinatie met xml, dus de data wordt in xml-formaat aangeleverd. Natuurlijk willen we niet dat Jan en alleman zomaar aan onze clouddienst gaat lopen morrelen, vandaar dat Windows Azure eerst wil weten of jij wel mag wijzigen. Hiervoor gebruiken ze certificaten. Aaahhhh certificaten! Toch niet die enge dingen? Helaas wel. 
Zo'n certificaat bestaat in de basis uit een public en private key die bij elkaar horen (schijnt iets ingewikkelds met priemgetallen te zijn). Daarnaast heb je nog wat extra herkenningsinformatie. Meestal zul je van zo'n certificaat nog een variant gebruiken die alleen de public key bevat. Dit certificaat lever je uit aan derden. In dit geval dus Windows Azure (in de Azure portal bij Settings->Managment Certificates kun je hem uploaden).
Certificaten kun je kopen, bijvoorbeeld bij  Verisign of Thawte, en zijn knetterduur. Gelukkig hebben we bij Visual Studio de beschikking over het makecert.exe tooltje dat voor ons certificaten genereert. Deze zijn bedoeld voor testdoeleinden en missen dus het autoriteitstoken van Verisign of Thawte (vergelijk het maar met een vals paspoort. Is ook niet uitgegeven door een officiële instantie). Het commando ziet er als volgt uit (uitvoeren met administrator rechten)

makecert -sky exchange -r -n "CN=<CertificateName>" -pe -a sha1 -len 2048 -ss My "<CertificateName>.cer"

Onthoud het gedeelte "CN=<CertificateName>". Dit is de subject name die je kunt gebruiken om het certificaat op te vragen. (als <CertificateName> kies ik BananenRepubliek)
Wanneer dit gelukt is, kun je met "certmgr.msc" kijken of hij in de certificate store van de Current User is opgenomen (afbeelding 4)


Afbeelding 4. Het certificaat met de public en private key is geregistreerd.
Het resulterende .cer bestand kun je nu uploaden naar de Azure Portal.
In de code hieronder kun je zien hoe je zo'n certificaat uitleest in. Het "certificate" object wordt uiteindelijk gebruikt voor de ReST aanroepen.

class Program
{
    static void Main(string[] args)
    {
        X509Certificate2 certificate = ReadCurrentUserMyCertificate("Bananenrepubliek");
        if (certificate == nullreturn;
    }

    private static X509Certificate2 ReadCurrentUserMyCertificate(string subjectName)
    {
        X509Store myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        myStore.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection certificates =
               myStore.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false);
        myStore.Close();
        if (certificates.Count > 0)
        {
            return certificates[0];
        }
        return null;
    }
}

De volgende stap is het in elkaar knutselen van de ReST aanroepen. Hiervoor 
kun je het best het HttpWebRequest object gebruiken. Hieronder zie je dat uitgewerkt in een base class. Let hierbij op wat je wilt doen (method). Dit kan zijn:
  1. GET om data op te vragen
  2. PUT voor update acties
  3. POST voor inserts
  4. DELETE voor het verwijderen van data.
Daarnaast is de version van belang. De Windows Azure ReST API wordt voortdurend uitgebreid en nieuwe toevoegingen werken dan ook alleen vanaf een bepaalde datum. Die datum geeft je op bij version. Let ook op het BaseAddress dat er als volgt uit ziet, "https://management.core.windows.net/<subscriptionID>/". SubscriptionID kun je weer in de Azure Portal vinden.
Een ReST aanroep kan er als volgt uitzien:

https://management.core.windows.net/<subscriberID>/services/hostedservices

Deze aanroep geeft een lijst van alle services, behorend bij een subscriptionID, terug. De volgende ReST aanroep vraagt informatie op van een specifieke service. De querystring "embed-detail=true" geeft daarbij ook een lijst van alle releases (deployments) terug. Die laatste heb je nodig om op te vragen hoeveel instanties er momenteel actief zijn.


https://management.core.windows.net/<subscriberID>/services/hostedservices/<hostedservice>?embed-detail=true

waarbij <hostedservice> de naam van de service is. Een volledige beschrijving van de ReST API vind je hier.

public class AzureClient
{
    private X509Certificate2 _certificate = null;
    protected readonly string BaseAddress;

    protected void DoRestCall(string url, 
                                         Action
<XmlReaderHttpStatusCode> returnAction, 
                                         string
 method = "GET"
                                         string
 version = "2012-03-01"
                                         byte
[] data = null)
    {
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
        request.Method = method;
        request.Headers.Add("x-ms-version", version);
        request.ClientCertificates.Add(_certificate);
        request.ContentType = "application/xml";

        if (data != null)
        {
            request.ContentLength = data.Length;
            using (Stream stream = request.GetRequestStream())
            {
                stream.Write(data, 0, data.Length);
            }
        }

        HttpStatusCode statusCode;
        HttpWebResponse response = null;
        XmlReader reader;
        try
        {
            response = (HttpWebResponse)request.GetResponse();
        }
        catch (WebException ex)
        {
            response = (HttpWebResponse)ex.Response;
        }

        statusCode = response.StatusCode;
        if (response.ContentLength > 0)
        {
            reader = XmlReader.Create(response.GetResponseStream());
            if (returnAction != null)
            {
                returnAction(reader, statusCode);
            }
        }
        if (response != null)
        {
            response.Dispose();
        }
    }
    public AzureClient(X509Certificate2 certificate, string subscriptionID)
    {
        _certificate = certificate;
        BaseAddress = string.Format("https://management.core.windows.net/{0}/", subscriptionID);
    }
}


Voor het managen van onze cloud service leiden we een nieuwe class van de hierboven beschreven base class af. Hierin programmeren we de echte ReST calls. een voorbeeld van zo'n afgeleide class is hieronder uitgewerkt. Hij bestaat uit drie aanroepen:
  1. GetHostedService, om informatie over een cloud service op te vragen
  2. GetHostedServiceDeployment, om informatie over een specifieke release op te vragen
  3. UpdateHostedServiceDeployments, om een release op of af te schalen.

public class AzureManagementClient : AzureClient
{
    private const string SERVICES = "services";
    private const string HOSTEDSERVICES = "hostedservices";
    private const string DEPLOYMENTS = "deployments";

    private T Deserialize<T>(XmlReader reader) where T : class
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        return serializer.Deserialize(reader) as T;
    }

    public HostedService GetHostedService(string serviceName)
    {
        string url = string.Format("{0}/{1}/{2}/{3}?embed-detail=true", BaseAddress, SERVICES, HOSTEDSERVICES, serviceName);
        HostedService service = null;
        DoRestCall(url, (reader, statusCode) =>
        {
            if (statusCode == HttpStatusCode.OK)
            {
                service = Deserialize<HostedService>(reader);
            }
        });

        return service;
    }

    public Deployment GetHostedServiceDeployment(string serviceName, string deploymentUniqueName)
    {
        string url = string.Format("{0}/{1}/{2}/{3}/{4}/{5}", BaseAddress, SERVICES, HOSTEDSERVICES, serviceName, DEPLOYMENTS, deploymentUniqueName);
        Deployment deployment = null;
        DoRestCall(url, (reader, statusCode) =>
        {
            if (statusCode == HttpStatusCode.OK)
            {
                deployment = Deserialize<Deployment>(reader);
            }
        });

        return deployment;
    }

    public bool UpdateHostedServiceDeployments(string serviceName, string deploymentUniqueName, ChangeConfiguration configuration)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(ChangeConfiguration));
        byte[] bytes;
        using (MemoryStream stream = new MemoryStream())
        {
            serializer.Serialize(stream, configuration);
            bytes = stream.ToArray();
        }

        bool isSuccess = false;
        string url = string.Format("{0}/{1}/{2}/{3}/{4}/{5}/?comp=config", BaseAddress, SERVICES, HOSTEDSERVICES, serviceName, DEPLOYMENTS, deploymentUniqueName);
        DoRestCall(url, (reader, statusCode) =>
        {
            if (statusCode == HttpStatusCode.BadRequest)
            {
                Error error = Deserialize<Error>(reader);
                if (error.Message == "No change in settings specified")
                {
                    // Met deze foutmelding is de opschaling geslaagd.
                    isSuccess = true;
                }
            }
        }, "POST", data: bytes);

        return isSuccess;
    }

    public AzureManagementClient(X509Certificate2 certificate, string subscriptionID)
        : base(certificate, subscriptionID)
    {

    }
}

de ReST aanroepen geven xml-data terug die we zelf kunnen uitvlooien, maar wellicht is het handiger om die data meteen om te zetten naar .NET objecten. Dat kun je het beste met de class XmlSerializer doen zoals hierboven gebeurt in de methode Deserialize<T>(). Hiervoor heb je wel een aantal class definities nodig waarnaar XmlSerializer de xml-data vertaalt. Hieronder zie je een aantal van die classen uitgewerkt (Let op dat je de propertienamen gelijk houdt aan de tag-namen in de xml-structuur. Verder zijn alleen properties opgenomen die ik relevant vond. Er komt nog veel meer data terug).


[XmlRoot("HostedService", Namespace = "http://schemas.microsoft.com/windowsazure")]
public class HostedService
{
    public string Url { getset; }
    public string ServiceName { getset; }
    public Deployment[] Deployments{ getset; }
}

[XmlRoot("Deployment", Namespace = "http://schemas.microsoft.com/windowsazure")]
public class Deployment
{
    public string Name { getset; }
    public string DeploymentSlot { getset; }
    public string PrivateID { getset; }
    public string Running { getset; }
    public string Label { getset; }
    public string Configuration { getset; }
    [XmlIgnore]
    public XDocument ConfigurationXml
    {
        get
        {
            return XDocument.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(Configuration)));
        }
        set
        {
            Configuration = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
        }
    }
}

[XmlRoot("ChangeConfiguration", Namespace = "http://schemas.microsoft.com/windowsazure")]
public class ChangeConfiguration
{
    public string Configuration { getset; }
    public string Mode { getset; }
    public string ExtendedProperties { getset; }

    [XmlIgnore]
    public XDocument ConfigurationXml
    {
        get
        {
            return XDocument.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(Configuration)));
        }
        set
        {
            Configuration = Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
        }
    }
}

[XmlRoot("Error", Namespace = "http://schemas.microsoft.com/windowsazure")]
public class Error
{
    public string Code { getset; }
    public string Message { getset; }
}

Al het voorbereidende werk is nu achter de rug. Nu kunnen we de zaak aan elkaar knopen. Hieronder zie je een voorbeeld waarbij een specifieke release wordt geschaald naar acht instanties. We halen hiervoor eerst de actuele configuratie van de deployment op. Daarin wijzigen we het aantal instanties en de gewijzigde configuratie sturen we vervolgens weer naar Windows Azure.


class Program
{
    static void Main(string[] args)
    {
        X509Certificate2 certificate = ReadCurrentUserMyCertificate("Bananenrepubliek");
        if (certificate == nullreturn;

        AzureManagementClient client = new AzureManagementClient(certificate, "subscriberid");
        HostedService service = client.GetHostedService("myservice");
        XDocument configXml = service.Deployments[0].ConfigurationXml;   
        XNamespace wa = "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration";
        XElement serviceConf = configXml.Element(wa + "ServiceConfiguration");
        var instances = serviceConf.Descendants(wa + "Instances").SingleOrDefault();
        if (instances != null)   
        {
            instances.SetAttributeValue("count", 8);
        }
        ChangeConfiguration changeConfig = new ChangeConfiguration();
        changeConfig.ConfigurationXml = configXml;
        changeConfig.Mode = "auto";
        client.UpdateHostedServiceDeployments("myservice""deployment_unique_name", changeConfig);
    }
 
    private static X509Certificate2 ReadCurrentUserMyCertificate(string subjectName)
    {
        X509Store myStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
        myStore.Open(OpenFlags.ReadOnly);
        X509Certificate2Collection certificates = myStore.Certificates.Find(X509FindType.FindBySubjectName, subjectName, false);
        myStore.Close();
        if (certificates.Count > 0)
        {
            return certificates[0];
        }
        return null;
    }
}

Nu je weet hoe je de performancedata kunt opvragen en hoe je ReST calls stuurt naar Windows Azure, kun je een Windows service in elkaar draaien die op gezette tijden de storage uitleest, bepaalt of er op- of afgeschaald moet worden en die de uiteindelijke ReST aanroepen naar Windows Azure verzorgt. Het bouwen van zo'n dienst laat ik aan jouw eigen fantasie over.

woensdag 20 maart 2013

Services in de Clouds

BesteProduct heeft op dit moment één server waarop de database draait en een aantal WCF (Windows Communication Foundation) services. Deze services leveren o.a. de datamodellen aan, die op een andere server worden geconsumeerd door de webapplicatie. Hier wordt vervolgens een mooi jasje om de datamodellen gegoten middels ASP.NET MVC 4. Het resultaat hiervan is wat de  bezoekers uiteindelijk te zien krijgen. De server waarop de webapplicatie draait kan de vraag gemakkelijk aan, maar de server waarop de WCF-services draaien heeft het knap lastig. Hij moeten immers de modelspecifieke data uit de database vissen. Omdat die data uit alle hoeken en gaten moet komen, zijn er vaak nogal wat queries nodig.
In de drukke decembermaand werd pijnlijk duidelijk dat deze server dergelijke piekmaanden nog maar net aankan, maar een volgende feestmaand zal hij niet overleven. Wat kunnen we nu doen:
  1. Processors en meer RAM bijprikken. Kostbare aangelegenheid en niet echt een oplossing, meer een pleister. Vroeg of laat loop je toch tegen het hardwareplafond aan.
  2. Meer machines aanschaffen met een loadbalancer, die de lasten eerlijk verdeelt. Zo zou het eigenlijk moeten, maar zo'n configuratie is, als je die baseert op de drukke maanden, vreselijk duur. Het grootste deel van de tijd betaal je dan voor overcapaciteit.
  3. De services in de cloud hosten. Berekeningen tonen aan dat, als je voor gelijke capaciteit gaat, je net zo duur uit bent. Het grote voordeel is dat je die capaciteit niet altijd hoeft in te zetten. 's Nachts hoef je niet met de volle servercapaciteit te draaien. En wat je niet gebruikt hoef je ook niet te betalen. En daar zit hem nu de winst.
Windows Azure maakt gebruik van virtuele machines (VM's). Zo'n VM kun je zien als een enorm bestand dat zich gedraagt als een harde schijf, waarop ook nog eens een besturingssysteem is geïnstalleerd. Dit bestand wordt vervolgens "ergens" naar een machine gekopieërd en vervolgens opgestart. Je kunt kopieën maken van zo'n bestand om daarvan weer op een andere machine te booten. En zo heb je dan eigenlijk twee servers. Zo'n kopie van VM noemen we een instantie.
Windows Azure heeft een hele bibliotheek met VM's met de laatste Windows versies waaruit gekozen kan worden. Mocht er geen geschikte kandidaat bijzitten, dan heb je altijd nog de mogelijkheid om jouw eigen VM aan te leveren.
De hele handel is aan te sturen via de Azure Portal. Voor websites en services heb je twee opties (afbeelding 1)
  1. Web Sites
  2. Cloud Services
Afbeelding 1. Azure portal
Web Sites leveren een Windows OS met daarop Internet Information Services (IIS), de webserver. Hierop kun je jouw eigen websites draaien, maar ook vooraf gedefinieerde applicaties zoals Joomla, Wordpress en Drupal (zie afbeelding 2). Je kunt deze keuze zien als de traditionele hostingvorm, maar nu gevirtualiseerd. Hierop kun je jouw webapplicatie publiceren via ftp of WebDeploy.


Afbeelding 2. Web Sites opties.
Cloud Services lijken sterk op Web Sites, maar dit is echt, wat men noemt, een PaaS (Platform as a Service) oplossing. Het verschil met Web Sites zit hem in de volgende punten:
  1. Op Cloud Services heb je administratieve rechten. 
  2. Je kunt extra software installeren
  3. Aparte staging omgeving om je bouwsels te testen voordat je ze in de productie-omgeving zet.
  4. Beschikking over remote desktop
  5. Je kunt inhaken op jouw eigen on-premise servers.
In Cloud Services heb je de beschikking over twee soorten omgevingen, één met IIS (Web Role) en één zonder IIS (Worker Role). De laatste kun je goed vergelijken met Windows Services (processen die in de achtergrond draaien), het zijn de werkpaarden.

Er zijn twee manieren om jouw applicatie in de cloud te krijgen:
  1. Uploaden vanuit de Azure Portal
  2. Publishen vanuit Visual Studio.
Wanneer je kiest voor uploaden vanuit de Azure Portal, zul je eerst een Cloud Service moeten aanmaken. Dat gaat erg eenvoudig. Selecteer Cloud Services en klik op die enorme "+" linksonder in beeld of op de "Create a Cloud Service" (afbeelding 3). Bij URL geef je een unieke naam op. Bij "Region or Affinity Group" geef je het datacenter op waar je jouw VM's wilt laten draaien. Kies er een die zo dicht mogelijk bij jouw doelgroep zit. Hieronder een lijst met regions en hun fysieke locatie
  • North-central US (Chicago)
  • South-central US (San Antonio)
  • West US (California)
  • East US (Virginia)
  • East Asia (Hong Kong)
  • South East Asia (Singapore)
  • West Europe (Amsterdam)
  • North Europe (Dublin)
Afbeelding 3. Een nieuwe Cloud Service aanmaken is eenvoudig
Eenmaal aangemaakt, kom je op het overzicht van jouw Cloud Service. Helemaal onder in beeld zit een knop "Upload" waarmee je jouw applicatie kunt uploaden. Het leidt tot het scherm in afbeelding 4. Hier moet de naam van de release worden gegeven en een tweetal bestanden:
Het eerste bestand is een package waarin de hele applicatie verpakt is (.cspkg). Het tweede bestand is een configuratie file (.cscfg) waarin je aangeeft hoeveel instanties (kopieën van VM's) je wilt hebben en welk besturingssysteem moet worden genomen (osFamily). Je hebt de volgende getallen (1 t/m 3):
  1. Windows Server 2008 SP2
  2. Windows Server 2008 R2
  3. Windows Server 2012
Bij iedere family kun je weer een osVersion kiezen. Wanneer je de laatste versie wilt hebben specificeer je gewoon "*". Let ook op het vinkje onderin "Deploy even if one or more roles contain a single instance". Microsoft garandeert een uptime van 99.95%, maar wel op basis van ten minste twee instanties.

Afbeelding 4. Een package uploaden.
Om zo'n .cspkg en .cscfg file te maken kun je het beste de Azure SDK downloaden. Voor .NET sluit deze naadloos aan op Visual Studio 2010 of 2012. Naast Cloud Projecten bevat deze SDK ook een emulator, zodat je de applicatie eerst lokaal kunt testen en niet steeds minuten hoeft te wachten voordat hij in de cloud staat. Bovendien wordt het debuggen een stuk eenvoudiger (afbeelding 5).
Wanneer jouw cloudproject gereed is, klik je op het "Package..." menu en even later worden beide bestanden gegenereerd.

Afbeelding 5. De cloud emulator met een Cloud Service met vier instanties.
Je kunt jouw applicatie ook vanuit Visual Studio uitzetten. Dit gaat dan via Publish.... Hiervoor moet je wel eerst een een speciaal bestand met jouw credentials downloaden. Dit bestand importeer je dan weer in jouw Publish settings (afbeelding 6).


Afbeelding 6. Credentials ophalen en vervolgens importeren
Daarna moet je een storage locatie opgeven waar je package tijdelijk geparkeerd wordt (afbeelding 7) en kun je nog aangeven hoe je wilt uitleveren (afbeelding 8). Na een korte samenvatting kan het uitleveren beginnen (afbeelding 9).


Afbeelding 7. Storage aanmaken om package tijdelijk te parkeren
Afbeelding 8. Hoe gaan we publiceren (Production of Staging, Debug of Release)
Afbeelding 9. Liftoff!!
Wanneer de publicatie geslaagd is, kun je vanuit de Azure Portaal de actuele status van de applicatie bekijken (afbeelding 10). Hierin kun je zien hoe het CPU-gebruik verloopt. Natuurlijk is het ook mogelijk om andere performance indicatoren, zoals geheugengebruik en netwerkbelasting, te monitoren.


Afbeelding 10. Een uitgerolde Web Role.
In afbeelding 11 zie je het aantal instanties (iedere instantie kun je zien als een aparte server) met hun status. Wanneer er eentje corrupt is zie je dat hier meteen terug. Daarnaast kun je met een remote desktop verbinding (via de Connect knop helemaal onderin beeld) naar een specifieke instantie gaan (afbeelding 12). Dat moet je in de uitlevering dan wel aangeven, want standaard kan dat niet.


Afbeelding 11. Het instantie-overzicht van de Web Role.

Afbeelding 12. Een remote desktop verbinding naar een van de instanties.
Tenslotte kun je ook bepalen hoeveel instanties je wilt hebben (afbeelding 13). Met de schuifregelaar kun je dat afregelen. Het duurt nog geen vijf minuten om zo'n opschaling uit te voeren.


Afbeelding 13. Bepaal hier hoeveel instanties je wilt hebben.
Voor BesteProduct was de migratie vrij eenvoudig te realiseren omdat de WCF-services al in IIS gehost werden. Het enige dat hoefde te gebeuren, was een cloud project laten genereren in Visual Studio 2012 (in het contextmenu van het web project "Add Windows Azure Cloud Service Project" selecteren). Na publicatie en het omzetten van de adressen in de web site werkte het meteen. Nu nog logica schrijven om het op- en afschalen te automatiseren, maar daarover de volgende keer meer.

PS.
Wanneer je vanuit "Add Service Reference" in Visual Studio 2012 probeert een WCF-service, die in Azure draait, toe te voegen, kun je tegen de volgende fout aanlopen:

The request failed with HTTP status 400: Bad Request.
Metadata contains a reference that cannot be resolved: 'http://blabla.cloudapp.net//MyService.svc'.
Content Type application/soap+xml; charset=utf-8 was not supported by service

Probeer in dat geval eens het volgende adres:

http://blabla.cloudapp.net//MyService.svc?singleWsdl

Dat singleWsdl (nieuw in .NET 4.5) genereert alles in één wsdl-bestand (anders worden wsdl en xsd's in afzonderlijke bestanden gegenereerd). 
Dit werkte voor mij prima.

zondag 10 maart 2013

Connecting to Windows Azure SQL Database using Entity Framework

Het ontsluiten van een Windows Azure SQL Database

Wanneer jouw database in Windows Azure SQL Database staat, wil je er natuurlijk ook mee werken. .NET Framework biedt hiervoor ADO.NET aan, dat zit er al vanaf versie 1.0 in. Je zou dus zoiets kunnen doen als:

SqlConnection connection = new SqlConnection("de connectiestring");

connection.Open();
SqlCommand command =
               new SqlCommand("SELECT * FROM table", connection);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
    object a = reader[0];
    object b = reader[1];
    // etc.
}
connection.Dispose();

Die connectiestring zou er voor Windows Azure SQL Database als volgt uit kunnen zien:


Data Source=servernaam.database.windows.net;Initial Catalog=

MijnDatabase;user id=gebruiker;password=wachtwoord;

Dat gaat dus helemaal hetzelfde als met een on-premise SQL Server database.


Ofschoon deze manier van dataontsluiting nog altijd bruikbaar is, is het niet meer van deze tijd. Nadeel van deze benadering is dat gepeuter met SQL. Met eenvoudige SQL-queries valt het allemaal nog wel mee, maar als ze ingewikkeld worden kan het nog wel eens mis gaan. Daarbij komt dat de moderne ontwikkelaar object georiënteerd wil werken. Dit betekent dus dat het resultaat van de SQL-query in objecten gestopt moet worden, en daarvoor moeten weer classen gedefinieerd worden. En als het een beetje tegen zit moet je ook nog insert-, update- en deletequeries gaan bouwen. Dat is voor kleine databases even leuk, maar bij die grote draken gaat de lol er snel vanaf.


Vanaf .NET Framework 3.5 en Visual Studio 2008 (beide met Service Pack 1) zag de eerste versie van Entity Framework het licht. Dit was een tenenkrommende versie die, met name door de Hybernate gemeenschap, schuddebuikend werd ontvangen. De volgende versie was dan ook meteen 4 (gelijk met .NET Framework 4), spoedig gevolgd door versie 5. Op dit moment zit versie 6 eraan te komen waarvan een alfa-release verkrijgbaar is.


Om te werken met Entity Framework, voeg je aan jouw project een "ADO.NET Entity Data Model" toe. Meteen start het connectievenster op waarin je de Windows Azure SQL Database server opgeeft, de credentials en de database die je wilt gebruiken (afbeelding 1). Daarna start de wizard van Entity Framework (afbeelding 2).



Afbeelding 1. Connection Wizard
Afbeelding 2. De wizard van Entity Framework

Entity Framework wil weten hoe je jouw model wilt maken. Dat kan op de volgende manieren:

  1. Op basis van een bestaande database. Er worden dan classen (entities) voor jou gegenereerd
  2. Je begint met het definieren van entities waarna de database wordt gegenereerd.
Omdat we al een database hebben kiezen we voor optie 1. Daarna volgen nog wat vragen over hoe de connectiestring moet worden opgeslagen en tenslotte welke objecten je uit de database wilt hebben. In mijn geval neem ik alle tabellen mee. Onder water worden nu duizenden regels code gegenereerd die ik anders handmatig had moeten schrijven. Bovendien wordt er een visualisatie gemaakt van het model (afbeelding 3).

Afbeelding 3. Een wirwar van entities.

Voor de komst van Entity Framework 5 moesten we het met dit diagram doen. We konden inzoomen, we konden uitzoomen en vooral scrollen, veel scrollen.

Gelukkig kunnen we met Entity Framework 5 en Visual Studio 2012 al die entities over meerdere diagrammen uitsmeren. Bovendien kunnen we de entities ook nog andere kleurtjes geven hetgeen leidt tot het resultaat in afbeelding 4.


Afbeelding 4. Diagram met een fragment van de entities. De kleuren geven in aan bij welk schema ze horen.

De data opvragen verloopt via een speciaal object, de context. Een query zou er nu als volgt uit kunnen zien:


using (IntermedEntities context = new IntermedEntities())

{
    var query = context.Products
        .Include(p=>p.Brand)
        .OrderBy(rec => rec.ID)
        .Skip(200)
        .Take(10);

    foreach (Product p in query)

    {
        Console.WriteLine("{0} {1}", p.Brand.Name, p.Name);
    }
}

IntermedEntities is zo'n gegenereerde class. Hij representeert de context en is verantwoordelijk voor de verbinding met de database, houdt bij wat er gewijzigd is en levert een aantal voorgedefinieerde queries aan (context.Products).

Een nieuw record toevoegen gaat als volgt:


Product p = new Product
{
    Name = "Halfvolle melk",
    Brand = new Brand { Name = "Zuivelbedrijf" }
};
using (IntermedEntities context = new IntermedEntities())
{
    context.Products.Add(p);
    context.SaveChanges();
}

De SaveChanges() zorgt ervoor dat de wijzigingen naar de database worden weggeschreven.

Code-First

Tegenwoordig is de Code-First benadering helemaal hot. Het komt erop neer dat je code begint te kloppen en als je het programma een keer draait, wordt er op magische wijze een database aangemaakt. Dat trucje wil ik wel eens uitproberen, en natuurlijk wil ik dat die database in de cloud wordt gegenereerd.
Om te beginnen smijten we er eerst maar eens een lap code tegenaan.


class Program
{
    static void Main(string[] args)
    {
        Person p = new Person
        {
             FirstName = "Patrick",
             LastName = "Schmidt" 
        };
        Hobby h = new Hobby        {
             Description = "Computers"
      
 };
        p.Hobbies.Add(h);

        using (HobbyModel context = new HobbyModel())
        {
            context.People.Add(p);
            context.SaveChanges();
            foreach(Person person in context.People)
            {
                Console.WriteLine(person.LastName);
                foreach (Hobby hobby in person.Hobbies)
                {
                    Console.WriteLine(hobby.Description);
                }
            }
        }
        Console.ReadLine();
    }
}

public class HobbyModel : DbContext
{
    public DbSet<Person> People { getset; }
    public DbSet<Hobby> Hobbies { getset; }

    public HobbyModel()
        : base("DataContext")
    { }
}

public class Person
{
    public int ID { getset; }
    public string FirstName { getset; }
    public string LastName { getset; }
    public virtual ICollection<Hobby> Hobbies { getset; }
    public Person()
    {
        Hobbies = new HashSet<Hobby>();
    }
}

public class Hobby
{
    public int ID { getset; }
    public string Description { getset; }
    public virtual ICollection<Person> People { getset; }
    public Hobby()
    {
        People = new HashSet<Person>();
    }
}

Hier zijn drie classen gedefinieerd; Person, Hobby en HobbyModel. De eerste twee zijn entities en laatste is verantwoordelijk voor de verbinding met de database en houdt de wijzigingen van de entities in de gaten. In de "Main" functie worden eerst instanties van Person en Hobby gemaakt die vervolgens met elkaar in verband worden gebracht. Daarna wordt de context (HobbyModel) geïnitialiseerd. Via context.Persons.Add() worden de nieuwe objecten in de context geregistreerd en wanneer de SaveChanges() wordt aangeroepen, moet de hele zaak in de database verdwijnen (die nog altijd niet bestaat).
Wanneer we dit programma starten, zal na de aanroep van SaveChanges() de database worden aangemaakt. De vraag is alleen waar?
Standaard zal dat een lokale SqlExpress versie van SQL Server zijn. Wanneer die niet te vinden is, zal LocalDb (VS2012) gebruikt worden. Wij hebben in de constructor van HobbyModel echter "DataContext" gespecificeerd. Dat is een verwijzing naar een connectionstring in de configuratiefile en daar staat:

<connectionStrings>
   <add name="DataContext"
             connectionString="Data Source=servernaam.database.windows.net;
                 Initial Catalog=MyDataBase;
                 user id=username;
                 password=password;"
             providerName="System.Data.SqlClient" />
</connectionStrings>

Daar wordt nu verwezen (Data Source) naar een Windows Azure SQL Database server, zodat de database nu in de cloud wordt aangemaakt (afbeelding 5). 



Afbeelding 5. In de cloud.
We hebben nu gezien hoe we Entity Framework kunnen gebruiken met Windows Azure SQL Database. Daarnaast is bekeken of Code-first ook werkt op Windows Azure SQL Database. Het maakt nauwelijks verschil met een lokale database.

Entity Framework 6

Binnenkort komt Entity Framework 6 uit. Op dit moment is een alpha-release te bewonderen. Hier volgt een korte beschrijving van een paar functionaliteiten.


1) Async Query and Save. Queries en save acties kunnen asynchroon worden uitgevoerd. Let wel op dat dergelijke operaties niet thread safe zijn. Hier een asynchrone uitwerking van het eerdere voorbeeld waarbij gebruik wordt gemaakt van SaveChangesAsync() en de extensie ForEachAsync()



class Program
{
    static void Main(string[] args)
    {
        AsyncTask();
        Console.ReadLine();
    }
    private static async void AsyncTask()
   {         
        Person
 p = new Person{FirstName = "Patrick", LastName = "Schmidt" };
        Hobby h = new Hobby { Description = "Computers" };
        p.Hobbies.Add(h);
        await
 CreateAsync(p);
        await
 ShowAsync();
    }
    private static async Task ShowAsync()
    {
        using
 (HobbyModel context = new HobbyModel())
        {             
            await
 context.People.ForEachAsync(person =>
                      {
                          Console
.WriteLine(person.LastName);
                          foreach
 (Hobby hobby in person.Hobbies)
                          {
                              Console
.WriteLine(hobby.Description);
                          }
                      });
        }
    }
    public static async Task CreateAsync(Person p)
    {
        using (HobbyModel context = new HobbyModel())
        {
            context.People.Add(p);
            await context.SaveChangesAsync();
        }
    }
}

2) Custom Code First Conventions. Hiermee hebben we controle over hoe de database wordt gegenereerd bij gebruik van Code-first. Bijvoorbeeld, in het eerdere voorbeeld werden alle stringproperties vertaald naar nvachar(max). Met CCFC kun je dat gedrag aanpassen. Hieronder zie je hoe dat gaat. De class StringTypeConvention wordt in de OnModelCreating geïnjecteerd.

public class StringTypeConvention : IConfigurationConvention<PropertyInfo
                                                              StringPropertyConfiguration>
{     
    public
 void Apply(PropertyInfo memberInfo, 
                            Func
<StringPropertyConfiguration> configuration)
    {
        configuration().MaxLength = 1024;
    }
}
public class HobbyModel : DbContext
{     
    public
 DbSet<Person> People { getset; }
    public DbSet<Hobby> Hobbies { getset; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Add(new StringTypeConvention());
    }
    public HobbyModel() : base("DataContext")
    {
    }
}

3) Code First Mapping to Insert/Update/Delete Stored Procedures. Het is nu ook mogelijk om de mapping van inserts, updates en deletes via stored procedures te laten verlopen. Helaas worden de stored procedures zelf nog niet gegenereerd. Het volgende stukje code laat zien hoe dat gaat. Let op dat de namen en argumenten onderhevig zijn aan naamconventies. Natuurlijk heb je daar ook weer invloed op.

protected override void OnModelCreating(DbModelBuilder< modelBuilder)
{
    modelBuilder.Entity<Hobby>().MapToStoredProcedures();
    modelBuilder.Entity<Person>().MapToStoredProcedures();
}

4) Connection Resiliency. Met de groeiende populariteit van Windows Azure SQL Database, waarbij het netwerk minder betrouwbaar is, is ook de vraag naar een goede afhandelingen bij netwerkproblemen toegenomen. Je kunt hierbij denken aan retry-acties. In EF 6 is het mogelijk om logica te injecteren die op dergelijke verbindingsproblemen anticipeert. Aan de grondslag hiervan liggen een drietal interfaces; IExecutionStrategy, IRetriableExceptionDetector en IRetryDelayStrategy die je moet implementeren.
Voor Windows Azure SQL Database is al een kant-en-klare strategie bijgevoegd (SqlAzureExecutionStrategy in System.Data.Entity.SqlServer). In zijn meest eenvoudige vorm voeg je een class, die erft van DbConfiguration, toe aan dezelfde assembly als waarin jouw DbContext gedefinieerd is. Via Dependency Resolution (ook nieuw in EF 6) wordt MyDbConfiguration ingeladen (SqlAzureDbConfiguration erft van DbConfiguration).

public class MyDbConfiguration : SqlAzureDbConfiguration
{
    public MyDbConfiguration()
    {
        AddDependencyResolver(
            new SqlExecutionStrategyResolver(
                   () => new SqlAzureExecutionStrategy(), null));
    }
}
public class HobbyModel : DbContext
{
    public DbSet<Person> People { getset; }
    public DbSet<Hobby> Hobbies { getset; }
    public HobbyModel() : base("DataContext")
    { }
}

Natuurlijk kun je ook jouw eigen strategie implementeren.
Voor meer nieuws, zie de wiki pagina voor Entity Framework 6