- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
INTRODUCTION
If you have been using ServiceNow Discovery and/or Service Mapping, then you should be familiar with Pattern Based Discovery. Patterns are the “code” that does the job of collecting information from data center devices and then populates the CMDB with that data. Patterns are made up of operations that run commands from the MID Server against target computers. Other operations process the collected data and populate attributes of CI Classes. After the pattern runs, Discovery sends that payload of the created/updated CI from the MID Server to the ECC Queue on the ServiceNow instance for processing in the CMDB.
DISCOVERY PATTERN LIMITATIONS
It may seem like Patterns are running on your ServiceNow Instance. But they are not. Patterns run as code objects on the MID Server. This means that any function calls or data accessed from a Discovery Pattern can only be made to data or code objects that are on the MID Server and not on the ServiceNow Instance. So, if you want your pattern to make calls to CMDB tables in your ServiceNow instance, this data is unavailable.
This can be limiting as use cases often arise which can only be satisfied by accessing data in ServiceNow tables such as the CMDB tables.
GETTING CMDB DATA TO THE MID SERVER
This limitation of not being able to access CMDB data during pattern execution is solved with a Pre-Pattern Execution Facility. The Pre-Pattern Execution Facility runs on the ServiceNow Instance before a pattern is executed. It can query the CMDB or any other ServiceNow tables for data. It then populates a Data Table object, and that object is passed down to the MID Server and allows the running discovery pattern to access this data.
There are 2 different types of discoveries used by Discovery Patterns: Horizontal Discovery and Service Mapping (top-down) Discovery. They each have a separate facility for Pre-Pattern Execution scripts that can be used to collect data from the CMDB or other tables and populate a data structure that is sent to the CMDB to be used by the running pattern.
Horizontal Discovery runs a Pre-Post Processing script that is stored in the sa_pattern_prepost_script table. There are already many scripts delivered by ServiceNow that are run before different patterns are executed so there are several good working examples that demonstrate this functionality.
Under Pattern Designer -> Pre Post Processing, you can access the scripts run for horizontal discovery
Service Mapping (top-down) Discovery runs Pre-Post Processing scripts stored in the sa_pre_task_script table. These scripts only run when a particular CI type is being updated based on data collected by a script. When the script runs, it can collect information from the CMDB and pass it in the form of a data structure to the running pattern. That data can then be used for various useful functions such as populating the discovered Cis or it can be used to make a dependency connection in a Connection Section.
For Service Mapping, access the sa_pre_task_script table by typing “sa_pre_task_script.LIST” in the Navigator
The “Pattern Pre/Post Scripts” for Horizontal Discovery are associated with a particular Discovery Pattern. The “Pre Service Mapping Task Scripts” are each associated with a particular CI type so they will run on any pattern that is associated with that CI Class. They might be executed before any number of patterns.
Below is an architectural diagram of how the Pre-Pattern Execution works with both Horizontal and Service Mapping Discovery. The Pre-Pattern script runs on the ServiceNow instance and populates a Data Table Object. That object is sent to the MID Server and is available as a temporary variable. The pattern can then use that temporary variable during pattern execution on the MID Server.
During Service Mapping, Pre-Pattern Execution Scripts can create a Data Table Object that is passed down to the MID Server to be used by the Pattern that is executing
The scripts are all JavaScript that you might execute for any automation reason on the ServiceNow Instance. To pass a data object to the MID Server, you need to initialize a data object by declaring it with a method call: “SNC.PrePatternExecutionData()”. Then, to populate the data object, there is a method “addTableEntry”, which allows you to populate the object as a table with column names. Then, at the end of your script, you need to have a statement: “rtrn = data;”, which will pass the data object to the script. See screenshots below.
USE CASE EXAMPLE
Customer Application Architecture
Consider the following Service Mapping Use Case: There is an application called “Customer” that has a custom application server process communicating to a back-end Database. In Service Mapping, the pattern looks for a configuration file or a persistent network connection to discover the connection between application components. But, in this use case, there is no configuration file for which our discovery pattern can access, and the network connection is not persistent. So, how can we teach our application service pattern how to make the connection to the back-end database?
It turns out that this back-end database is Microsoft SQL Server and the specific instances of SQL Server that our application uses contain a database catalog named “Customers”. It also turns out that we’ve done a horizontal discovery of all our servers, and we collected an inventory of all the back-end SQL Server database instances with the database catalogs contained within them. So, the CMDB has all the data we need to know which SQL Server database instances our application depends. We just need to query the CMDB data and pass that information to our Service Map in the form of a custom data object. Thankfully, we can use the Service Mapping pre task scripts to accomplish this.
First, we need to create a Service Mapping Pre-Task Script that creates a data object of all the customer databases. We open the sa_pre_task_script table and create a new entry called “Customer Databases”. We associate this with the CI Class: Windows Server [cmdb_ci_win_server] and write the script to query all instances of Microsoft SQL Servers which contain a database named “Customers” and creates a data table called ”customer_db_server” with the columns: databaseinstance, databasename, computer, ipaddress, and port. Below is the code I used. Note that I have some debug statements that are turned on with a ‘debug’ variable and the specific pre_pattern_statements will only execute if I set the pre_pattern_statements variable to ‘true’.
/**** Get Customer DBs ***/
/*** this code is meant to be put into the Pre Service Mapping Task Script table: sa_pre_task_script ***/
var pre_pattern_statements = true; /* set to false for debugging and running in the Background Scripts **/
var debug = false; /* set to true for debugging */
if (pre_pattern_statements)
{
var data = new SNC.PrePatternExecutionData(); /* initialize the data table object **/
}
var cireltype = new GlideRecord ( 'cmdb_rel_type' );
cireltype.addQuery ( 'parent_descriptor', "Runs on" );
cireltype.query();
cireltype.next();
var runsontype = cireltype.sys_id + '';
if (debug) gs.log ( "Got Runs On relationship type:" + runsontype );
var cireltype = new GlideRecord ( 'cmdb_rel_type' );
cireltype.addQuery ( 'parent_descriptor', "Contains" );
cireltype.query();
cireltype.next();
var containedbytype = cireltype.sys_id + '';
if (debug) gs.log ( "Got contained by relationship type:" + containedbytype );
var mssqldb = new GlideRecord ( 'cmdb_ci_db_mssql_database' );
mssqldb.addQuery ( 'name', 'Customers' );
mssqldb.query();
while (mssqldb.next())
{ /* parse through all databases */
if (debug) gs.log ( "Got database name: " + mssqldb.name );
var cirel1 = new GlideRecord ( 'cmdb_rel_ci' );
cirel1.addQuery ( 'child', mssqldb.sys_id + '' );
cirel1.addQuery ( 'type', containedbytype );
cirel1.query(); /*** query the MS SQL instance in which this database is contained ***/
if (cirel1.next())
{ /* get sql instance in which this db is contained */
var num_customer_servers=0;
var mssqli = new GlideRecord ( 'cmdb_ci_db_mssql_instance' );
mssqli.get ( cirel1.parent + '' );
if (debug) gs.log ( "Got contained MS SQL Instance:" + mssqli.name + " (" + mssqli.sys_id + ")" );
/*** now get the servrer on which this MS SQL Instance is running ****/
var cirel2 = new GlideRecord ( 'cmdb_rel_ci' );
cirel2.addQuery ( 'parent', mssqli.sys_id + '' );
cirel2.addQuery ( 'type', runsontype );
cirel2.query(); /*** query the MS SQL instance in which this database is contained ***/
if (cirel2.next())
{ /*** get the server instance ***/
var cm_sysid = cirel2.child;
var cm = new GlideRecord ( 'cmdb_ci' );
cm.get ( cm_sysid + '' );
if (debug) gs.log ( "For MS SQL Instance:" + mssqli.name + " runs on server:" + cm.name );
/*** now get the different ports on which the SQL Server is listening ***/
var tcpports = mssqli.tcp_port;
var tcpparr = tcpports.split ( ':' ); /* typical list of ports are: :1433:1434:49696: ***/
for (var i=0;i<tcpparr.length;i++)
{ /* parse out each port and add the record to the table entry ***/
var cur_port = tcpparr[i] + '';
cur_port = cur_port.trim();
if (cur_port.length == 0)
continue; /* no need to connect to an empty port ***/
num_customer_servers++;
if (debug) gs.log ( "Got port:" + cur_port );
if (debug)
{
gs.log ( "Writing Customer DB Server Entry:" );
gs.log ( "databaseinstance:" + mssqli.name );
gs.log ( "databasename:" + mssqldb.name );
gs.log ( "computer:" + cm.name );
gs.log ( "ipaddress:" + cm.ip_address );
gs.log ( "port:" + cur_port );
}
if (pre_pattern_statements)
{ /* write this server record to the customer db server table */
data.addTableEntry('customer_db_server',
{ /* create the table entry in the object */
'databaseinstance':mssqli.name,
'databasename':mssqldb.name,
'computer':cm.name,
'ipaddress':cm.ip_address,
'port':cur_port
} );
} /* write this server record to the customer db server table */
} /* parse out each port and add the record to the table entry ***/
} /*** get the server instance ***/
} /* get sql instance in which this db is contained */
} /* parse through all databases */
if (debug) gs.log ( "Total Customer Servers: " + num_customer_servers );
if (pre_pattern_statements)
{
rtrn = data;
}
Once that script code is working, I save it in the sa_pre_task_script table and we can then start mapping the Application Service. As expected, the map only discovers as far as the application server component. We need to teach it how to connect to the back-end database server.
Our Discovered Map only Maps to the Application Server
If we go into the Discovery Pattern in DEBUG mode for the Customer Application component, we will see a new temporary variable table called “customer_db_server”. We can then create a new Connection Section called “CUSTOM: Connect to Customer Database Instance”. This section will have a “Create Connection” operation that will make the connection to all back-end SQL Server database instances using the data in the table “customer_db_server”.
This is the Discovery Pattern for the Custom Application Server Component. We just added a new Connection Section
In DEBUG mode in the Connection Section, we can see the customer_db_server table created by the Pre Task Script for Windows Server
The customer_db_server table has an entry for each computer and listening port for each MS SQL DB Instance
Now, when we save the new Connection Section and publish it, we can re-run our Discovery. It will now make the connection to the back-end SQL Server Database and the application is mapped to completion. It maps down to the database server because we were able to leverage information in the CMDB.
- 5,283 Views
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.