Archivo por meses: enero 2013

Cómo respaldar Azure SQL Database usando un Worker Role

Trabajando con una empresa que tiene un ERP de nicho, quieren mover su operación a Azure. Dentro de los temas que importantes a tener en cuenta apareció el tema del cumplimiento de normativas que les obligan a tener respaldos y manejarlos siguiendo una política.

En ese sentido, el resapldo en Azure SQL Database es diferente a lo que ofrece SQL Server. Puede verse un detalle de esto en http://msdn.microsoft.com/en-us/library/jj650016.aspx. En Azure SQL Database podemos realizar un respaldo lógico de la siguiente manera.

El Worker Role hace una copia de la base de datos en Azure Sql Database, esta se utilizará para hacer el export. No se hace directamente de la base de datos original porque el export no garantiza consistencia ya que va exportando los datos tabla a tabla y si se hace un cambio mientras el proceso está corriendo podría quedar inconsistente. La Copia si asegura consistencia. Luego de copiar la db sobre este duplicado se hace el export. Una vez terminado el proceso entonces se borra la copia de la db. El proceso se muestra en el siguiente diagrama de secuencia.

Escenario

En este artículo vamos a desarrollar un ejemplo de un servicio que realiza el export de una base de datos siguiendo el proceso antes descrito.

Las ventajas de desarrollarlo de esta forma es que podemos dejarlo corriendo de manera programada y así asegurarnos de cumplir con la política. Además, que el Export se hace en la nube por lo cual no hay costos de bajar la data a nuestro datacenter u otro lugar.

Prerrequisitos

Los prerrequisitos para poder desarrollar esta solución son:

  1. Tener instalado Windows Azure SDK for .NET
  2. Tener los instaladores de los componentes del Microsoft Data-tier Application Framework. Se pueden obtener desde
    1. http://www.microsoft.com/en-us/download/details.aspx?id=24000
    2. http://www.microsoft.com/en-us/download/details.aspx?id=35756
  3. Tener creada una base de datos en Azure Sql Database

Pasos para construir el proyecto

Los pasos a seguir para construir el proyecto de ejemplo son los siguientes

  1. Crear el proyecto Cloud
  2. Agregar Local Storage
  3. Agregar cadena de conexión con nuestro storage
  4. Configurar las variables necesarias para el export
  5. Agregar las referencias a utilizar para CloudDirve
  6. Crear una clase Helper para manejar CloudDrive
  7. Probar el funcionamiento del disco CloudDrive
  8. Agregar la referencia necesaria para Data-tier Application Framework (DAC)
  9. Crear una clase Helper para manejar DAC
  10. Crear método para copiar la DB
  11. Crear el método para borrar la copia de la DB
  12. Crear los manejadores de eventos y configuración de la traza
  13. Invocar el proceso de copia, Export y borrado en el método Run()
  14. Probar el proceso completo en el Emulador
  15. Configurar la instalación de DAC Framework
  16. Probar el Deploy en Azure

Paso 1: Crear el proyecto Cloud

En visual studio 2012 se crea un proyecto cloud, con un Worker Role. La siguiente figura ilustra el proyecto a crear.

Paso 2: Agregar Local Storage

Los CloudDrive son discos duros virtuales que se pueden montar en las máquinas de los Cloud Services. En nuestro caso vamos utilizar un CloudDrive para guardar ahí los archivos export que sacaremos desde la base de datos. CloudDrive es un concepto importante, y pueden leer un buen documento en el siguiente link

Para poder utilizar CloudDrive debemos agregar Local Storage a la definición del servicio, esto lo hacemos en la configuración del Worker Role como se muestra en la siguiente imagen.

Paso 3: Agregar cadena de conexión con nuestro storage

El CloudDrive se alancena en un Blob Storage. Para poder conectarnos a ese Blob Storage necesitamos una cade de conexiones, que declaramos en la configuración del servicio como aparece en la siguiente figura. Yo estoy configurando un Storage en la nube, pero se puede utilizar el de desarrollo de igual manera mientas se desarrolla.

