blambert/azure – Windows Azure Worker Roles – Thinking Outside The Box

In this posting I am going to show you how to run an Azure Worker Role outside the Development Fabric.

Why would you want to do this?

Because you want to be an Azure Ninja!  (空色の忍者)

While testing an Azure Worker Role in the Development Fabric is an important part of the development process, having to deploy an Azure Service on every development cycle is costly.  Especially if it is a Web and Worker Cloud Service, and you are only working on the Worker Role.

Fortunately, an Azure Worker Role is simply an assembly containing a class derived from RoleEntryPoint, and what we need from RoleManager isn’t that complicated.

This won’t be that hard!

Let’s get started by creating a standard Azure Service with a standard Worker Role:

Create

At this point you will see:

WorkerRole

Let’s run this in the Development Fabric to see that everything works.

 WorkerRole1

And we can see it’s up and running by the “Working” log entries.

Manual Mode

safetyswitch

I’m going to fast-forward now to the changes I made to the Example Solution, and then explain what I did.

Here’s my solution after making my changes:

SolutionAfter

Summary: I added two new projects: Helpers, using the Class Library project template, and Example_WorkerRole.Simulator, using the the Console Application project template.

In the Helpers project, I added a class called AzureHelper.  In this class I added a WriteToLog method, which mimics RoleManager.WriteToLog, and uses the RoleManager.IsRoleManagerRunning property to see whether to write messages to Azure or to the Console:

 namespace Helpers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Globalization;
    using Microsoft.ServiceHosting.ServiceRuntime;

    /// <summary>
    /// AzureHelper class.
    /// </summary>
    public static class AzureHelper
    {
        /// <summary>
        /// Writes a string value to the log.
        /// </summary>
        /// <param name="eventLogName">
        /// The type of event to be written to the log. Permitted string values
        /// are Critical, Error, Warning, Information, Verbose.
        /// </param>
        /// <param name="message">The message to write to the log. </param>
        public void WriteToLog(string eventLogName, string message)
        {
            // When Azure RoleManager is running, write to the Azure log; otherwise, write to the console.
            if (RoleManager.IsRoleManagerRunning)
            {
                RoleManager.WriteToLog(eventLogName, message);
            }
            else
            {
                Console.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} {1}", eventLogName, message);
            }
        }
    }
}

Next, I changed the WorkerRole.cs file to use AzureHelper.WriteToLog instead of RoleManager.WriteToLog (and added a few other bits, which are self-explanatory):

 namespace Example_WorkerRole
{
    using System;
    using System.Collections.Generic;
    using System.Threading;
    using System.Linq;
    using System.Text;
    using Microsoft.ServiceHosting.ServiceRuntime;
    using Helpers;
    
    /// <summary>
    /// Example worker role.
    /// </summary>
    public class WorkerRole : RoleEntryPoint
    {
        /// <summary>
        /// A value which indicates whether the worker role is running.
        /// </summary>
        private volatile bool running;

        /// <summary>
        /// Called to start the worker role.
        /// </summary>
        public override void Start()
        {
            AzureHelper.WriteToLog("Information", "Started");

            for (running = true; running; )
            {
                AzureHelper.WriteToLog("Information", "Working");
                Thread.Sleep(1000);
            }

            AzureHelper.WriteToLog("Information", "Exiting");
        }

        /// <summary>
        /// Called to stop the worker role.
        /// </summary>
        public override void Stop()
        {
            running = false;
            base.Stop();
        }

        /// <summary>
        /// Returns the current health status of the role. 
        /// </summary>
        /// <returns>A RoleStatus representing the health status of the role.</returns>
        public override RoleStatus GetHealthStatus()
        {
            return RoleStatus.Healthy;
        }
    }
}

Now our Worker Role doesn’t care where it’s running!

You can mimic RoleManager.GetConfigurationSetting and RoleManager.GetLocalResource in the same way. Implementing ILocalResource looks to be a snap… (I’ll leave this work for you to do to keep this posting as concise as possible.)

