First published on TECHNET on Jul 03, 2014
Introduction
Special thanks to Srimathi Santhanam, Thomas Yip, and Filippo Seracini (a.k.a. The Doctor) for putting together and testing the examples in this post.
Following the release of System Center 2012 R2 and Windows Azure Pack in fall of 2013, we have been getting an increasing number of inquiries from people wanting help on how to think about support for VMM
Service Template
s in our IaaS APIs. To be completely clear: Service Provider Foundation has included support for managing and using VMM service templates since its inception.
The most likely reason for people asking questions is the very strong emphasis on Azure consistency which permeates all our efforts in pursuit of the Cloud OS vision. To help people truly realize the long-term benefits of the Cloud OS (one experience, one toolset, one API) we began some time ago to encourage customers and partners to focus on
Windows Azure Pack’s Service Management API
as the entry point for programming against IaaS capabilities in the part of the Cloud OS powered by Windows Server & System Center. From this perspective, Service Provider Foundation is merely the conduit between Windows Azure Pack’s Service Management API and System Center, not the primary IaaS API itself.
However, also because of the emphasis on Azure consistency, the Service Management API in Windows Azure Pack does not include support for VMM service templates. And while it does include support for the Azure-consistent
VM Role artifact
, which is a similar concept, this newer artifact is currently lacking some capabilities of VMM service templates (e.g. multiple tiers in a single definition).
So, while we wait on VM Role evolution to close the gaps, what should people do? The answer is to take a hybrid approach: continue to focus on Windows Azure Pack’s Service Management API for your IaaS programming efforts, but also use Service Provider Foundation for VMM service templates if you want programmatic access to those capabilities. Done correctly, you can retain the Windows Azure Pack plan/subscription context and also make use of VMM service templates if you need to.
To provide support for service templates, you first have to add a service template to the tenant subscription, and then execute the desired operations on the service (create, delete, scale out, etc.).
The following information describes how to do this, with generalized examples (i.e. you will need to edit these examples for them to function properly in your deployed environment).
To add a Service Template to a Tenant Subscription via Service Management Automation (SMA) Runbook
The Admin provides tenants (subscribed through
WAP Tenant API
) with access to a
Service Template
through an SMA runbook that gets triggered by SPF when the tenant creates a subscription:
-
Open Admin console in the WAP portal, access the “Automation” section (left nav pane) and create a new runbook
-
Paste the script you see below into the new runbook
-
Replace the variables with your environment-specific values
-
Publish the runbook, and make sure to enter the tag value of “SPF” into the runbook configuration (just those three letters, no quotes, this is critical else you won’t see the runbook in the next step), click save
-
Access the “VM Clouds” section, “Automation” tab, and create a new event-to-runbook mapping: object = “subscription”; action = create; runbook = the one you just created, whatever you named it
workflow AddServiceTemplateToSubscription
{
param
(
[Parameter(Mandatory=$true)]
[string] $name,
[Parameter(Mandatory=$true)]
[string] $operation,
[Parameter(Mandatory=$true)]
[object] $resourceObject
)
#only perform action when it is subscription create
if (($name -ne "Subscription") -or ($operation -ne "Create"))
{
throw "Mismatch operation. Finishing runbook"
}
$subscriptionName = <your subscriptionName>
if ($resourceObject.SubscriptionName -eq $subscriptionName)
{
$UserRoleId = $resourceObject.SubscriptionId
#define variable for service template we are going to use
$serviceTemplateName = <your ServiceTemplateName>
$serviceTemplateRelease = <your ServiceTemplateRelease>
$SystemCred = <Your Credentials>
$VmmServer1 = <Your VMM Server>
if ($VmmServer1 -eq $null -or $VmmServer1 -eq "") {
throw "Cannot get VMM Instance"
}
if ($SystemCred -eq $null -or $SystemCred -eq "") {
throw "Cannot get SystemCred"
}
try
{
InlineScript
{
Import-Module virtualmachinemanager
$VmmServer = get-scvmmserver -computername localhost
$UserRole = ""
$count = 0
do # it may take a while for the userrole to be created
{
$Userrole = Get-SCUserRole -ID $using:UserRoleID -VMMServer $VmmServer
sleep 10
$count++
} while (($UserRole -eq $null) -and ($count -lt 20))
if ($UserRole -eq $null)
{
throw "UserRole is not created in VMM"
}
#get service template
$serviceTemplate = get-SCServiceTemplate -Name $using:serviceTemplateName | where {$_.Release -eq $using:serviceTemplateRelease}
if ($serviceTemplate -eq $null)
{
throw "Service Template not found"
}
#Grant permission
Grant-SCResource -Resource $serviceTemplate -UserRoleName $UserRole.Name
} -PSComputerName $VmmServer1 -PSCredential $SystemCred
}
catch
{
throw $_
}
}
}
To create a new Service by using .NET
-
Connect to the Service Provider Foundation
VMM
service.
-
Create a new method. Let’s call it CreateService.
-
Create a new instance of
VMConfiguration
class.
-
Add the VMConfiguration instance to a new instance of the
ObservableCollection<VMConfiguration>
class.
-
Create a new instance of
ServiceTierAndVMConfiguration
class.
-
Set the
Name
property to match the computer tier name in the service template you are deploying.
-
Set the
VMConfigurations
property with the previously created instance of the ObservableCollection<VMConfiguration> class.
-
Create a new instance of the
ObservableCollection<ServiceTierAndVMConfiguration>
class.
-
Add the instance of
ServiceTierAndVMConfiguration
to it.
-
Create a new instance of the
ServiceDeploymentConfiguration
class.
-
Set the
Name
property.
-
Set the
Description
property.
-
Create a new instance of the
NewServiceDeployment
class
-
Set the
ServiceConfiguration
property with the instance of NewServiceDeployment previously created.
-
Set the
TierConfiguration
property with the instance of ObservableCollection<ServiceTierAndVMConfiguration> previously created.
-
Create a new instance of
Service
.
public Service CreateService(string subscriptionId, string name, ServiceTemplate serviceTemplate, Guid stampId, Guid cloudId)
{
ServiceDeploymentConfiguration sdc = new ServiceDeploymentConfiguration()
{
Name = <name of your service deployment configuration>,
Description = <description of your service deployment configuration>
};
// Depending on the Service Template create the VMTier and VMConfig
ObservableCollection<ServiceTierAndVMConfiguration> serviceTierAndVMConfiguration = new ObservableCollection<ServiceTierAndVMConfiguration>();
ObservableCollection<VMConfiguration> vmConfigurations = new ObservableCollection<VMConfiguration>();
VMConfiguration vmc = new VMConfiguration()
{
ComputerName = <your tier VM name>
VMName = <your VM name here>
};
vmConfigurations.Add(vmc);
ServiceTierAndVMConfiguration tierAndVMConfig = new ServiceTierAndVMConfiguration()
{
Name = "SQLServer Host Tier",
VMConfigurations = vmConfigurations
};
serviceTierAndVMConfiguration.Add(tierAndVMConfig);
// Provide ServiceTemplate ID
Guid serviceTemplateID = <your serviceTemplateID. You can retrieve the serviceTemplateID via RDFE query>
// Provide Cloud ID
Guid cloudID = <your cloudID>
// Provide Stamp ID
Guid stampID = <your stampID>
// add the ServiceDeployment
NewServiceDeployment serviceDep = new NewServiceDeployment {
ServiceConfiguration = sdc ,
TierConfigurations = serviceTierAndVMConfiguration
};
// Instantiate a VMM Service
var service = new Service()
{
Name = name,
StampId = stampID,
CloudId = cloudID,
ServiceTemplateId = serviceTemplateID
NewServiceDeployment = serviceDep
};
// POST a HTTP Request. Be sure to add the Tenant Certificate to the http request prior to this. See Snippet for Tenant Client below
HttpResponseMessage response = TenantClient.PostAsJsonAsync<Service>(TenantServiceEndPoint + subscriptionId + @"/services/systemcenter/VMM/Services/", service);
return response.Content.ReadAsAsync<Service>().Result;
}
To POST an HTTP request
In order to invoke service creation, you need to have client code executing an HTTP POST. For an example of Tenant Client, see code snippet below:
public TenantClient(string tenantServiceEndpoint, string authSiteEndPoint, string userName, string password, ref DateTime tokenExpiryTime)
{
httpClient = new HttpClient();
var identityProviderEndpoint = new EndpointAddress(new Uri(authSiteEndPoint + "wstrust/issue/usernamemixed"));
var identityProviderBinding = new WS2007HttpBinding(SecurityMode.TransportWithMessageCredential);
identityProviderBinding.Security.Message.EstablishSecurityContext = false;
identityProviderBinding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;
identityProviderBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
var trustChannelFactory = new WSTrustChannelFactory(identityProviderBinding, identityProviderEndpoint)
{
TrustVersion = TrustVersion.WSTrust13,
};
trustChannelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication() { CertificateValidationMode = X509CertificateValidationMode.None };
trustChannelFactory.Credentials.SupportInteractive = false;
trustChannelFactory.Credentials.UserName.UserName = userName;
trustChannelFactory.Credentials.UserName.Password = password;
var channel = trustChannelFactory.CreateChannel();
var rst = new RequestSecurityToken(RequestTypes.Issue)
{
AppliesTo = new EndpointReference("http://azureservices/TenantSite"),
TokenType = "urn:ietf:params:oauth:token-type:jwt",
KeyType = KeyTypes.Bearer,
};
RequestSecurityTokenResponse rstr = null;
var token = channel.Issue(rst, out rstr);
tokenExpiryTime = new DateTime(token.ValidTo.Year, token.ValidTo.Month, token.ValidTo.Day, token.ValidTo.Hour, token.ValidTo.Minute, token.ValidTo.Second, DateTimeKind.Utc);
var tokenString = (token as GenericXmlSecurityToken).TokenXml.InnerText;
var jwtString = Encoding.UTF8.GetString(Convert.FromBase64String(tokenString));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwtString);
this.tenantServiceEndPoint = tenantServiceEndpoint;
httpClient.PrincipalId = <your ID>;
}
In order for the client to work it requires a certificate, added prior to the HTTP request, as seen in the following example:
//Adding Certificate through Public Endpoint https://<>:30006/
var handler = new WebRequestHandler();
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var result = store.Certificates.Find(X509FindType.FindByIssuerName, <your trusted certificate autority>, false);
if (result.Count == 0)
{
throw new Exception("tenant public api certificate not found.");
}
X509Certificate2 cert = result[0];
handler.ClientCertificates.Add(cert);
httpClient = new HttpClient(handler);
this.tenantServiceEndPoint = tenantServiceEndpoint;
SubscriptionCertificate subscriptionCertificate = new SubscriptionCertificate()
{
SubscriptionCertificateData = <data>,
SubscriptionCertificatePublicKey = <SubscriptionCertificatePublicKey>,
SubscriptionCertificateThumbprint = <SubscriptionCertificateThumbprint>
};
httpClient.PostAsXmlAsync<SubscriptionCertificate>(TenantServiceEndPoint + @"subscriptions/" + subscriptionId + @"/certificates", subscriptionCertificate);