Paso 4: Configurar las variables necesarias para el export

Las variables siguientes deben estar definidas para realizar el export:

  1. miStoragevhd: cadena de conexión con el storage donde se almacena el CloudDrive. Es muy importante que el Storage que se use en Azure esté en el mismo Datacenter que el Worker Role. Mejor si están en el mismo grupo de afinidad. Otro punto importante es que se debe utilizar en esta conexión HTTP no HTTPS. Por ejemplo:

    DefaultEndpointsProtocol=http;AccountName=jpgarciaexportdb;AccountKey=xxxxxxxxxx==

  2. NombreBaseDatos: nombre de la base de datos a exportar
  3. CadenaConexionDb: cadena de conexión para la base de datos, reemplazando el nombre de la db por {0}, ejemplo

    Server=tcp:abc.database.windows.net,1433;Database={0};User ID=SQLDATASERVER@e4rvhdsl4i;Password=P@ssw0rd;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;

La siguiente figura muestra las variables de configuración

Paso 5: Agregar las referencias a utilizar para CloudDrive

Para poder utilizar CloudDriver necesitamos agregar referencias a ODataLib 5.0.2 y Storage Client. Para agregar ODataLib en la versión 5.0.2 podemos utilizar Package Manager Console con la siguiente línea de comandos

PM> Install-Package Microsoft.Data.OData -Version 5.0.2

Ahora, debemos instalar las librearías de Windows Azure Store, utilizando para ellos NuGet Packages como se muestra en la siguiente pantalla.

Para poder acceder a al storage debemos agregar la referencia Microsoft.WindowsAzure.StorageClient.dll.

Por ultimo agregamos la referencia a Microsoft.WindowsAzure.CloudDrive.dll , para poder utilizar CloudDrive.

Paso 6: Crear una clase Helper para manejar CloudDrive

Para simplificar el desarrollo vamos a crear una clase que nos ayude a manejar todos los aspectos relacionados a CloudDirve. Esa clase se llamará CloudDriveHelp.

Esta clase tiene dos métodos, el primero para iniciar el disco. Para ello se debe definir cuál Storage se almacena en disco virtual. En este método se monta el disco virtual y se obtiene la letra del drive. Esta letra se almacena en la propiedad azureDrivePath de la clase CloudDriveHelp. myCloudDrive también es una propiedad de la clase CloudDriveHelp porque es necesario que su alcance sea general para así poder utilizarla para desmontar el dirve al finalizar el ciclo de vida del Worker Role.

Este método se llama solo una vez al iniciar el Worker Role y lo que hace es montar (literalmente) el disco virtual (VHD) en la máquina virtual que corre nuestro Worker Role.

public void IniciarDisco()
{
    CloudStorageAccount storageAccount;
    //1. Storage a Utilizar
    if (RoleEnvironment.IsEmulated)
    {
        storageAccount = CloudStorageAccount.DevelopmentStorageAccount;
    }
    else
    {
        storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("miStoragevhd"));
    }
    //2. Referencia al disco de cache local
    LocalResource localCache = RoleEnvironment.GetLocalResource("midisco");
    //3. Inicializa el disco de cache
    CloudDrive.InitializeCache(localCache.RootPathlocalCache.MaximumSizeInMegabytes);
    //4. Validar que exista Drives 
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    blobClient.GetContainerReference("drives").CreateIfNotExist();
    //5. Crear el cloudDrive 
    myCloudDrive =
        storageAccount.CreateCloudDrive(blobClient.GetContainerReference("drives").
            GetPageBlobReference("myexportdrive.vhd").Uri.ToString());
    try
    {
        // 6. Crearlo si no existe, ojo que este drive es persistente
        myCloudDrive.CreateIfNotExist(localCache.MaximumSizeInMegabytes);
    }
    catch (CloudDriveException ex)
    {
        error = ex.Message;

    }
    //6. Aqui se obtiene la letra del Drive
    azureDrivePath = myCloudDrive.Mount(25DriveMountOptions.Force);
}