Next I added WorkerRoleSimulator.cs to the Helpers project:

 namespace Helpers
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Reflection;
    using System.Threading;
    using System.Diagnostics;
    using Microsoft.ServiceHosting.ServiceRuntime;

    /// <summary>
    /// WorkerRoleSimulator class.  Represents an Azure worker role simulator.
    /// </summary>
    /// <typeparam name="T">The type of the RoleEntryPoint to instantiate.</typeparam>
    /// <remarks>
    /// This simulator provides an environment outside of Azure where the functionality
    /// of a worker role can be tested.
    /// </remarks>
    public class WorkerRoleSimulator<T> where T : RoleEntryPoint
    {
        /// <summary>
        /// The role entry point.
        /// </summary>
        private RoleEntryPoint roleEntryPoint = Activator.CreateInstance<T>();

        /// <summary>
        /// The workerRoleThread.
        /// </summary>
        private Thread workerRoleThread;

        /// <summary>
        /// Initializes a new instance of the WorkerRoleSimulator class.
        /// </summary>
        private WorkerRoleSimulator()
        {
        }

        /// <summary>
        /// Starts the worker role simulator.
        /// </summary>
        public static void Start()
        {
            WorkerRoleSimulator<T> workerRoleSimulator = new WorkerRoleSimulator<T>();
            workerRoleSimulator.Run();
        }

        /// <summary>
        /// Runs the simulator.
        /// </summary>
        public void Run()
        {
            // Advise.
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Worker Role Simulator");
            Console.WriteLine("---------------------");

            // Start the worker role running in its own thread, leaving this thread to be its console.
            this.workerRoleThread = new Thread(this.WorkerRoleThreadEntryPoint);
            this.workerRoleThread.IsBackground = true;
            this.workerRoleThread.Start();

            // While running.
            bool running = true;
            do
            {
                // Prompt for command.
                Console.WriteLine("Enter your command:");
                string command = Console.ReadLine().ToUpperInvariant().Trim();

                // Process command.
                if (String.IsNullOrEmpty(command))
                {
                    this.ShowValidCommands();
                }
                else
                {
                    // Dispatch.
                    switch (command)
                    {
                        // Exit command.
                        case "EXIT":
                            this.roleEntryPoint.Stop();
                            this.workerRoleThread.Join();
                            running = false;
                            break;

                        // Foreign contaminant
                        default:
                            this.ShowValidCommands();
                            break;
                    }
                }
            }
            while (running);

            // Goodbye.
            Console.WriteLine("-------");
            Console.WriteLine("Goodbye");
        }

        /// <summary>
        /// Shows valid commands.
        /// </summary>
        private void ShowValidCommands()
        {
            Console.WriteLine();
            Console.WriteLine("Valid commands are:");
            Console.WriteLine();
            Console.WriteLine("exit - to exit.");
            Console.WriteLine();
        }

        /// <summary>
        /// The worker role thread entry point.
        /// </summary>
        private void WorkerRoleThreadEntryPoint()
        {
            // Start the worker role.
            this.roleEntryPoint.Start();
        }
    }
}

I modeled WorkerRoleSimulator as a Console for running an Azure worker role.  It starts a background thread which calls the RoleEntryPoint.Start method, and waits for you to type exit; at which point it will call the RoleEntryPoint.Stop method.

You can flesh this class out to more closely mimic the Development Fabric by adding suspend and re-start with very little effort.

Finally, I changed the Program.cs file in the Example_WorkerRole.Simulator project to start the WorkerRoleSimulator:

 namespace Example_WorkerRole.Simulator
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Helpers;
    using Example_WorkerRole;

    /// <summary>
    /// Example_WorkerRole.Simulator program.
    /// </summary>
    class Program
    {
        /// <summary>
        /// Main entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        static void Main(string[] args)
        {
            WorkerRoleSimulator<WorkerRole>.Start();
        }
    }
}

And that’s it!

Let’s fire it up:

SimulatorRunning

Best!

Brian