Custom Resource Providers in Windows Azure Pack - Extending the Hello World Sample calling a SMA Runbook

Custom Resource Providers in Windows Azure Pack - Extending the Hello World Sample calling a SMA Runbook

  • Comments 27
  • Likes

Today we have a guest blogger! Torsten Kuhn, Principal Consultant from Microsoft Consulting Services in Germany will walk us through the process of extending the Hello World Custom Resource Provider to call a SMA Runbook!

Hello readers I'm Torsten Kuhn a Principal Consultant from Microsoft Services and in this post I will walk you through the different steps required to programmatically extend the Hello World custom Resource Provider sample that is included in the Windows Azure Pack Developers Kit. This post is based on the Azure Pack example resource provider 'Hello World'. In this blog post I'll show all the necessary steps to extend the 'Hello World' example from the UI to the backend REST API with custom code.

Dear readers! – You should be familiar with: Visual Studio, REST API's, JavaScript and Compile, Compile, Compile… J

This blog post goes in line with the post from Victor Arzate that describes what Resource Providers are and how you can deploy the Hello World sample. You can learn more about the Hello World sample here and also, you can learn more about custom resource providers here.

Prerequisites

  • Visual Studio 2012 installed (in my case I had Update 4 applied as well). Note that Visual Studio 2012 does not need to be installed in the same machine where you have Windows Azure Pack (WAP) installed.
  • Windows Installer XML (WiX) toolset 3.7.1224.0 installed (direct download: http://wixtoolset.org/releases/feed/v3.7)
  • A Windows Azure Pack environment up & running.
  • Hello World Custom Resource Provider deployed as described in the blog post from Victor Arzate here.
  • Service Management Automation (SMA) from the System Center 2012 R2 Orchestrator DVD a trial version could be downloaded here. This blog uses the "Sample-Using-RunbookParameters" runbook that is included in the SMA extension of the WAP Admin Site.

This blog explains the following steps to extend the Hello World Sample with a call to a SMA Runbook:

Step 1 - Create the backend REST API

Step 2 - Implement the call to the SMA Runbook

Step 3 - Changes to the Hello World API client

Step 4 - Call into the REST API from the MVC controller of the tenant site

Step 5 - Implement the UI logic that triggers the call to the backend service

 

Step 1 – Create the backend REST API

Launch Visual Studio 2012 and open the Hello World Solution located in the folder where you extracted the sample archive (in my case C:\Projects\Microsoft.WAP.Samples.HelloWorld\ Microsoft.WAP.Samples.HelloWorld.sln). Navigate to the Microsoft.WAP.Samples.HelloWorld.Api project. Open the WebApiConfig.cs located in the App_Start folder and add the following lines:

config.Routes.MapHttpRoute(

name: "ExecuteRunbook",

routeTemplate: "subscriptions/{subscriptionId}/executerunbook",

defaults: new { controller = "FileShare" });

This will route the ExecuteRunbook call to the FileShareController. Go to the FileShareController.cs and add the following empty function stub into the FileShareController class (in step 2 we will implement the call to the REST API):

[HttpPost]

[System.Web.Http.ActionName("executerunbook")]

public void ExecuteRunbook(string subscriptionId, RunbookParameter runbookParameters)

{

 

}

We will pass the runbook parameters in a custom RunbookParameter class. In order to create this class navigate to the Microsoft.WAP.Samples.HelloWorld.ApiClient project select the DataContracts folder, add a new class named RunbookParameter and fill in the following code:

using System;

using System.Runtime.Serialization;

 

namespace Microsoft.WindowsAzurePack.Samples.HelloWorld.ApiClient.DataContracts

{

[DataContract(Namespace = Constants.DataContractNamespaces.Default)]

public class RunbookParameter

{

[DataMember(Order = 0)]

public string Name { get; set; }

[DataMember(Order = 1)]

public int Number { get; set; }

[DataMember(Order = 2)]

public string[] StringArray { get; set; }

[DataMember(Order = 3)]

public DateTime Date { get; set; }

[DataMember(Order = 4)]

public Boolean SayGoodbye { get; set; }

}

}

Make sure that the System.Runtime.Serialization assembly is referenced by the project.

 

Step 2 – Implement the call to the SMA Runbook

Now we will fill in the code to call the SMA Runbook. First we need to add a service reference to the SMA web service we installed in the prerequisites section. Make sure that the Microsoft.WAP.Samples.HelloWorld.Api project is selected and use the "Add Service Reference" command from the projects context menu. Enter the URL to the endpoint of the Orchestrator web site (on my machine https: \\theserver:9090) and add the empty GUID parameter 00000000-0000-0000-0000-000000000000 as displayed in the picture otherwise the call to the service endpoint will fail with a 404 HTTP error. Use SMAWebservice as the namespace:

Open the file FileShareController.cs file in the Controllers subfolder and add the following using statements:

using System;

using System.Collections.Generic;

using System.Globalization;

using System.Data.Services.Client;

using System.Net;

using System.Net.Security;

using System.Threading.Tasks;

using Microsoft.WindowsAzurePack.Samples.HelloWorld.Api.SMAWebService;

using IO = System.IO;

using System.Linq;

using System.Web.Http;

using Microsoft.WindowsAzurePack.Samples.HelloWorld.ApiClient.DataContracts;

Then navigate to the ExecuteRunbook empty function that we created in step 1 and replace it with the following code:

[HttpPost]

[System.Web.Http.ActionName("executerunbook")]

public void ExecuteRunbook(string subscriptionId, RunbookParameter rbParameter)

{

var api = new OrchestratorApi(new Uri("https://theserver:9090//00000000-0000-0000-0000-000000000000"));

((DataServiceContext)api).Credentials = CredentialCache.DefaultCredentials;

ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });

 