El método IniciarDisco() se llama desde el método OnStrat() del Worker Role, al iniciar el role se inicia el disco.

public override bool OnStart()
 {
    ConfigurarTraza();
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;
    //1. Crea el objeto myDrive
    myDrive = new CloudDriveHelp();
    //2. Inicia el Drive y lo monta
    myDrive.IniciarDisco();
    return base.OnStart();
 }

El método FinalizarDisco() de la clase CloudDriveHelp simplemente desmonta el drive de la VM donde se está ejecutando el role.

public override bool OnStart()
 {
    ConfigurarTraza();
    // Set the maximum number of concurrent connections 
    ServicePointManager.DefaultConnectionLimit = 12;
    //1. Crea el objeto myDrive
    myDrive = new CloudDriveHelp();
    //2. Inicia el Drive y lo monta
    myDrive.IniciarDisco();
    return base.OnStart();
 }

Este método se llama cuando el Worker Role se detiene, es decir desde el método OnStop() como muestra el siguiente código

public override void OnStop()
        {
            myDrive.FinalizarDisco();
        }

Paso 7: Probar el funcionamiento del disco CloudDrive

Para validar que nuestro disco este funcionado correctamente vamos a ejecutar el proyecto en modo debug e inspeccionar el disco. Esto lo haremos en el emulador, para simplificar. Una vez ejecutado el proyecto y que el Worker Role ya se inicie vamos al Storage Emulator y utilizando el menú abrimos la carpeta donde se almacenan los Azure Drive.

Ahora, vamos a la carpeta Drives y vemos que se ha creado nuestro disco myexportdrive.vhd como se muestra en la siguiente imagen.

Paso 8: Agregar la referencia necesaria para Data-tier Application Framework (DAC)

Ahora vamos a utilizar DAC para acceder a Azure Sql Database y hacer un export de una base de datos. Para ello debemos agregar la referencia a Microsoft.SqlServer.Dac.dll. Esta DLL es parte de los prerrequisitos que aparecen al inicio del artículo.

Paso 9: Crear una clase Helper para manejar DAC

Para simplificar el uso de DAC vamos a utilizar la clase DACHelp. El método principal que implementamos es Exportar() que realiza un export de la base de datos a un archivo. La idea es que ese archivo se almacene en el CloudDrive.

El argumento Mensajes y Progreso son referencia a manejadores de eventos, que reciben información del progreso y mensajes de la tarea de exportación.

 public bool Exportar(string sourceDatabaseNamestring targetConnectionStringstring fullNombreArchivo, 
        EventHandler<DacMessageEventArgs> MensajesEventHandler<DacProgressEventArgs> Progreso)
    {
        try
        {
            DacServices svc;
            //1. Crear el servicio DAC con la cadena de conexión
            svc = new DacServices(targetConnectionString);
            if (Mensajes != null)
            {
                //a. asignar los métodos de manejo de eventos de mensajes y Progreso
                svc.Message += new EventHandler<DacMessageEventArgs>(Mensajes);
                svc.ProgressChanged += new EventHandler<DacProgressEventArgs>(Progreso);
            }
            //2. Ejecutar el Export de la base de datos a un archivo
            svc.ExportBacpac(fullNombreArchivosourceDatabaseName);

        }
        catch (DacServicesException )
        {

            throw;
        }
        //TODO: dar mejor info del proceso
        return true;
    }

Paso 10: Crear método para copiar la DB

Para poder copiar la base de datos vamos a utilizar una instrucción SQL que nos permite crear una base de datos como una copia de una existente. Este comando se debe ejecutar en la base de datos MASTER. El comando SQL a ejecutar es:

CREATE DATABASE {nombreCopia} AS COPY OF {nombreDB}

