Discovery for MS Clusters of Any Kind

6 minute read

Often when developing an OpsMgr management pack for server class applications, we need to be cluster-aware. Sometimes workflows don’t need to run on a cluster, sometimes, the workflow should only be executed on a cluster. (i.e. I wrote a monitor that runs on a Windows 2008 R2 Hyper-V cluster once a day and check if all virtual machines are hosted by their preferred cluster nodes.)

There are many good articles out there explaining cluster-aware discoveries in OpsMgr management packs:

However, in many occasions, only using the “IsVirtualNode” property from the Windows Server class (Microsoft.Windows.Server.Computer) is not enough (or granular enough) to identify the specific clusters.

I’m explain what I mean using an example.

For example, I have a 2-node SQL cluster configured as below:

  • Node 1 name: blablablaSQL01A
  • Node 2 name: blablablaSQL01B
  • Cluster Name: blablablaSQL01C
  • DTC Cluster Resource Access Name: blablablaSQL01D
  • SQL Server Cluster Resource Access Name: blablablaSQL01E

image

After installing the OpsMgr agent on both cluster nodes and enabled Agent-Proxy for them, totally 5 Windows Server objects will be discovered, one for each name mentioned above:

image

As shown above, other than the 2 cluster nodes, other 3 instances have “IsVirtualNode” property set to “True”.

When I looked at the “Computer” state view in the Microsoft SQL management pack, all 5 “Windows Server” are listed as the computer which has SQL installed:

image

If I create a discovery for SQL Clusters based on any computers have SQL DB Engine installed and is a virtual node, I would have discovered 3 instances (SQL01C, SQL01D and SQL 01E) for the same cluster.

If I only want to discover the cluster itself (blablablaSQL01C), I believe the discovery needs to perform the following checks:

01. The Windows Server Is Virtual Node

After a bit of digging, I found the “Windows Clustering Discovery” from Windows Cluster Library MP sets “IsVirtualNode” to True:

image

02. The existence of Cluster Service

I’m not sure if there are any management packs other than Windows Clustering Library out there that set IsVirtualNode to True. So , to be safe, I would also configure my discovery to look for the Cluster service.

03. The cluster is actually hosting my application

This is done via a WMI query to the MSCluster_Resource class in root\MSCluster name space.

In order to identify the cluster is hosting my application, I need to find if there are any cluster resources that has a specific resource type and also has a name that matches my search string.

i.e. I have access to 3 kinds of clusters in my work environment. I’ll list the WMI query for each cluster type:

SQL Cluster: “Select * from MSCluster_Resource Where Type = ‘SQL Server’ And Name LIKE ‘SQL Server’

Hyper-V Cluster: “Select * from MSCluster_Resource Where Type = ‘Virtual Machine’ And Name LIKE ‘Virtual Machine %’”

Note: This is because each VM in Hyper-V cluster have a resource name of “Virtual Machine ".

OpsMgr 2007 RMS Cluster: “Select * from MSCluster_Resource Where Type = ‘Generic Service’ And Name LIKE ‘System Center Data Access

As you can see, I believe in order to accurately identify my application, both cluster resource type and name need to match. Only using resource type in WMI query is not enough because the resource type could be “Generic Service”.

04. The computer name matches the cluster name

Because I am only interested in the actual cluster, not client access names for cluster resource groups, the computer name of the Windows Server instance needs to match the cluster name. I can read the cluster name in registry “HKLM\SYSTEM\CurrentControlSet\Services\ClusSvc\Parameters\ClusterName

In my sample MP, I created a class based on Microsoft.Windows.ComputerRole for my cluster and created a Timed Script Discovery based on the 4 criteria mentioned above.

Note: I know that using a script discovery targeting a wide range (all windows servers) is not ideal. I couldn’t manage to write a custom discovery module that meets my requirements. for example, the computer name could be in capital but the cluster name could be in lower case, System.ExpressionFilter (which is used by filtered registry discovery module) does not support case insensitive regular expression match (More Info). Therefore in my script, I have many IF statements nested. for example, if the windows server is not a virtual node, at the first if statement, it would not meet the if criteria and bypass the rest of the script, jump to the end of the script and submit an empty set of discovery data. I’ve done it this way to ensure the script does not continue running if one criteria is not met.

Again, using SQL clusters as an example, I created a class called “SQL Server Cluster”, and only the actual clusters (name ends with letter “C”) are discovered:

image

In order to re-use the code, I have create a snippet template in VSAE. This snippet template includes the class definition, discovery workflow and associated language pack (ENU) display strings.

Here’s the code for the snippet template:

