Ahora que ya tenemos la base de datos en Azure y el WebSite convertido a una WebApplication, por fin podemos ponernos manos a la obra con la conversión en un WebRole de Azure.
Realmente, no se trata de una conversión, sino más bien añadir una funcionalidad a la aplicación web para que pueda ejecutarse como tal. La idea es simplemente añadir una clase que herede de RoleEntryPoint para que el controlador de AppFabric de Azure pueda manejar la aplicación.
Convirtiendo la WebApplication en un WebRole
El
primer paso es agregar las referencias necesarias de Windows Azure de todo WebRole. Estas referencias son -recordad
descargar el SDK de Windows Azure para tenerlas disponibles:
El segundo paso a realizar, es añadir la entrada del listener de monitor de diagnósticos de Windows Azure en el web.config. Para ello añadimos la siguiente entrada justo después de la sección <configSections>:
<system.diagnostics>
<trace>
<listeners>
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="AzureDiagnostics">
<filter type="" />
</add>
</listeners>
</trace>
</system.diagnostics>
Finalmente, en el tercer paso agregamos la clase WebRole que hereda de RoleEntryPoint:
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.Diagnostics
Imports Microsoft.WindowsAzure.ServiceRuntime
Public Class WebRole
Inherits RoleEntryPoint
Public Overrides Function OnStart() As Boolean
' Change the transfer period to the Blob
Dim config = DiagnosticMonitor.GetDefaultInitialConfiguration()
config.Directories.ScheduledTransferPeriod = TimeSpan.FromMinutes(1)
config.Logs.ScheduledTransferPeriod = TimeSpan.FromMinutes(1)
config.WindowsEventLog.ScheduledTransferPeriod = TimeSpan.FromMinutes(1)
' Enable collection of crash dumps
Microsoft.WindowsAzure.Diagnostics.CrashDumps.EnableCollection(True)
DiagnosticMonitor.Start("DiagnosticsConnectionString", config)
' For information on handling configuration changes
' see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.
AddHandler RoleEnvironment.Changing, AddressOf RoleEnvironmentChanging
Return MyBase.OnStart()
End Function
Private Sub RoleEnvironmentChanging(ByVal sender As Object, ByVal e As RoleEnvironmentChangingEventArgs)
' If a configuration setting is changing
If (e.Changes.Any(Function(change) TypeOf change Is RoleEnvironmentConfigurationSettingChange)) Then
' Set e.Cancel to true to restart this role instance
e.Cancel = True
End If
End Sub
End Class
Bueno, ya está convertida la aplicación en un WebRole. ¿Y ahora cómo podemos comprobar su funcionamiento?
Creación del proyecto de servicio en la nube
Antes de desplegarlo, hay que agregar a la solución un nuevo proyecto del tipo "Servicio en la nube"
1) Pulsamos botón derecho sobre la solución y seleccionamos "Agregar->Nuevo proyecto..."
2) Seleccionamos de las plantillas de Cloud el tipo de proyecto "Windows Azure Cloud Project"
3) En la siguiente ventana de incluir roles al servicio, no agregamos ninguno y pulsamos el botón "Ok"
4) Ahora, pulsando con el botón derecho sobre la subcarpeta Roles, usamos la acción "Add->Web Role project in solution..."
5) En la ventana se nos presentan todos los proyectos tipo WebApplication que existen en la solución. Seleccionamos nuestro DotNetNukeCommunityWebApp para incluirlo dentro de la configuración del servicio y pulsamos Ok
6) Pulsamos botón derecho sobre el servicio y seleccionamos el menú "Establecer como proyecto de inicio"
Dejando el resto de los parámetros tal y como vienen por defecto y si tenemos correctamente configurado nuestro entorno de desarrollo de Azure (Development Fabric y Development Storage), estamos listos para hacer la prueba inicial del despliegue.
Iniciando el WebRole en Development Fabric
Una vez que estamos listos para arrancar por primera vez el WebRole, pulsamos F5 para ejecutarlo dentro del entorno de desarrollo. Se despliega automáticamente sobre Development Fabric...
...y luego se abre el navegador...vaya, un error :) No todo iba a ser un paseo...
Errores solucionados para el despliegue
Los errores solucionados no eran muchos, realmente sólo tres, así que los expongo porque son "curiosos". Empecemos de menos a más.
1. Error de .NET trust level
El primero error que me apareció en pantalla era referido fue por un parámetro de configuración en el web.config de DNN. El error en cuestión era el siguiente:
"This configuration section cannot be used at this path. This happens when the site administrator has locked access to this section using <location allowOverride="false"> from an inherited configuration file"
Line 123: <trust level="Medium" originUrl=".*" />
Esto es lógico porque en el modelo de servicios cloud la configuración de .NET Trust se realiza en la configuración del WebRole. Para evitar este error dentro de Fabric, simplemente hay que comentar en el web.config la línea <trust level=”Medium” orginURL=”.*” /> e indicar el nivel de confianza de .NET en la pestaña de configuración del WebRole:
2. Error de ruta demasiado larga
La siguiente vez que realicé el despliegue, surgió un nuevo error, que me dejó algo preocupado. El error en cuestión se dió cuando al iniciar la aplicación se intentaron cargar archivos que estaban ubicados en una ruta demasiado larga:
“The path is too long after being fully qualified. Make sure the full path is less than 260 characters and the directory name is less than 248 characters.”
Este límite está impuesto por la WIN32 API y ha sido bastante criticado a lo largo de los años. Como a mí me encanta decir: "NEFASTO!"
Madre mía, ¿y ahora? Tranquilidad...esto le ha tenido que pasar a alguien más. Un segundo de búsquedas después apareció la respuesta en
este post en un blog de MSDN.
Por defecto la ruta donde se despliegan los webroles en Development Fabric es en c:\Users\<username>\AppData\Local\dftmp. A partir de ahí se despliega y dependiendo de la profundidad de las carpetas y largo de nombre de archivos este límite es muy fácil de alcanzar, ya que el despliegue le añade a la ruta un "churro" de caracteres que identifican unívocamente al mismo.
Como solución temporal para ganar unos caracteres, podemos establecer la ruta por defecto de los despliegues de Development Fabric en una ruta más corta, por ejemplo en "C:\A". ¿Cómo? Creando una variable de entorno llamada _CSRUN_STATE_DIRECTORY con el valor de dicha ruta.
Con esto por suerte a mí me valió. ¿Qué podemos hacer si en otro proyecto pasa algo similar? Podemos seguir dos estrategias:
Si podemos tocar los nombres de los ensamblados, archivos, etc. renombrar los archivos con nombres más cortos. ¿Una broma? Sí, aunque ganaríamos unos caracteres posiblemente podríamos alcanzar el límite en algún momento -sin contar el estúpido esfuerzo. ¿Alguna otra solución?
Indagando un poco por la red,
me encontré con este otro post que aclara que el sistema de archivos NTFS soporta rutas de hasta 32k de largo...32k!!! ¿Cómo? Pues simplemente añadiendo como prefijo los caracteres "\\?\" a la ruta...ale, otra que no sabía. Con esto, podríamos modificar la ruta anterior a "\\?\C:\A" y no habría límite.
Lo he probado y no funciona -¡NEFASTO!. Da un error de "Caracteres no válidos" al desplegar sobre Development Fabric desde Visual Studio.
Si tengo alguna novedad sobre el tema, lo pondré como un comentario.
3. Problema con el módulo de UrlRewriteModule
Este tercer error, con el que podría escribir un post entero con más detalle, fue con el único que tuve que retocar algo el código fuente. También fue uno de los que más que me costó detectar, ya que se me pasó en esta fase de desarrollo y realmente lo detecté haciendo una traza con IntelliTrace sobre Azure en la fase posterior.
Una vez detectado, volví al entorno de desarrollo y observé que también se daba con lo que fue ya mucho más fácil corregirlo.
Bueno...déjate de rollos y al grano.
El error en concreto es un
fallo de bucle infinito de redirección (
DNN Redirect Loop). En resumen es que al abrir cualquier página del portal
el navegador se queda redirigiéndose una y otra vez a la página de inicio -con Internet Explorer se queda en bucle, Chrome o Firefox lo detectan y te lo muestra con más detalle.
Lo primero que pensé era un problema con los Alias del portal. Tras repasarlos, pude comprobar que estaban correctamente configurados. Sin ponerme a depurar aún -recuerden que lo detecté cuando estaba publicado en Azure- me puse a buscar los posibles motivos.
La mayoría de los posts eran indicando
un problema de versión con la versión de las librerías AJAX. Como cada deploy en la nube me costaba mucho tiempo entre pruebas -mínimo 30 minutos entre cada despliegue- quise comprobar que efectivamente se trataba de lo que comentaban. Fue aquí donde usé IntelliTrace -ya escribiré ún post con el detalle de cómo hacerlo, que no acabamos hoy.
Pues no tenía que ver para nada con las librerías AJAX. Se trataba de la diferencia de resultados que devuelve el método Request.Url.AbsoluteUri que usa el módulo de Rewriting dependiendo de si se ejecuta en la arquitectura Azure o no.
Para que se entienda rápidamente, imaginemos una simple aplicación con una página aspx que en el evento PageLoad tiene el siguiente código:
Response.Write(Request.Url.AbsoluteUri)
Si se ejecuta fuera del entorno Azure -como una aplicación web normal y corriente- el valor que devuelve coincide con la URL que se está solicitando:
Si ahora lo ejecutamos dentro del entorno de Development Fabric (en Azure pasa lo mismo) el resultado es el siguiente (en Development Fabric se muestra el puerto 5100, en real el puerto usado es el 20000):
¿Por qué devuelve esto? Muy claro. Tal y como
se menciona en el foro de Windows Azure, las peticiones en Azure se envían al Fabric Controller, que es la dirección "pública". Luego el controlador envía la petición a una de las instancias de IIS Hosted Web Core que tengan alojado el WebRole -actúa como un balanceador de carga. El problema es que devuelve la URL del Hosted Web Role, no la externa del Fabric Controller.
La solución pasa por utilizar este código en vez del anterior:
Response.Write(Request.Url.Scheme & Uri.SchemeDelimiter & Request.Headers("Host") & Request.RawUrl)
Una vez realizados los cambios en el módulo, se acabaron los problemas. Ya funcionaba todo correctamente.
Quiero más...¡potencia!
Recordando las palabras de Maverick en TopGun -realmente decía "¡Quiero más velocidad!" aunque también me vale- me vino a la mente probar a desplegar más instancias para comprobar las posibilidades del portal adaptado al entorno Azure.
¿Que hace falta añadir máquinas del servicio porque tenemos el portal saturado? ¿Qué hace falta quitar capacidad de proceso porque ya no hacen falta tantas?
Muy fácil. Para eso precisamente está pensada la nube. Vamos al fichero de configuración y modificamos el número o tipo de instancias a desplegar. Pulsamos F5 para desplegar de nuevo en el entorno de desarrollo y listo (he de reconocer que me pasé al agregar 10 instancias, casi me quedo sin memoria física en mi máquina de desarrollo y el disco duro no me paraba de hacer swapping, pero una imagen vale más que mil palabras):
Tarea para el futuro: crear un módulo en DNN para poder realizar todas las configuraciones y modiificaciones de despliegues desde dentro del mismo portal.
Conclusión
Después de algunos errores algo "curiosos", la solución está preparada para su despliegue en Azure. La prueba de desplegar en varias instancias funciona de perillas y deja entrever las posibilidades de tener un CMS en la nube.
El siguiente objetivo estaba cerca: desplegar en la nube.
Como siempre, les dejo el enlace al portal que ya está publicado en Azure para que vayan viendo las evoluciones: