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”.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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]