Gaurav Mantri's Personal Blog.

Windows Azure Scheduler Service – Part II: Managing Cloud Services

In the previous post about Windows Azure Scheduler Service, we talked about some of the basic concepts. From this post, we will start digging deep into various components of this service and will focus on managing these components using REST API. As promised in the last post, this and subsequent posts in this series will be code heavy :).

In this post we will focus on Cloud Service component of this service and we will see how you can manage Cloud Service using REST API.

Cloud Service

Just to recap from our last post, a cloud service is a top level entity in this service. You would need to create a cloud service first before you can create a job. Few things about cloud service:

  • Consider a cloud service as an application in which many of your job will reside and execute. A subscription can have many cloud services.
  • A cloud service is data center specific i.e. when you create a cloud service, it resides in a particular data center. If you want your jobs to be performed from many data centers, you would need to create separate cloud services in each data center.

The Code!

Now let’s code! What I have done is created a simple console application and tried to consume REST API there. Before we dig into cloud service, let’s do some basic ground work as we don’t want to write same code over and over again.

Subscription Entity

As mentioned in the previous blog post, to authorize your requests you would need subscription id and a management certificate (similar to authorizing Service Management API requests). To encapsulate that, I created a simple class called “Subscription”.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;

namespace SchedulingServiceHelper
{
    /// <summary>
    /// 
    /// </summary>
    public class Subscription
    {
        /// <summary>
        /// Subscription id.
        /// </summary>
        public Guid Id
        {
            get;
            set;
        }

        /// <summary>
        /// Management certificate
        /// </summary>
        public X509Certificate2 Certificate
        {
            get;
            set;
        }
    }
}

Helper

Since we are going to make use of REST API, I created some simple helper class which is responsible for doing web requests and handling responses. Here’s the code for that.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace SchedulingServiceHelper
{
    public static class RequestResponseHelper
    {
        /// <summary>
        /// x-ms-version request header.
        /// </summary>
        private const string X_MS_VERSION = "2012-03-01";

        /// <summary>
        /// XML content type request header.
        /// </summary>
        public const string CONTENT_TYPE_XML = "application/xml; charset=utf-8";

        /// <summary>
        /// JSON content type request header.
        /// </summary>
        public const string CONTENT_TYPE_JSON = "application/json";

        private const string baseApiEndpointFormat = "https://management.core.windows.net/{0}";

        public static HttpWebResponse GetResponse(Subscription subscription, string relativeUri, HttpMethod method, string contentType, byte[] requestBody = null)
        {
            var baseApiEndpoint = string.Format(baseApiEndpointFormat, subscription.Id);
            var url = string.Format("{0}/{1}", baseApiEndpoint, relativeUri);
            var request = (HttpWebRequest) HttpWebRequest.Create(url);
            request.ClientCertificates.Add(subscription.Certificate);
            request.Headers.Add("x-ms-version", X_MS_VERSION);
            if (!string.IsNullOrWhiteSpace(contentType))
            {
                request.ContentType = contentType;
            }
            request.Method = method.ToString();
            if (requestBody != null && requestBody.Length > 0)
            {
                request.ContentLength = requestBody.Length;
                using (var stream = request.GetRequestStream())
                {
                    stream.Write(requestBody, 0, requestBody.Length);
                }
            }
            return (HttpWebResponse)request.GetResponse();
        }
    }

    public enum HttpMethod
    {
        DELETE,
        GET,
        HEAD,
        MERGE,
        PATCH,
        POST,
        PUT,
    }

    public static class Constants
    {
        public const string Namespace = "http://schemas.microsoft.com/windowsazure";
    }
}

Cloud Service Operations

Now we are ready to consume REST API for managing Cloud Service.

Cloud Service Entity

Let’s first create a class which will encapsulate the properties of a cloud service. It’s a pretty simple class. We will use this class throughout this post.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace SchedulingServiceHelper
{
    public class CloudService
    {
        /// <summary>
        /// Cloud service name.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Cloud service label.
        /// </summary>
        public string Label
        {
            get;
            set;
        }

        /// <summary>
        /// Cloud service description.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Region where cloud service will be created.
        /// </summary>
        public string GeoRegion
        {
            get;
            set;
        }
    }
}

Create

As the name suggests, you use this operation to create a cloud service. To create a cloud service, you would need to specify all of the four properties mentioned above. You can learn more about this operation here: http://msdn.microsoft.com/en-us/library/windowsazure/dn528943.aspx.