Una vez ejecutado el comando de copia debemos esperar que la copia esté lista antes de continuar con el proceso de export. Para saber el estado de la copia ejecutamos el comando SQL

select SD.state_desc AS [Current State] from sys.databases SD

LEFT JOIN sys.dm_database_copies DDC ON DDC.database_id = SD.database_id

WHERE SD.name = ‘nombreCopia’

La idea es repetir esta consulta hasta obtener como respuesta «ONLINE» que significa que la copia terminó y podemos utilizarla para exportar el esquema y los datos. El tiempo que se espera, medido e segundos, se almacena en la variable segundos. El valor se lee desde el archivo de configuración app.config, de la llave RespaldarCada[Minutos]. Lo coloqué ahí para poder cambiarlo libremente sin tener que hacer update de la configuración del ROL con propósitos de pruebas. Las llaves que se agregarón al app.config se muestran a continuación.

<appSettings>
    <add key="switchvalue ="1"/>
    <add key="transferTimevalue ="5"/>
    <add key="snapshotPoolingSegundosvalue ="30"/>
    <add key="RespaldarCada[Minutos]value ="60"/>
  </appSettings>

El método Copiardb() es el siguiente.

public string  CopiarDb(string sourceDatabaseNamestring MasterConnectionString)
{
    string  dbCopyName;
    bool copySw = true;
    string currentState;
    string comando;
    Random random = new Random();
    int randomNumber = random.Next(0100);
    try
    {
        //1. Nombre de la DB copia
        dbCopyName = string.Format("{0}_Copy_{1}"sourceDatabaseNamerandomNumber.ToString());

        using (SqlConnection con = new SqlConnection(MasterConnectionString))
        {
            //2. Comando crear base de datos como copia de otra
            comando = string.Format("CREATE DATABASE {0} AS COPY OF {1}",dbCopyNamesourceDatabaseName);
            con.Open();
            using (SqlCommand command = new SqlCommand(comandocon))
            {
                //4.Ejecutar el comando de copia
                command.ExecuteNonQuery();
            }
            Trace.WriteLine("Copiando base de datos:" + dbCopyName"Snapshot");
            do
            {
                //3. comando para consultar el estado de la copia, ya que toma tiempo
                comando = 
                    string.Format(
                        "select SD.state_desc AS [Current State] from sys.databases SD LEFT JOIN sys.dm_database_copies DDC " + 
                        " ON DDC.database_id = SD.database_id WHERE SD.name = '{0}'"dbCopyName);
                using (SqlCommand command = new SqlCommand(comandocon))
                {
                    //4. ejecutar la consulta del estado de la copia
                    currentState = command.ExecuteScalar().ToString();
                    Trace.WriteLine(currentState + ": " + dbCopyName"Snapshot");
                }
                if (currentState == "ONLINE")
                {
                    //La copia esta lista
                    copySw = false;
                }
                if (currentState == "SUSPECT")
                {
                    //la copia fallo
                    throw new Exception("The 'CREATE DATABASE AS COPY OF' operation failed");
                }
                //Esperar antes de volver a preguntar por el estado de la copia
                int segundos = int.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("snapshotPoolingSegundos"));
                System.Threading.Thread.Sleep(segundos*1000);
            } while (copySw);
        }

    }
    catch (Exception X)
    {
        Trace.WriteLine("Error: " + X.Message,"Snapshot");
        throw;
    }
    return dbCopyName;
} 

Paso 11: Crear el método para borrar la copia de la DB

Borrar una base de datos es simple utlizando una sentencia SQL estando conectado a la base de datos Master. Sólo se ejecuta

DROP DATABASE [NombreBaseDatos]

El método implementado es el siguiente

public void BorrarDB(string dbNamestring MasterConnectionString)
    {
        try
        {
            using (SqlConnection con = new SqlConnection(MasterConnectionString))
            {
                string comando = string.Format("DROP DATABASE {0}"dbName);
                con.Open();
                using (SqlCommand command = new SqlCommand(comandocon))
                {
                    command.ExecuteNonQuery();
                }
            }
        }
        catch (Exception x)
        {
            Trace.WriteLine(x.Message,"Borrar DB error:");
        }
    }