var runbook = api.Runbooks.Where(r => r.RunbookName == "Sample-Using-RunbookParameters").AsEnumerable().FirstOrDefault();

if (runbook == null) return;

 

var runbookParams = new List<NameValuePair>

{

new NameValuePair() {Name = "Name", Value = rbParameter.Name},

new NameValuePair() {Name = "Number", Value = rbParameter.Number.ToString(CultureInfo.InvariantCulture)},

new NameValuePair() {Name = "StringArray", Value = string.Join(",", rbParameter.StringArray)},

new NameValuePair() {Name = "Date", Value = rbParameter.Date.ToLongDateString()},

new NameValuePair() {Name = "SayGoodbye", Value = rbParameter.SayGoodbye.ToString(CultureInfo.InvariantCulture)}

};

 

OperationParameter operationParameters = new BodyOperationParameter("parameters", runbookParams);

var uriSma = new Uri(    string.Concat(api.Runbooks, string.Format("(guid'{0}')/{1}", runbook.RunbookID, "Start")),UriKind.Absolute);

var jobIdValue = api.Execute<Guid>(uriSma, "POST", true, operationParameters) as QueryOperationResponse<Guid>;

if (jobIdValue == null) return;

 

var jobId = jobIdValue.Single();

Task.Factory.StartNew(() => QueryJobCompletion(jobId));

}

 

private void QueryJobCompletion(Guid jobId)

{

 

}

Please note that in the last line in I started a new background task with a delegate to QueryJobCompletion. This function queries for job completion and handles the status update for the called runbook. In a future blog I will explain the different options we have to give feedback from long running backend operations back to the WAP portal user.

 

Step 3 – Changes to the HelloWorld API client

The HelloWorld API client (Microsoft.WAP.Samples.HelloWorld.ApiClient) hides all the complexity when calling the backend REST API's. The Tenant site MVC controller only makes simple function calls to the client API and then the client creates the target Uri's and makes the necessary HTTP PUT and GET calls to the backend, transforms the results in a way that makes it easy for the UI part of the portal to use them. Navigate to the Microsoft.WAP.Samples.HelloWorld.ApiClient project and open the HellworldClient.cs file. Add the following constant as the URI template for the new REST API at the top off HelloWorldClient class:

