Search code examples
c#restazurewebclientazure-batch

Create azure batch pool using Azure Batch Restful API, encounter exception


I am trying to create a pool using RESTful API. I know there is C# library for batch service, but in order to programmingly specify the subnet id, I have to use RESTful API to create it which I read about in this MSDN article.

My Post URI follow the format

https://{account-name}.{region-id}.batch.azure.com/pools?api-version={api-version}

Code

 using (var client = new WebClient())
 {
     client.Headers[HttpRequestHeader.ContentType] = "application/json";
     client.Headers[HttpRequestHeader.Authorization] = "SharedKey <AccountName>:<Signature>";
     client.Headers[HttpRequestHeader.Date] = DateTime.UtcNow.ToString();
     try 
     {
         result = client.UploadString(baseURI, "POST", json);
     } 
     catch(Exception ex)
     {
         Console.WriteLine(ex.StackTrace);
     }
     Console.WriteLine(result);
 }

The json I sent: {"Id":"DotNetPool","vmSize":"small"}

at System.Net.WebClient.UploadDataInternal(Uri address, String method, Byte[] data, WebRequest& request) at System.Net.WebClient.UploadString(Uri address, String method, String data)
at System.Net.WebClient.UploadString(String address, String method, String data) at batchServer.Program.createPool(String poolId, String machineSize, String osFamily, String subnetId, String commandLine, Int32 numberOfMachine, List`1 resourceFiles) in C:\Users\fange\Downloads\ALMTest-master\batchServer\Program.cs:line 61

Can anyone help me out?


Solution

  • Based on the code you provided, I tested on my side and reproduced this issue. When you debug the code, you could find the detailed error as follows:

    As far as I know, some common headers are considered restricted and are protected by the system and cannot be set or changed in a WebHeaderCollection object, you could follow this tutorial.

    For a simple way, I recommend that you could use HttpWebRequest instead of WebClient to achieve your purpose. Here is my testing code for you to create a pool using RESTful API.

    public static void CreatePoolViaRestAPI(string baseUrl, string batchAccountName, string batchAccountKey,string jsonData)
    {
        string verb = "POST";
        string apiVersion= "2016-07-01.3.1";
        string ocpDate= DateTime.UtcNow.ToString("R");
        string contentType = "application/json; odata=minimalmetadata; charset=utf-8";
        string reqUrl = string.Format("{0}/pools?api-version={1}", baseUrl, apiVersion);
    
        //construct the request
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(reqUrl);
        request.Method = verb;
        //Set ContentType
        request.ContentType = contentType;
        //Set ocp-date
        request.Headers.Add("ocp-date", ocpDate);
        var buffer = Encoding.UTF8.GetBytes(jsonData);
        request.ContentLength = buffer.Length;
    
        #region generate the signature
        string CanonicalizedHeaders = string.Format("ocp-date:{0}", ocpDate);
        string CanonicalizedResource = string.Format("/{0}/pools\napi-version:{1}", batchAccountName, apiVersion);
        string stringToSign = string.Format("{0}\n\n\n{1}\n\n{2}\n\n\n\n\n\n\n{3}\n{4}",
            verb,
            buffer.Length,
            contentType,
            CanonicalizedHeaders, CanonicalizedResource);
        //encode the stringToSign
        string signature = EncodeSignStringForSharedKey(stringToSign, batchAccountKey);
        #endregion
    
        //Set Authorization header
        request.Headers.Add("Authorization", string.Format("SharedKey {0}:{1}", batchAccountName, signature));
        using (var rs = request.GetRequestStream())
        {
            rs.Write(buffer, 0, buffer.Length);
        }
    
        //send the request and get response
        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            Console.WriteLine("Response status code:{0}", response.StatusCode);
        }
    }
    

    Note: The cloudServiceConfiguration and virtualMachineConfiguration properties are mutually exclusive and only one of the properties can be specified. If neither is specified then the Batch service returns Bad Request (400). Therefore, the jsonData parameter in the function above should look like as follows:

    "{\"id\":\"DotNetPool\",\"vmSize\":\"small\",\"cloudServiceConfiguration\":{\"osFamily\":\"4\"}}"
    

    UPDATE:

    The method for encoding stringToSign would look like as follows:

    public string EncodeSignStringForSharedKey(string stringToSign, string accountKey)
    {
        HMACSHA256 h = new HMACSHA256(Convert.FromBase64String(accountKey));
        var byteArray = h.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
        string signature = Convert.ToBase64String(byteArray);
        return signature;
    }
    

    Details you could follow Authentication via Shared Key.