Paso 12: Crear los manejadores de eventos y configuración de la traza

Para poder recibir y mostrar el estado de avance, así como los mensajes del proceso de Export vamos a crear dos métodos que escriben esos detalles en la traza. Los métodos son los siguientes y se crean en el Worker Role.

public void receiveDacServiceMessageEvent(object senderDacMessageEventArgs e)
{
    Trace.WriteLine(string.Format("Message Type:{0} Prefix:{1} Number:{2} Message:{3}", 
        e.Message.MessageTypee.Message.Prefixe.Message.Numbere.Message.Message));
}
public void receiveDacServiceProgessEvent(object senderDacProgressEventArgs e)
{
    Trace.WriteLine(string.Format("Progress Event:{0} Progrss Status:{1}"e.Messagee.Status));
}

Cuando trabajamos en el emulador, toda la traza aparece en la consola del emulador. Ahora, cuando estamos en Azure y queremos ver la traza debemos configurar dónde vamos a almacenar la información de la traza. En este caso utilizaremos una tabla en el store. Para ello debemos agregar lo siguiente en el app.config

<system.diagnostics>
        <trace>
            <listeners>
                <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.8.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
                    name="AzureDiagnostics">
                    <filter type="" />
                </add>
            </listeners>
        </trace>
    </system.diagnostics>

Luego en el método OnStrat() llamar al método ConfigurarTraza() que se hace cargo de configurar la conexión a la tabla y el tiempo de transferencia de los datos. El método es el siguiente.

private void ConfigurarTraza()
{
    string wadConnectionString = "Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString";
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(
                                            RoleEnvironment.GetConfigurationSettingValue(wadConnectionString));
    RoleInstanceDiagnosticManager roleInstanceDiagnosticManager = 
                                        storageAccount.CreateRoleInstanceDiagnosticManager(
                                                RoleEnvironment.DeploymentIdRoleEnvironment.CurrentRoleInstance.Role.Name, 
                                                RoleEnvironment.CurrentRoleInstance.Id);
    DiagnosticMonitorConfiguration config = roleInstanceDiagnosticManager.GetCurrentConfiguration();
    if (config == null)
    {
        config = DiagnosticMonitor.GetDefaultInitialConfiguration();
    }
    //Configura cada cuanto tiempo se envía la información a la tabla
    double transferTime = double.Parse(System.Configuration.ConfigurationManager.AppSettings.Get("transferTime"));
    config.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(transferTime);
    config.Logs.ScheduledTransferLogLevelFilter = LogLevel.Verbose;
    roleInstanceDiagnosticManager.SetCurrentConfiguration(config);
}

La configuración que utiliza para conectarse a la tabla se define en la configuración del Web Role.

Paso 13: Invocar el proceso de copia, Export y borrado en el método Run()

El método Run() del Worker Role implementa el proceso de copia, exportación y borrado de la base de datos. Esto se realiza casa cierto tiempo definido en minutos en la variable de configuración «RespaldarCada[Minutos]«. El proceso es controlado por la variable de configuración «switch» del app.config. La idea es que si esa llave tiene el valor 0 no se realiza el proceso. Esto está pensando en poder detener los export sin necesidad de bajar el Worker Role.

Al iniciar el proceso se captura la hora de inicio para poder calcular el tiempo que toma el proceso. Luego se obtiene desde la configuración del role el nombre de la base de datos, la cadena de conexión a la db a exportar y a la Master. Con estos datos comienza el proceso con el primer paso que es copiar la base de datos. Para ello se invoca el método privado CopiarDb() que retorna el nombre de la copia.