private const string TenantExecuteRunbook = "{0}/" + RegisteredPath + "/executerunbook";

Then add the following function to the same class:

public async Task ExecuteRunbook(string subscriptionId, RunbookParameter rb)

{    

var requestUrl =

this.CreateRequestUri(

string.Format(CultureInfo.InvariantCulture,

TenantExecuteRunbook, subscriptionId));

await this.PostAsync<RunbookParameter>(requestUrl, rb);

}

 

 

Step 4 – Call into the REST API from the MVC controller of the tenant site

Now we will add the new HelloWorldClient API call to the Tenant site's HelloWorldTenantController. To do so navigate to the Microsoft.WAP.Samples.HelloWorld.TenantExtension project and open the Models subfolder. Create a new RunbookParameterModel class that is used to pass the parameter values for the REST API from the UI to the HelloWorldTenantController and add the following code fragment:

using Microsoft.WindowsAzurePack.Samples.HelloWorld.ApiClient.DataContracts;

public class RunbookParameterModel

{

public string Name { get; set; }

public int Number { get; set; }

public string[] StringArray { get; set; }

public DateTime Date { get; set; }

public Boolean SayGoodbye { get; set; }

 

public RunbookParameter ToApiObject()

{

return new RunbookParameter()

{

Name = this.Name,

Number = this.Number,

StringArray = this.StringArray,

Date = this.Date,

SayGoodbye = this.SayGoodbye

};

}

}

 

Open the HelloWorldTenantController.cs and add the following method to the existing code:

[HttpPost]

[ActionName("ExecuteRunbook")]

public async Task<JsonResult> ExecuteRunbook(string subscriptionId,RunbookParameterModel rb)

{

await ClientFactory.HelloWorldClient.ExecuteRunbook(subscriptionId,rb.ToApiObject());

return this.Json("Success");

}

 

 

Step 5 – Implement the UI logic that triggers the call to the backend service

The last piece of the puzzle is the JavaScript code we need to trigger the call to the ExcecuteRunbook API from the user interface. For simplicity reasons we make the call from a button event handler, the parameter values are passed as constant values. In one of my next blogs I will show you how to collect the parameter values from a wizard based UI.

  • Open the HelloWorldTenant.Controller.js located in the TenantExtension\Content\Scripts subfolder and add the following function:

function executeRunbook(subscriptionId, name, number, stringArray, date, sayGoodbye) {

return Shell.Net.ajaxPost({

data: {

subscriptionId: subscriptionId,

Name: name,

Number: number,

StringArray: stringArray,

Date: date,

SayGoodbye: sayGoodbye

},

url: baseUrl + "/ExecuteRunbook"

});

}

 

  • In order to make the function accessible from the file share tab navigate to the bottom of the controller file and add the following line:

global.HelloWorldTenantExtension = global.HelloWorldTenantExtension || {};

global.HelloWorldTenantExtension.Controller = {

createFileShare: createFileShare,

listFileSharesUrl: listFileSharesUrl,

getFileShares: getFileShares,

getfileSharesData: getfileSharesData,

navigateToListView: navigateToListView,

executeRunbook: executeRunbook

};

 

  • Open the HellworldTenant.fileSharesTab.js file and navigate to the updateContextualCommands function, add the following line:

Exp.UI.Commands.Contextual.add(new Exp.UI.Command("Execute", "Execute",Exp.UI.CommandIconDescriptor.getWellKnown("Start"), true, null,onExecuteRunbook));

 

  • In the same file add the following command handler:

