In my previous post I described the idea of using plug-ins in Windows Azure.  In context of this sample we implemented a general plug-in framework. As we saw in the video, we need two services: one which is taking the work orders and stores them in the repository and the other one which is processing them.

WorkOrderGatewayService is representing a service which takes the work orders and stores them in the BLOB Storage. This service also answers the question whether the work order is done or not. WorkOrderProcessorService is representing the service which is processing the work orders.

Web Role: WorkOrderGatewayService

The WorkOrderGatewayService is the simple web service based on WCF technology. This service is the only service which is visible to clients. The responsibility of the WorkOrderGatewayService is:

  • store the work orders in the BLOB Storage and create work order messages in the queue
  • provide the method which creates a command to refresh plugins
  • provide the method to check if the work order has benn finished
    public class WorkOrderGatewayService : IWorkOrderGatewayService
    {
        private readonly IWorkOrderManagement _workOrderManagementBusinessLayer;
        private readonly ICommandManagement _commandManagementBusinessLayer;

        public WorkOrderGatewayService()
        {
            var storageConnectionString = RoleEnvironment.GetConfigurationSettingValue("StorageConnectionString");
            var blobStorageWorkOrdersContainer = RoleEnvironment.GetConfigurationSettingValue("BlobStorageWorkOrdersContainer");
            var messageQueueName = RoleEnvironment.GetConfigurationSettingValue("MessageQueueName");

            var azureRepository = new AzureRepository(storageConnectionString);
            var orderWorkerRepository = new WorkOrderRepository(azureRepository, blobStorageWorkOrdersContainer, messageQueueName);

            _workOrderManagementBusinessLayer = new WorkOrderManagement(orderWorkerRepository, azureRepository, messageQueueName);
            _commandManagementBusinessLayer = new CommandManagement(azureRepository, messageQueueName);
        }

        public void ExecuteWorkOrder(WorkOrder order)
        {
            _workOrderManagementBusinessLayer.SendWorkOrderToProcessingQueue(order);
        }

        public bool OrderFinished(Guid identifier, out WorkOrderResult result)
        {
            result = _workOrderManagementBusinessLayer.GetWorkOrderResult(identifier);
            return result != null;
        }

        public void RefreshPlugins()
        {
            _commandManagementBusinessLayer.SendCommandToProcessing(new Command {CommandText = "RefreshPlugins"});
        }
    }

Worker Role: WorkOrderProcessorService

The WorkOrderProcessorService is the service, which is processing the work orders.

While the plug-in’s infrastructure, we created before, is able to load the plugins from the file system, this is a problem in context of Windows Azure, because:

  • we don’t have direct access to the machines file system, to be able to upload plugins there
  • the local file system is not persistent

How can we solve this? We will use the BLOB Storage as plugins repository. In the BLOB Storage we are creating the container named ‘plugins’ (if you don’t create it, WorkOrderProcessorService will do so, when the role is starting). To this container we can upload the plugins, as you can see below. How you name the  “folders” is up to you.

image

image

To be able load plugins by FileSystemPluginLoader, we have to copy the content of ‘plugins’ container to the machine’s file system. This is the most tricky part. As I wrote in my  previous post, we have to ensure that all plugins are under the applications base path directory (do you remember the sense of  the PrivateBinPath?) to be able to instantiate them when we are creating new Application Domain.

You can retrieve the application’s bin directory in Windows Azure, like this.

                //In Azure enviroment can be RoleRoot path something like this C:
                //If you don't add DirectorySeparatorChar, than path will be after Path.Combine something like this C:approot
                //C:approot has different meaning as C:\\approot
                //for more details see
                //http://stackoverflow.com/questions/1527942/why-path-combine-doesnt-add-the-path-directoryseparatorchar-after-the-drive-des
                //http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx#relative%5Fpaths
                var roleRootPath = Environment.GetEnvironmentVariable("RoleRoot");
                if(!roleRootPath.EndsWith(Path.DirectorySeparatorChar.ToString())) //checks if directory separator exists
                {
                    roleRootPath = roleRootPath + Path.DirectorySeparatorChar.ToString(); //Path.Combine deliver in this context bad result
                }

                var applicationPhysicalPath = Path.Combine(roleRootPath, "approot"); //Gets application physical path

We are copying the plugins to the file system when the role is starting with the method RefreshPlugins of class PluginRepository. PluginRepository is wrapping the class PluginManager created in the previous post and provide some extra methods.

image

The plugins are copied to the local file system when the role is starting

        public override bool OnStart()
        {
            Trace.TraceInformation("Role is starting");

            try
            {
				...
                _pluginRepository = new PluginRepository(_azureRepository, applicationPhysicalPath, relativePluginsDirectoryPath, pluginsSearchPattern, blobStoragePluginsContainer);

                Trace.TraceInformation("Refreshing plugins");
                _pluginRepository.RefreshPlugins();
                Trace.TraceInformation("Plugins refreshed");

            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Exception thrown on role start: {0}", ex));
                throw;
            }

            Trace.TraceInformation("Role starting completed");
            return base.OnStart();

        }
        public void RefreshPlugins()
        {
            IEnumerable thrownExceptions;

            if (!_pluginManager.TryToUnloadPlugins(out thrownExceptions))
            {
                Trace.TraceError("Unloading plugins thrown exception");
                foreach (var thrownException in thrownExceptions)
                {
                    Trace.TraceError(thrownException.ToString());
                }
            }

            DeletePluginsFromLocalDrive();
            CopyPluginsToLocalDrive();

            if (!_pluginManager.TryToLoadPlugins(out thrownExceptions))
            {
                Trace.TraceError("Loading plugins thrown exception");
                foreach (var thrownException in thrownExceptions)
                {
                    Trace.TraceError(thrownException.ToString());
                }
            }
        }