private string CopiarDB(string NombreBaseDatosstring CadenaConexionDbMaster)
{
    string respuesta = null;
    //1. Crer el Helper
    DACHelp myDAC = new DACHelp();
    try
    {
        DateTime tiempoInicio = System.DateTime.Now;
        respuesta = myDAC.CopiarDb(NombreBaseDatosCadenaConexionDbMaster);
        Trace.WriteLine("Tiempo Copia: " + DateTime.Now.Subtract(tiempoInicio).ToString(), "Copia DB");
    }
    catch (Exception x)
    {
        Trace.WriteLine("************************************************************");
        Trace.WriteLine(x.Message"Error Copia DB:");
        Trace.WriteLine("************************************************************");
    }

    return respuesta;
}

Si la copia es exitosa, esto lo sabe porque CopiarDB() entrega el nombre de la base de datos copiada, entonces se llama el método privado exportar() que también utiliza DACHelp. Este es el encargado de formar el nombre del archivo donde se guarda el export, agregándole al nombre la fecha y hora en que se hizo. Un punto importante de notar en el nombre del archivo es como se obtiene la ruta del mismo utilizando myDrive.azureDrivePath. Cuando se monta un disco se le asigna una letra, pero no se asegura que siempre sea la misma por lo cual es importante recuperar la letra asignada para confeccionar el nombre completo del archivo.

También, al invocar el Exportar indica cuales son los manejadores de eventos que reciben la información del proceso y sus mensajes.

private void exportar(string NombreBaseDatosstring targetConnectionStringstring idRespaldo)
{
    //1. Crer el Helper
    DACHelp myDAC = new DACHelp();
    //2. Arma el nombre del archivo para el BACPAC
    string fullNombreArchivo = string.Format("{0}{1}_{2}.BACPAC "myDrive.azureDrivePathNombreBaseDatosidRespaldo);
    try
    {
        //3. Ejecuta el Exportar del Helper
        myDAC.Exportar(NombreBaseDatostargetConnectionStringfullNombreArchivothis.receiveDacServiceMessageEventthis.receiveDacServiceProgessEvent);
    }
    catch (Exception x)
    {
        Trace.WriteLine("************************************************************");
        Trace.WriteLine(x.Message,"Error Copiar DB:");
        Trace.WriteLine("************************************************************");
    }  
}

Una vez terminado el export, se manda a la traza el tiempo que se demoró el proceso y se listan los respaldos que se encuentran en el cloudDrive.

Por último, se llama al método privado BorrarTemp() para que borre la copia de la DB. Este también es un método simple porque utiliza DACHelp.

private void BorrarDBTemp(string nombreDbTmpstring CadenaConexionDbMaster)
{
    //1. Crer el Helper
    DACHelp myDAC = new DACHelp();
    //2. Arma el nombre del archivo para el BACPAC
    try
    {
        //3. Ejecuta el Exportar del Helper
        myDAC.BorrarDB(nombreDbTmpCadenaConexionDbMaster);
    }
    catch (Exception x)
    {
        Trace.WriteLine("************************************************************");
        Trace.WriteLine(x.Message"Error Borrar DB TMP:");
        Trace.WriteLine("************************************************************");
    }
}

Por último el método Run() del Worker Role espera el tiempo definido en minutos en la variable de configuración RespaldarCada[Minutos] para iniciar nuevamente el proceso completo.

Paso 14: Probar el proceso completo en el Emulador

Ahora estamos listos para probar el proyecto apuntando a una DB en Azure y utilizando el Storage del emulador. Si se ejecuta el proyecto, en la consola del emulador se puede ver el progreso.

Como primer paso aparece el mensaje de copiando la base de datos AdventureWorks2012 a AdventureWorks2012_Copy_94. 94 es un número que se genera aleatoriamente para evitar que se repitan los nombres de la bases de datos de copia. EL mensaje en la consola es:

Snapshot: COPYING: AdventureWorks2012_Copy_94

Cuando termina la copia de la base de datos aparece el mensaje:

Copia DB: Tiempo Copia: 00:05:37.8043059