function onExecuteRunbook() {

var subscriptionId =

    global.Exp.Rdfe.getSubscriptionsRegisteredToService("helloworld")[0].id,

name = "test",

number = 100,

stringArray = new Array("value1", "value2", "value3"),

date = new Date(),

sayGoodbye = true;

 

var promise = HelloWorldTenantExtension.Controller.executeRunbook(subscriptionId, name,

            number, stringArray, date, sayGoodbye);

global.waz.interaction.showProgress(

promise,

{

initialText: "Executing runbook...",

successText: "Runbook launched successfully.",

failureText: "Failed to execute runbook."

}

);

promise.done(function () {

});

}

 

Now compile the solution. As a result you will get a new version of the MSI setup for the HelloWorld sample. If you followed all instruction in Victor Arzate's blog you have already an installed and registered version of HelloWorld. Make sure to uninstall the existing version in control panel and install the new one but don't touch the extension registration. Login to the Azure Pack Tenant Site and navigate to the HelloWorld extension:

You see your new Execute button and can now launch the runbook.

 

Until next time! Torsten

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • hi,Why is my "Hello World" reveals no activated?

  • Hi zhengyq - Could you provide more details on your question? Thanks!

  • thanks Victor Arzate! my "Hello World" Add Service Reference SMA web service. WAP admin portal "hello world"
    “file Servers” and “products” label finds "https://azureportal.contoso.com:30004/services/helloworld/fileservers " 404 error. but delete SMA web service all right.

  • hi Victor Arzate thank you. My "Hello world" add service reference SMA Web Service , do not modify any code , but file servers label found "https://azureportal.contoso.com:30004/services/helloworld/fileservers" 404 error. delete SMA Web Service , all right

  • hi Victor Arzate. my "Hello World" add a service reference to the SMA web service ,do not modify any code,Admin portal "FILESERVER" label found "https://azureportal.contoso.com:30004/services/helloworld/fileservers" 404 error. but delete SMA web service, all right.

  • Hi Venkat
    my "hello world" add a service reference to the SMA web service , do not modify any code , in the admin portal "fileserver" label found 404 error. users can not subscribe to plan, because the display is not activated. but delete SMA web service , is all right.

  • hi,my email is zhengyiqun@outlook.com. my messages are not approved

  • Hi victor thanks. My "Hello World" add a service reference to the SMA web service , do not modify any code . access to "fileserver" label get 404 error . delete the reference, you can access. Rerun the register script problem persists

  • Hi, you could add a Service reference to the SMA Web Service only by following "Step 2" and adding an empty GUID Parameter (see the screenshot) to the end of the SMA url. In my dev environment for example the URL is https: \\theserver:9090\00000000-0000-0000-0000-000000000000 . If the empty GUID is missing you will get a HTTP error 404. Regards Torsten

  • hi,Torsten Kuhn thank. “step2” is Ok, add sma web service reference is ok, my spf service and azure pack portal in the two VM, installed "msi" is ok , open admin portal "hello world" label is the blank. debug request is 404. bug portal and spf service in the one VM is ok

  • but portal and spf service in the one VM is ok

  • Hi BrightBlade_z,
    The SMA runbook is called from the Tenant Site and not from the Admin Site. As a prerequisite make sure
    that the HelloWorld Sample is properly installed and registered following the steps described in the deployment post (from Victor Arzate). Make sure that the basic HelloWorld sample is running without problems on your machine. Launching the tenant site you should see the file shares view selecting the Hello World node.
    Where comes the HTTP error 404 from ? Did you find it in the logs ?
    Regards Torsten

  • hi Torsten Kuhn
    if no reference to the SMA Web Service the “hello world” sample is ok, in the tenant site i can see the file shares view selecting the Hello World node, debug request state is 200. but reference the SMA Web Service , "hello world" node is blank, request state is 404.

  • portal and sma service in the two vm is bad can't see the list of shares, one vm is good.

  • 404 in the VS debug, request "https://azureportal.contoso.com:30004/services/helloworld/fileservers" state is 404, but portal and sma service in the one vm and no reference the SMA web service the request state is 200.