This is not the only way, how it is to possible refresh the plugins. In this example, you can send commands (and in this sample, the only one: ‘RefreshPlugins’). The command ‘RefreshPlugins’ is just simple text in the queue ‘workorders’ (created by  WorkOrderProcessorService service) which is parsed by the method GetCommand provided by the class CommandProcessing.

        public Tuple<CloudQueueMessage, Command> GetCommand(CloudQueueMessage message)
        {
            if (message != null)
            {
                Guid workOrderIdentifier;
                bool isGuid = Guid.TryParse(message.AsString, out workOrderIdentifier);

                //Only GUIDs are work order identifiers. Everything else are commands.
                if (!isGuid)
                {
                    return  new Tuple<cloudqueuemessage, command="">(message, new Command
                        {
                            CommandText = message.AsString
                        });
                }
            }

            return null;
        }

The method GetCommand is called in the Run method of WorkOrderProcessorService class.

        public override void Run()
        {
            Trace.TraceInformation("Role is running");

            try
            {
                while (true)
                {

                    CloudQueueMessage message = _azureRepository.GetNextMessage(_messageQueueName, _messageTimeout);

                    Trace.TraceInformation("Try to get work command");
                    Tuple<cloudqueuemessage, command=""> command = _commandProcessingBusinessLayer.GetCommand(message);

                    if (command == null)
                    {
                        Trace.TraceInformation("No command to processing.");
                    }
                    else
                    {
                        Trace.TraceInformation("Try to execute command");
                        _commandProcessingBusinessLayer.ExecuteCommand(command);
                        Trace.TraceInformation("Command executed");
                    }

                    Trace.TraceInformation("Try to get work order");
                    Tuple<cloudqueuemessage, workorder=""> workOrder = _workOrderProcessingBusinessLayer.GetWorkOrder(message);

                    if (workOrder == null)
                    {
                        Trace.TraceInformation("No work order is existing to be processed.");
                        Trace.TraceInformation("Sleeping");
                        Thread.Sleep(10000);
                    }
                    else
                    {
                        Trace.TraceInformation("Try to processing order worker");
                        StartWorkOrderProcessing(workOrder);
                        Trace.TraceInformation("Order worker processed");
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Exception thrown on running: {0}", ex));
            }
        }

This concept is definitively not the best one. Why?

  • because, this is working only for one instance. After the instance of WorkOrderProcessorService executed the command, throws him (message) away, so no other instance has a chance to execute the command again.
  • because you must parse each message to check if it is a command or not

This is definitely the brain teaser and can be part of your orchestration strategy. Some ideas which can work:

  • you allow machines to refresh the plugins in some defined period
  • you create a separate queue for the commands for each machine

What is necessary to mention is, that you should ensure that all plugins finished thei work, before you unload them from memory, otherwise you get AppDomainUnloadedException.

If the work order is finished, the result is stored in the BLOB Storage.

image

How does the work order look like? The work order is the serialized object of type WorkOrder into the workorder.xml.

image

<?xml version="1.0" encoding="utf-16"?>
 	<!--?xml version="1.0" encoding="utf-16"?-->
<WorkOrder xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">	 	 
 <Identifier>8da0a95c-6401-49f9-aef5-8707112a5d98</Identifier>
 <PluginName>RandomWordsPlugin</PluginName>	 	 
 <PluginVersion>1.0.0.0</PluginVersion>	 	 
 <InputText>Anton Kalcik</InputText>	 	 
</WorkOrder>

The Identifier is GUID and provides the way to track concrete work orders. PluginName is the name, PluginVersion is the version of the plugin. Both parameters should match the PluginMetadata attribute one of the plugins loaded in the memory. InputText is the text which should be manipulated by concrete plugins.

The result of the plugin operation (in this case is it the text manipulation) is serialized as object of type WorkOrderResult to the workorder.result.xml. This is the same file which is checked by WorkOrderGatewayService to ensure whenever the work order is finished, or not.

image

The purpose of the Identifier is the same as by work order, the Result contains the outcome of the plugin operation.

<?xml version="1.0" encoding="utf-16"?>
 	<!--?xml version="1.0" encoding="utf-16"?-->
<WorkOrderResult xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">	 	 
 <Identifier>8da0a95c-6401-49f9-aef5-8707112a5d98</Identifier> 	 
 <Result>kiclaK notnA</Result>	 	 
</WorkOrderResult>

Deployment

To deploy the service to the Azure you have to

  • add the connection string to the Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString
  • add the connection string to the StorageConnectionString

All BLOB containers and queues are created when the roles are starting. In the BLOB container ‘workorders’ you can upload the plugins (some sample plugins are included already in the solution). To test the services, you can use the CloudServices.TestConsole.

I hope I gave you the idea, how you can get more from your cloud services. The latest you can find here. I will be thankful for your valuable feedback and appreciate any suggestions you might have for improvements – please share them with us with your comments below.

3 comments

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.