Ahora comienza el proceso de exportación. En la consola se pueden ver información del proceso y los mensajes de DAC. Por ejemplo el evento cuando completa el export de la tabla : [Sales].[CurrencyRate] es el siguiente:

Progress Event:Processing table: [Sales].[CurrencyRate] Progrss Status:Completed

Por otra parte, un ejemplo de un mensaje donde muestra la cantidad de registros exportados de la tabla [Sales].[PersonCreditCard] sería:

Message Type:Message Prefix:SQL Number:73152 Message:Table [Sales].[PersonCreditCard] has exported 19118 rows.

Yo estoy utilizando la base de datos AdventureWorks2012 que descargue desde el siguiente link.

http://msftdbprodsamples.codeplex.com/releases/view/55330

Las propiedades de la base de datos que me muestra el portal de administración se muestran en la Siguiente imagen.

El tamaño de la base de datos es de 141.51 MB y en mi prueba tomó 5 minutos con 37 segundo las copia de la base de datos. La exportación del esquema y los datos tomó 9 minutos con treinta y dos segundos. Cuando el proceso termina nos entrega la siguiente información:

Progress Event:Exporting data from database Progrss Status:Completed

Information: Fin Respaldo

Information: Tiempo Respaldo: 00:09:32.6295793

info respaldo:: The directory a:\ contains the following files:

info respaldo:: The size of AdventureWorks2012_2013-01-10_07-15-02-PM.BACPAC is 17681362 bytes.

Information: proximo respaldo:3600000

Ahora, revisando el disco virtual en el Storage Emulator podemos ver que el archivo pesa 17 MB.

Paso 14: Configurar la instalación de DAC Framework

Una vez probado en el ambiente local podemos preparar el proyecto para el Deploy en Azure. Cuando se ejecuta en Azure el Worker Role, es necesario instalar los componentes de Data-Tier Application Framework de la versión 2008 R2 y de la versión 2012. Estos podemos encontrarlos respectivamente en los siguientes links:

http://www.microsoft.com/en-us/download/details.aspx?id=35756

http://www.microsoft.com/en-us/download/details.aspx?id=24000

Los DAC en la versión 2012 requiere instalar los MSI de x86 y x64 para correr en los sistemas operativos de 64 bits.

Para instalar estos componentes vamos a agregar los archivos MSI al proyecto para poder instalarlos antes de ejecutar el ROL. La siguiente imagen muestra los archivos ya incorporados al proyecto.

Una vez agregados, vamos a sus propiedades y configuramos Copy to Output Directory con el valor Copy always. Esto es necesario para que cuando se hace deploy en la VM del Worker Role, se copien los instaladores.

Ahora agregamos un archivo llamado intallDAC.cmd al proyecto. Este archivo para asegurarnos que se ejecuta demos grabarlo con la codificación Western European (Windows) codepage 1252. Esto se hace en la opción Adavanced Save Options. En este cmd también vamos a sus propiedades y configuramos Copy to Output Directory con el valor Copy always.

El archivo CMD lo primero que hace es discriminar si se está corriendo en el emulador o no. Si se corre en el emulador no instala los MSI. Esto porque en mi maquina ya tengo todo instalado y no es necesario.