<ManagementPackFragment SchemaVersion="1.0">
 <TypeDefinitions>
 <EntityTypes>
 <ClassTypes>
 <ClassType ID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole" Base="#alias('Microsoft.Windows.Library')#!Microsoft.Windows.ComputerRole" Accessibility="Public" Abstract="false" Hosted="true" Singleton="false">
 <Property ID="ClusterName" Key="false" Type="string" />
 </ClassType>
 </ClassTypes>
 </EntityTypes>
 </TypeDefinitions>
 <Monitoring>
 <Discoveries>
 <Discovery ID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole.Discovery" Target="#alias('Microsoft.Windows.Library')#!Microsoft.Windows.Server.Computer" Enabled="true" ConfirmDelivery="false" Remotable="true" Priority="Normal">
 <Category>Discovery</Category>
 <DiscoveryTypes>
 <DiscoveryClass TypeID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole">
 <Property TypeID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole" PropertyID="ClusterName" />
 </DiscoveryClass>
 </DiscoveryTypes>
 <DataSource ID="DS" TypeID="#alias('Microsoft.Windows.Library')#!Microsoft.Windows.TimedScript.DiscoveryProvider">
 <IntervalSeconds>3600</IntervalSeconds>
 <SyncTime />
 <ScriptName>ClusterDiscovery.vbs</ScriptName>
 <Arguments>$MPElement$ $Target/Id$ $Target/Property[Type="#alias('Microsoft.Windows.Library')#!Microsoft.Windows.Computer"]/PrincipalName$ "#text('Cluster Resource Type')#" "#text('Cluster Resource Name Search String')#" "$Target/Property[Type="#alias('Microsoft.Windows.Library')#!Microsoft.Windows.Server.Computer"]/IsVirtualNode$"</Arguments>
 <ScriptBody>
 <![CDATA[
'========================================================
' AUTHOR: Tao Yang
' Script Name: ClusterDiscovery.vbs
' DATE: 20/03/2014
' Version: 1.0
' COMMENT: Script to discover failover clusters
'========================================================
On Error Resume Next
SourceID = WScript.Arguments(0)
ManagedEntityID = WScript.Arguments(1)
strComputer = WScript.Arguments(2)
strCLResType = WScript.Arguments(3)
strCLResName = WScript.Arguments(4)
'IsVirtualNode property from Windows.Server.Computer class is either true or empty. never false
IF NOT IsNull(WScript.Arguments(5)) THEN
bIsVirtualNode = WScript.Arguments(5)
END IF

'Declare variables
const HKEY_LOCAL_MACHINE = &H80000002

Set oAPI = CreateObject("MOM.ScriptAPI")
Set oDiscoveryData = oAPI.CreateDiscoveryData(0,SourceID,ManagedEntityID)

'Only continue if IsVirtualNode = "True"
IF UCase(bIsVirtualNode) = "TRUE" Then
 'Check if Failover Cluster service exists
 strKeyPath = "SYSTEM\CurrentControlSet\Services\ClusSvc"
 'connect to the registry provider
 Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\default:StdRegProv")
 If oReg.EnumKey(HKEY_LOCAL_MACHINE, strKeyPath, arrSubKeys) = 0 Then
 'Cluster Service exists, continue, check if specified cluster resource exists
 bCLRes = False
 CLResWMIQuery = "Select * from MSCluster_Resource Where Type = '" & strCLResType &"' AND Name LIKE '" & strCLResName & "'"
 Set objWMICluster = GetObject("winmgmts:\\" & strComputer & "\root\MSCluster")
 Set ColCLRes = objWMICluster.ExecQuery (CLResWMIQuery)
 For Each objCLRes in ColCLRes
 bCLRes = TRUE
 Next

'NetBIOS Computer Name
 ComputerName = Split(strComputer, ".", -1)(0)
 'Read Cluster name from registry
 strCLKeyPath = "SYSTEM\CurrentControlSet\Services\ClusSvc\Parameters"
 strCLNameValue = "ClusterName"
 oReg.GetStringValue HKEY_LOCAL_MACHINE,strCLKeyPath, strCLNameValue,strClusterName

'Proceed if NetBIOS Computer Name equals to cluster name
 If UCase(ComputerName) = UCase(strClusterName) Then
 IF bCLRes = TRUE THEN
 Set oInstance = oDiscoveryData.CreateClassInstance("$MPElement[Name='#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole']$")
 oInstance.AddProperty "$MPElement[Name='#alias('Microsoft.Windows.Library')#!Microsoft.Windows.Computer']/PrincipalName$", strComputer
 oInstance.AddProperty "$MPElement[Name='System!System.Entity']/DisplayName$", strComputer
 oInstance.AddProperty "$MPElement[Name='#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole']/ClusterName$", UCase(strClusterName)
 oDiscoveryData.AddInstance(oInstance)
 END IF
 END IF
 End If
END IF
oAPI.Return oDiscoveryData
 ]]></ScriptBody>
 <TimeoutSeconds>120</TimeoutSeconds>
 </DataSource>
 </Discovery>
 </Discoveries>
 </Monitoring>
 <LanguagePacks>
 <LanguagePack ID="ENU" IsDefault="true">
 <DisplayStrings>
 <DisplayString ElementID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole">
 <Name>#text('Class DisplayName')#</Name>
 <Description></Description>
 </DisplayString>
 <DisplayString ElementID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole" SubElementID="ClusterName">
 <Name>Cluster Name</Name>
 <Description></Description>
 </DisplayString>
 <DisplayString ElementID="#text('MP Id')#.#text('Cluster Type')#.Cluster.ComputerRole.Discovery">
 <Name>#text('Class DisplayName')# Discovery</Name>
 <Description>Script discovery for #text('Class DisplayName')#</Description>
 </DisplayString>
 </DisplayStrings>
 </LanguagePack>
 </LanguagePacks>
</ManagementPackFragment>

When using this template, for each cluster that you want to define and discover in your MP, simply supply the following information:

image

  • MP Id: the ID (or the prefix) of your MP. this is going to be used as the prefix for all the items defined in the snippet.
  • Cluster Type: the type (or common name) of your cluster. i.e. SQL, Hyper-V, etc.
  • Cluster Resource Type: The value of the “Type” property of the MSCluster_Resource WMI instance.

image

  • Cluster Resource Name Search String: the search string for the cluster resource name.

image

The “SQL Server Cluster” discovered in the previous screenshot is created using this snippet template.

You can also download the snippet template here.

Leave a comment