San Valentín se Baila
Este domingo en la plaza de Callao de Madrid habrá un evento de El Corte Inglés, Microsoft y Xbox 360 para celebrar el día de los enamorados. En este evento Plain Concepts presentará dos aplicaciones para Windows Phone 7 y Surface 2 para que las parejas de enamorados puedan hacerse fotografías con el teléfono móvil (un Nokia Lumia) y después componer una tarjeta de felicitación en un Surface 2.
Os invitamos a todos a que os paséis este domingo por la mañana por Callao con vuestras parejas, para celebrar San Valentín y disfrutar de estas aplicaciones.
Windows Phone 7
Las imágenes se suben a Azure y luego desde la aplicación de Surface 2 se pueden componer.
Surface 2
Ejecutar tareas elevadas durante el ciclo de vida del Rol de Azure
Cuando desarrollamos para Windows Azure podemos encontrarnos con distintos escenarios que van desde aplicaciones completamente .NET y aplicaciones que son migraciones de aplicaciones existentes. En ese sentido uno de los dolores de cabeza a la hora de trabajar con Azure son los registros de componentes COM durante el arranque del rol de Azure. Este tipo de problema se soluciona normalmente creando una tarea en el startup del rol que desea consumir ese tipo de componentes COM.
Si por ejemplo nosotros durante el ciclo de ejecución de nuestro rol queremos ejecutar un proceso con elevación, es decir con permisos completos de administrador no podemos hacerlo porque el proceso que hostea la web y el worker role no está elevado, y aunque nosotros lo indiquemos a la hora de ejecutar el proceso eso no va a funcionar.
Es por eso que podemos hacer un pequeño truco para que podamos ejecutar proceso elevados durante nuestro ciclo de ejecución del rol, es decir en cualquier momento, así podemos registrar componentes COM, o llamar a ejecutables del SO de manera mucho más cómoda. Para poder llegar a esa aproximación tenemos que buscar un entorno donde podamos ejecutar nuestras aplicaciones de manera elevada, y ese entorno es el entorno de startup del rol, así que de alguna manera lo que tenemos que tener es un proceso sentinel que se arranque en el startup del rol y que acepte peticiones para ejecutar procesos de manera elevada.
Pues justamente eso es lo que vamos a hacer, utilizando WCF para abrir un pipe de comunicación entre los procesos vamos a crear un servicio que escuche peticiones de otro proceso a través de un pipe para enviar un mensaje que representa una invocación de un proceso.
Vamos por pasos:
Definición del servicio
Como lo que queremos hacer es exponer un servicio de WCF a través de pipes de Windows, tenemos que definir la interfaz del contrato de operaciones:
[ServiceContract(Namespace = "http://azure.plainconcepts.com/schemas/04/2011/azure/executionhost")] public interface IExecutionHost { [OperationContract] void ExecuteTask(ProcessTask host); }
Una vez que tenemos definido el contrato servicio tenemos que hacer dos cosas, primero hacer la implementación del servicio, es decir el proceso sentinel que escuchará las peticiones recibidas y hará el trabajo de ejecutar esos procesos.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class ExecutionHostService : IExecutionHost { public void ExecuteTask(ProcessTask host) { Process process = new Process(); process.StartInfo = host.StartInfo; process.Start(); } }
Otra cosa que tenemos que hacer en el proceso sentinel es hostear el servicio y ponerlo a escuchar peticiones a través del binding que nosotros seleccionemos, en este caso NetNamedPipeBinding:
public class ExecutionHostServiceManager { public ExecutionHostServiceManager() { service = new ExecutionHostService(); ServiceHost host = new ServiceHost(service); string address = "net.pipe://PlainConcepts/Azure/ExecutionHost"; NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None); host.AddServiceEndpoint(typeof(IExecutionHost), binding, address); // Add a mex endpoint long maxBufferPoolSize = binding.MaxBufferPoolSize; int maxBufferSize = binding.MaxBufferSize; int maxConnections = binding.MaxConnections; long maxReceivedMessageSize = binding.MaxReceivedMessageSize; NetNamedPipeSecurity security = binding.Security; string scheme = binding.Scheme; XmlDictionaryReaderQuotas readerQuotas = binding.ReaderQuotas; BindingElementCollection bCollection = binding.CreateBindingElements(); HostNameComparisonMode hostNameComparisonMode = binding.HostNameComparisonMode; bool TransactionFlow = binding.TransactionFlow; TransactionProtocol transactionProtocol = binding.TransactionProtocol; EnvelopeVersion envelopeVersion = binding.EnvelopeVersion; TransferMode transferMode = binding.TransferMode; host.Open(); } private ExecutionHostService service; }
Todo ello lo tenemos que poner en un pequeño programa de consola que será el proceso en sí que hosteará el pipe de Windows que aceptará peticiones a través de WCF:
class Program { static void Main(string[] args) { new ExecutionHostServiceManager(); Thread.Sleep(Timeout.Infinite); } }
Fijaros que al final de la ejecución de la clase hay un Thread.Sleep(Timeout.Infinite) que nos permite esperar eternamente en el proceso para que así el proceso esté disponible durante todo el ciclo de vida del rol, permitiéndonos ejecutar un proceso elevado en cualquier momento.
Haciendo llamadas al servicio
Como bien es sabido para poder hacer llamadas a un servicio de WCF lo primero que tenemos que hacer es generar un proxy en el cliente para hacer esas llamadas. Como queremos hacerlo todo por código para simplificar, lo que vamos a hacer es una clase que herede de ClientBase<T> siendo T la interfaz del contrato de operaciones de nuestro servicio.
public class ExecutionHostClient : ClientBase<IExecutionHost> { static ExecutionHostClient() { string address = "net.pipe://PlainConcepts/Azure/ExecutionHost"; NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None); binding.CloseTimeout = TimeSpan.MaxValue; binding.ReceiveTimeout = TimeSpan.MaxValue; binding.SendTimeout = TimeSpan.MaxValue; EndpointAddress endpoint = new EndpointAddress(address); client = new ExecutionHostClient(binding, endpoint); } public ExecutionHostClient(Binding binding, EndpointAddress remoteAddress) : base(binding, remoteAddress) { } public void ExecuteTask(ProcessTask task) { Channel.ExecuteTask(task); } public static void ExecuteRemoteTask(ProcessTask task) { client.ExecuteTask(task); } private static ExecutionHostClient client; }
Es importante que el proxy se inicialice con el mismo binding que el de servidor para que las invocaciones funcionen. En este ejemplo para simplificar tenemos una referencia estatica del proxy y solamente lo exponemos a través de un método estático.
Invocando servicios
Para el ejemplo actual podemos registrar los componentes COM de una carpeta que tengamos en nuestro worker role:
public class RegisterComHelper { public RegisterComHelper() { } public void Register() { // hay que buscar la localizacion en el servidor de azure de donde estan los ensamblados // como no sabemos donde estan los ficheros tenemos que buscar el modulo // Habitania.RegisterCom.dll que es especifico para este ejemplo // así nos aseguramos que estamos buscando la dll correcta Process current = Process.GetCurrentProcess(); var found = (from p in current.Modules.Cast<ProcessModule>().ToList() where p.ModuleName == "PlainConcepts.Azure.WorkerRoleDemo.dll" select p).FirstOrDefault(); if (found != null) { // a partir de la locacion del modulo cargada por el proceso // somos capaces de encontrar la informacion del directorio y buscar // la carpeta dlls que contiene la lista de dlls que queremos registar string directoryLocation = Path.GetDirectoryName(found.FileName); string dllPath = Path.Combine(directoryLocation, "V3COM30"); string[] files = Directory.GetFiles(dllPath); foreach (var item in files) { if (item.EndsWith(".dll")) RegisterComObject(item); } dllPath = Path.Combine(directoryLocation, "V3COM"); files = Directory.GetFiles(dllPath); foreach (var item in files) { if (item.EndsWith(".dll")) RegisterComObject(item); } } } private void RegisterComObject(string filePath) { ProcessStartInfo info = new ProcessStartInfo(); info.FileName = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.System), "regsvr32.exe"); info.Arguments = string.Format("/i {0}", filePath); info.UseShellExecute = false; ExecutionHostClient.ExecuteRemoteTask(new ProcessTask() { StartInfo = info }); } }
Este método para trabajar con Windows Azure puede ser un poco complicado de montar, pero una vez hecho tenemos un mecanismo muy sencillo para hacer cosas más complicadas como por ejemplo ejecutar otro tipo de tareas de mantenimiento directamente desde ahí.
El codigo completo del ejemplo aquí.
Luis Guerrero.
El worker role de Azure lanza un FileNotFoundException al cargar
Actualmente estoy trabajando en un player con tecnología de smooth streaming basado en Silverlight y tenemos algunos servicios que el player consume que queremos publicar en Azure. El caso es que me he puesto a hacer las primeras pruebas en Azure en mi maquina local, pero cada vez que ejecuto el projecto el Azure Simulation Environment este me lanza un FileLoadException.
[runtime] Role entrypoint could not be created: System.IO.FileLoadException: Could not load file or assembly 'Microsoft.Silverlight.MediaPlayer.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a10dc7bdece43c5c' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) File name: 'Microsoft.Silverlight.MediaPlayer.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a10dc7bdece43c5c' ---> System.IO.FileLoadException: Could not load file or assembly 'file:///C:\Plain\Microsoft.SmoothPlayer\Source\Azure\Microsoft.Silverlight.MediaPlayer.Online\bin\Debug\Microsoft.Silverlight.MediaPlayer.Online.csx\roles\Microsoft.Silverlight.MediaPlayer.Web\approot\bin\Microsoft.Silverlight.MediaPlayer.Web.dll' or one of its dependencies. The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047) File name: 'file:///C:\Plain\Microsoft.SmoothPlayer\Source\Azure\Microsoft.Silverlight.MediaPlayer.Online\bin\Debug\Microsoft.Silverlight.MediaPlayer.Online.csx\roles\Microsoft.Silverlight.MediaPlayer.Web\approot\bin\Microsoft.Silverlight.MediaPlayer.Web.dll' WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure logging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog]. at System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection) at System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection) at System.Reflection.Assembly.LoadFrom(String assemblyFile) at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.CreateRoleEntryPoint(RoleType roleTypeEnum) at Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.InitializeRoleInternal(RoleType roleTypeEnum)
Llegado a este punto hice una doble comprobación de que realmente el fichero estaba ahi y se tenian permisos para leer el fichero. Active la salida del Fusion Log para ver los errores de carga de los ensamblados pero no me aparecía nada. Así que decidí utilizar el WinDBG para sacar un poco más de información.
Me atache al proceso host de web de Azure WaWebHost.exe (Microsoft Windows Azure Web Host), cargue todos los símbolos y SOS. El primer comando que ejecute fue !threads y encontré esto:
0:000> !threads
ThreadCount: 6
UnstartedThread: 0
BackgroundThread: 5
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
PreEmptive Lock
ID OSID ThreadOBJ State GC GC Alloc Context Domain Count APT Exception
XXXX 1 1ba0 000000000014c4a0 2008220 Enabled 00000000053a1c10:00000000053a1fd0 0000000002f182a0 1 Ukn System.IO.PathTooLongException (0000000005305a80)
XXXX 2 294 00000000000e46d0 b220 Enabled 00000000053d09f8:00000000053d1fd0 0000000002f17990 0 MTA (Finalizer)
XXXX 3 13b0 00000000022f9350 a802220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 MTA (Threadpool Completion Port)
XXXX 4 1a80 0000000002fbc080 80a220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 MTA (Threadpool Completion Port)
XXXX 5 1ae0 0000000002fbe650 1220 Enabled 0000000000000000:0000000000000000 0000000002f17990 0 Ukn
0 6 7e4 0000000002fe22c0 1020 Enabled 00000000053c69b0:00000000053c7fd0 0000000002f182a0 0 Ukn System.IO.FileLoadException (00000000053a7ca8)
Como se puede observer hay dos dominios de aplicación cargados en el proceso, el thread 1 y 6 están ejecutando código de ese dominio de aplicación y el thread 1 muestra la excepción que estamos depurando FileLoadException pero el thread 6 muestra un System.IO.PathTooLongException.
0:001> !pe 0000000005305a80 Exception object: 0000000005305a80 Exception type: System.IO.PathTooLongException Message: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters. InnerException: <none> StackTrace (generated): SP IP Function 00000000031BD360 000007FEED2D5A8F mscorlib_ni!System.IO.Path.NormalizePathFast(System.String, Boolean)+0xd0f 00000000031BD420 000007FEED239CA6 mscorlib_ni!System.IO.File.Exists(System.String)+0x96 StackTraceString: <none> HResult: 800700ce The current thread is unmanaged
Así que el único problema es que el path del fichero es demasiado largo y el sistema no puede cargarlo.
Espero que os sea de ayuda!
Luis Guerrero.