Here’s a simple function I wrote to create a cloud service. This function is part of the “CloudService” class I mentioned above:

        public void Create(Subscription subscription)
        {
            try
            {
                string requestPayLoadFormat = @"<CloudService xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/windowsazure""><Label>{0}</Label><Description>{1}</Description><GeoRegion>{2}</GeoRegion></CloudService>";
                var requestPayLoad = string.Format(requestPayLoadFormat, Label, Description, GeoRegion);
                var relativePath = string.Format("cloudServices/{0}", Name);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.PUT, RequestResponseHelper.CONTENT_TYPE_XML, Encoding.UTF8.GetBytes(requestPayLoad)))
                {
                    var status = response.StatusCode;
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

A few things to keep in mind:

  • For “GeoRegion” element, possible values are: “uswest“, “useast“, “usnorth“, “ussouth“, “north europe“, “west europe“, “east asia“, and “southeast asia“. Any other values will result in 400 error. I have tried creating a service with each and every one of them and was able to successfully create them. Update: Based on the feedback I received – even though you could create a cloud service in all regions, at the time of writing of this post, Windows Azure only allows you to create a Job Collection in “ussouth” and “north europe” regions. Thus for trying out this service, you may want to create a cloud service in these regions only.
  • The name of your cloud service can’t contain any spaces. It can start with a number and can contain both upper and lower case alphabets and can also contain hyphens.
  • Since you send “Label” and “Description” as part of XML payload, I’m guessing that you would need to escape them properly like replacing “<” sign with “&lt;” and so on however documentation does not mention any of that. Furthermore I wish they had kept it consistent with other Service Management API calls where elements like these must be Base64 encoded.

Delete

As the name suggests, you use this operation to delete a cloud service. This operation deletes the cloud service and all resources in it thus you would need to be careful when performing this operation. To delete a cloud service, you would just need the name of the cloud service. You can learn more about this operation here: http://msdn.microsoft.com/en-us/library/windowsazure/dn528936.aspx.

Here’s a simple function I wrote to delete a cloud service. This function is part of the “CloudService” class I mentioned above:

        public void Delete(Subscription subscription)
        {
            try
            {
                var relativePath = string.Format("cloudServices/{0}", Name);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.DELETE, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    var status = response.StatusCode;
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

Get

This is one undocumented operation I stumbled upon accidently. This operation returns the properties of a cloud service. The endpoint you would use for performing this operation would be “https://management.core.windows.net/<subscription-id>/cloudServices/<cloud-service-id>” and the HTTP method you would use is “GET”.

Here’s a simple function I wrote to get information about a cloud service. This function is part of the “CloudService” class I mentioned above:

        public static CloudService Get(Subscription subscription, string cloudServiceName)
        {
            try
            {
                var relativePath = string.Format("cloudServices/{0}", cloudServiceName);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.GET, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    using (var streamReader = new StreamReader(response.GetResponseStream()))
                    {
                        string responseBody = streamReader.ReadToEnd();
                        XElement xe = XElement.Parse(responseBody);
                        var name = xe.Element(XName.Get("Name", Constants.Namespace)).Value;
                        var label = xe.Element(XName.Get("Label", Constants.Namespace)).Value;
                        var description = xe.Element(XName.Get("Description", Constants.Namespace)).Value;
                        var geoRegion = xe.Element(XName.Get("GeoRegion", Constants.Namespace)).Value;
                        return new CloudService()
                        {
                            Name = name,
                            Label = label,
                            Description = description,
                            GeoRegion = geoRegion,
                        };
                    }
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

Get operation also returns information about all the resources contained in that service. We’ll get back to this function again when we talk about job collections.

List

This is another undocumented operation I stumbled upon accidently. This operation returns the properties of all cloud services in a subscription. The endpoint you would use for performing this operation would be “https://management.core.windows.net/<subscription-id>/cloudServices” and the HTTP method you would use is “GET”.

Here’s a simple function I wrote to get information about all cloud services in a subscription. This function is part of the “CloudService” class I mentioned above:

        public static List<CloudService> GetAll(Subscription subscription)
        {
            try
            {
                using (var response = RequestResponseHelper.GetResponse(subscription, "cloudServices", HttpMethod.GET, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    using (var streamReader = new StreamReader(response.GetResponseStream()))
                    {
                        string responseBody = streamReader.ReadToEnd();
                        List<CloudService> cloudServices = new List<CloudService>();
                        XElement xe = XElement.Parse(responseBody);
                        foreach (var elem in xe.Elements(XName.Get("CloudService", Constants.Namespace)))
                        {
                            var name = elem.Element(XName.Get("Name", Constants.Namespace)).Value;
                            var label = elem.Element(XName.Get("Label", Constants.Namespace)).Value;
                            var description = elem.Element(XName.Get("Description", Constants.Namespace)).Value;
                            var geoRegion = elem.Element(XName.Get("GeoRegion", Constants.Namespace)).Value;
                            cloudServices.Add(new CloudService()
                            {
                                Name = name,
                                Label = label,
                                Description = description,
                                GeoRegion = geoRegion,
                            });
                        }
                        return cloudServices;
                    }
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

Complete Code

Here’s the complete code for CloudService class with all the operations.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace SchedulingServiceHelper
{
    public class CloudService
    {
        /// <summary>
        /// Cloud service name.
        /// </summary>
        public string Name
        {
            get;
            set;
        }

        /// <summary>
        /// Cloud service label.
        /// </summary>
        public string Label
        {
            get;
            set;
        }

        /// <summary>
        /// Cloud service description.
        /// </summary>
        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Region where cloud service will be created.
        /// </summary>
        public string GeoRegion
        {
            get;
            set;
        }

        public void Create(Subscription subscription)
        {
            try
            {
                string requestPayLoadFormat = @"<CloudService xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"" xmlns=""http://schemas.microsoft.com/windowsazure""><Label>{0}</Label><Description>{1}</Description><GeoRegion>{2}</GeoRegion></CloudService>";
                var requestPayLoad = string.Format(requestPayLoadFormat, Label, Description, GeoRegion);
                var relativePath = string.Format("cloudServices/{0}", Name);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.PUT, RequestResponseHelper.CONTENT_TYPE_XML, Encoding.UTF8.GetBytes(requestPayLoad)))
                {
                    var status = response.StatusCode;
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

        public void Delete(Subscription subscription)
        {
            try
            {
                var relativePath = string.Format("cloudServices/{0}", Name);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.DELETE, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    var status = response.StatusCode;
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

        public static CloudService Get(Subscription subscription, string cloudServiceName)
        {
            try
            {
                var relativePath = string.Format("cloudServices/{0}", cloudServiceName);
                using (var response = RequestResponseHelper.GetResponse(subscription, relativePath, HttpMethod.GET, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    using (var streamReader = new StreamReader(response.GetResponseStream()))
                    {
                        string responseBody = streamReader.ReadToEnd();
                        XElement xe = XElement.Parse(responseBody);
                        var name = xe.Element(XName.Get("Name", Constants.Namespace)).Value;
                        var label = xe.Element(XName.Get("Label", Constants.Namespace)).Value;
                        var description = xe.Element(XName.Get("Description", Constants.Namespace)).Value;
                        var geoRegion = xe.Element(XName.Get("GeoRegion", Constants.Namespace)).Value;
                        return new CloudService()
                        {
                            Name = name,
                            Label = label,
                            Description = description,
                            GeoRegion = geoRegion,
                        };
                    }
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }

        public static List<CloudService> GetAll(Subscription subscription)
        {
            try
            {
                using (var response = RequestResponseHelper.GetResponse(subscription, "cloudServices", HttpMethod.GET, RequestResponseHelper.CONTENT_TYPE_XML, null))
                {
                    using (var streamReader = new StreamReader(response.GetResponseStream()))
                    {
                        string responseBody = streamReader.ReadToEnd();
                        List<CloudService> cloudServices = new List<CloudService>();
                        XElement xe = XElement.Parse(responseBody);
                        foreach (var elem in xe.Elements(XName.Get("CloudService", Constants.Namespace)))
                        {
                            var name = elem.Element(XName.Get("Name", Constants.Namespace)).Value;
                            var label = elem.Element(XName.Get("Label", Constants.Namespace)).Value;
                            var description = elem.Element(XName.Get("Description", Constants.Namespace)).Value;
                            var geoRegion = elem.Element(XName.Get("GeoRegion", Constants.Namespace)).Value;
                            cloudServices.Add(new CloudService()
                            {
                                Name = name,
                                Label = label,
                                Description = description,
                                GeoRegion = geoRegion,
                            });
                        }
                        return cloudServices;
                    }
                }
            }
            catch (WebException webEx)
            {
                using (var resp = webEx.Response)
                {
                    using (var streamReader = new StreamReader(resp.GetResponseStream()))
                    {
                        string errorMessage = streamReader.ReadToEnd();
                    }
                }
                throw;
            }
        }
    }
}

Summary

That’s it for this post. I hope you will find it useful. As always, if you find any issues with the code or anything else in the post please let me know and I will fix it ASAP.

In the next post, we will talk about Job Collections so hang tight :).

So long!!!


[This is the latest product I'm working on]