REM   Check if this task is running on the compute emulator.
IF "%ComputeEmulatorRunning%" == "true" (
    REM   This task is running on the compute emulator. Perform tasks that must be run only in the compute emulator.
) ELSE (
		ECHO This task is running on the cloud. Perform tasks that must be run only in the cloud.

	 IF EXIST "MSI\DAC2012x64\log_DACFramework.txt" (

		ECHO Ya se instalaron los MSI. 

	) 	ELSE 	(
		ECHO \DAC2012x64
		MSI\DAC2012x64\DACFramework.msi  /qn /l* MSI\DAC2012x64\log_DACFramework.txt
		MSI\DAC2012x64\SQLSysClrTypes.msi  /qn /l* MSI\DAC2012x64\log_SQLSysClrTypes.txt
		MSI\DAC2012x64\SqlDom.msi  /qn /l* MSI\DAC2012x64\log_SqlDom.txt

		ECHO \DAC2012x86
		MSI\DAC2012x86\DACFramework.msi  /qn /l* MSI\DAC2012x86\log_DACFramework.txt
		MSI\DAC2012x86\SQLSysClrTypes.msi  /qn /l* MSI\DAC2012x86\log_SQLSysClrTypes.txt
		MSI\DAC2012x86\SqlDom.msi  /qn /l* MSI\DAC2012x86\log_SqlDom.txt

		ECHO \DAC2008R2
		MSI\DAC2008R2\DACFramework.msi  /qn /l* MSI\DAC2008R2\log_DACFramework.txt
		MSI\DAC2008R2\SQLSysClrTypes.msi  /qn /l* MSI\DAC2008R2\log_SQLSysClrTypes.txt
		MSI\DAC2008R2\TSqlLanguageService.msi  /qn /l* MSI\DAC2008R2\log_TSqlLanguageService.txt
		MSI\DAC2008R2\SharedManagementObjects.msi  /qn /forcerestart /l*  MSI\DAC2008R2\log_SharedManagementObjects.txt
	)
)

Ahora, si está corriendo en Azure valida que los componentes no hayan instalado antes. Para eso lo que hace es ver si existe el archivo log_DACFramework.txt que se crea al instalar utilizando este CMD. Esta validación es necesaria porque al instalar el último componente se reinicia la vm del Worker Role. Sin esta validación, la VM entraría en el loop infinito de reinicios. El archivo CMD es el siguiente.Por último, en el archive ServiceDefinition.csdef debemos agregar la tarea de inicio para que se ejecuten las instalaciones de los archivos MSI y la variable de control ComputeEmulatorRunning para poder saber si estamos en el emulador o en Azure. Esto se hace agregando el siguiente TAG al archivo.

<Startup>
      <Task commandLine="installDAC.cmdexecutionContext="elevatedtaskType="background">
        <Environment>
          <Variable name="ComputeEmulatorRunning">
            <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />
          </Variable>
        </Environment>
      </Task>
    </Startup>

Más información de cómo definir tareas de inicio para un role pueden verlo en How to Define Startup Tasks for a Role

Paso 15: Probar el Deploy en Azure

Ahora hacemos deploy a Windows azure. Para poder revisar el archivo en el CloudDrive, activamos en el wizard de publicación la opción de Remote Desktop como muestra la siguiente imagen.

Una vez hecho el deploy, podemos revisar que tengamos nuestro Azure CloudDrive.

Se puede revisar en el disco de la aplicación, que se encuentran los archivos de LOG de instalación, como se de en la siguiente imagen.

Como estamos en Azure y no en el emulador, para poder ver el avance nos conectamos a la tabla de Traza para poder ver el avance. La cadena de conexión es la que se configuró en las propiedades del role. Podemos ver la traza de inicio del respaldo y que se comenzó a hacer la copia de la base de datos con el nombre AdventureWorks2012_Copy_43.

Cuando termina la copia de la base de datos, aparece el registro que nos indica que la copia tomo 16 minutos.

Copia DB: Tiempo Copia: 00:16:05.5937425

Por último podemos ver el término del proceso, el cual tomó 19 minutos y generó un archivo de 17MB.

Como validación final, vamos al disco F que en este caso es nuestro CloudDrive y vemos el archivo BACPAC creado de 17 MB, es decir ya tenemos el respaldo de nuestra base de datos.

Al cierre

Este ejemplo implementa el proceso de copia, exportación y borrado de la copia de una base de datos de Azure en un CloudDrive. El uso de CLoudDrive es opcional, ya que se podría haber implementado el guardar los archivos directamente en el Blob Storage sin usar un disco virtual.

El código es de ejemplo y no debe ser utilizado en producción, puede ser descargado desde aquí.

Otros artículos relacionados con Azure: