THURSDAY, FEBRUARY 09, 2012

Walking the Service-now.com CMDB Relationship Tree

One of the great features of Service-now.com is its CMDB and relational mapping.  You can easily set up relationships between any CIs in the system.  Once the relationships are defined, it becomes very simple to pull up a visual representation of a CI and its dependencies by using Service-now BSM maps.  Using this feature allows an end user to look at that CI and identify what else in the environment is impacted by an outage or a change to that CI.

While this works fine for most situations, it may be necessary to be able to use this relationship information outside of the map view.  What if you wanted to determine all of the Business Services impacted by a particular change request?  What if you wanted to pull an approval group from each impacted Business Service to approve a specific step in your change workflow?

The ‘CIUtils’ Script Include is designed to help you get some of this information.  You can find it by navigating to ‘System Definition -> Script Includes’.  To use it, you could do something like this in a Business rule, UI action, or Scheduled job…

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByCI(current.cmdb_ci);
//Do something with the array of Business Services returned

You can also use the ‘servicesAffectedByTask’ function to use a task record as your input parameter

var ciu = new CIUtils();
//Return all of the Business services affected by the current CI on this ticket
var services = ciu.servicesAffectedByTask(current);
//Do something with the array of Business Services returned

There are a couple of limitations with the CIUtils Script include though. To fill in some of those gaps, I recently created an enhancement to CIUtils called ‘CIUtils2′. It is used in pretty much the same way as the CIUtils Script include, but it gives you these added benefits…

  • Return all impacted Business Services based on the ‘cmdb_ci’ field AND the ‘Affected CIs’ related list
  • Return all impacted CIs based on the CI classes you specify
  • Return all impacted CIs throughout the entire CMDB

To use it, simply create a new Script include record with the script below WITH A NAME OF ‘CIUtils2′. You can then call the functions like this…

Usage:
var ciu = new CIUtils2();
//cisAffectedByTask takes a Task glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByTask(TaskGlideRecord); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByTask(TaskGlideRecord, ["cmdb_ci_service", "cmdb_ci_web_server"]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByTask(TaskGlideRecord, ["ALL"]); //Returns an array of ALL impacted CIs

//cisAffectedByCI takes a CI glideRecord as input and returns an array of CI sys_ids
var services = ciu.cisAffectedByCI(current.cmdb_ci); //Returns an array of impacted Business Service CIs
var services = ciu.cisAffectedByCI(current.cmdb_ci, ["cmdb_ci_service", "cmdb_ci_web_server"]); //Returns an array of impacted CIs from the CI classes specified
var services = ciu.cisAffectedByCI(current.cmdb_ci, ["ALL"]); //Returns an array of ALL impacted CIs. Use this with caution!!!

//getCIXML takes a CI sys_id as input and returns an XML-formatted string showing all CIs impacted by an outage to the CI given
var myXML = ciu.getCIXML(current.cmdb_ci);

Script:

gs.include('PrototypeServer');

var CIUtils2 = Class.create();

CIUtils2.prototype = {

initialize : function() {
this.maxDepth = gs.getProperty('glide.relationship.max_depth',10); // how deep to look
this.currentDepth = 0;

this.CIs = new Object(); // list of affected CIs
this.maxSize = gs.getProperty('glide.relationship.threshold',1000); // how many records to return
this.added = 0; // track how many added, since can't get size() for an Object
},

/**
* Determine which CIs are affected by a specific CI
*
* Inputs:
* id is the sys_id of a configuration item (cmdb_ci)
* classArr is an array of CI class names that should be returned
*
* Returns:
* an array of sys_id values for cmdb_ci records downstream of
* (or affected by) the input item
*/


cisAffectedByCI: function(id, classArr) {
var ci = new GlideRecord('cmdb_ci');
ci.get(id);
//If no class specified then assume business service
if(classArr == null){
classArr = ['cmdb_ci_service'];
for (var ciClass in classArr){
if (ci.sys_class_name == classArr[ciClass]){
this._addCI(id, this.CIs);
}
}
}
//If class = 'ALL' then just add the CI
else if(classArr[0] == 'ALL'){
this._addCI(id, this.CIs);
}
else{
for (var ciClass in classArr){
if (ci.sys_class_name == classArr[ciClass]){
this._addCI(id, this.CIs);
}
}
}
this._addParentCIs(id, this.CIs, this.currentDepth, classArr);

var ciarr = new Array(); // CIs is an Object, convert to Array for final query
for (var i in this.CIs)
ciarr.push(i);

return ciarr; // list of affected CIs
},

/**
* Determine which CIs are affected by a task
*
* Inputs:
* task is a task GlideRecord (e.g., incident, change_request, problem)
* classArr is an array of CI class names that should be returned
*
* Returns:
* an array of sys_id values for cmdb_ci records downstream of
* (or affected by) the configuration item referenced by the task's cmdb_ci field and Affected CIs list
*/


cisAffectedByTask: function(task, classArr) {
//Find the impacted CIs for the 'cmdb_ci' value
var id = task.cmdb_ci.toString();
var allCIArr = new Array();
if(id){
allCIArr = this.cisAffectedByCI(id, classArr);
}

//Find the impacted CIs for any Affected CIs listed on the task
var affCIArr = new Array();
var affCI = new GlideRecord('task_ci');
affCI.addQuery('task', task.sys_id);
affCI.query();
while(affCI.next()){
affCIArr = this.cisAffectedByCI(affCI.ci_item.sys_id, classArr);
if(allCIArr.length > 0){
//Concat the array with the current array
allCIArr = allCIArr.concat(affCIArr);
}
else{
allCIArr = affCIArr;
}
}
//Strip the array of any duplicate values
return this.unique(allCIArr);
},

/**
* Returns an XML-formatted string showing all CIs impacted by an outage to the CI given
*
* Inputs:
* id is the sys_id of the root CI
*
* Returns:
* an XML-formatted string containing cmdb_ci records downstream of
* (or affected by) the configuration item provided as input
*/


getCIXML: function(id) {
var gr = new GlideRecord('cmdb_rel_ci');
gr.addQuery('child', id);
gr.query();
gr.next();
var str = '';
str += '<CI>';
str += '<sys_id>'+gr.child.sys_id+'</sys_id>';
str += '<name>'+gr.child.name+'</name>';
str += '<relType>SELF</relType>';
ret = this._recurs(id);
if(ret){
str += '<children>';
str += ret;
str += '</children>';
}

str += '</CI>';
return str;
},

_recurs: function(ci) {
var gr = new GlideRecord('cmdb_rel_ci');
gr.addQuery('child', ci);
gr.query();
var str = '';
while(gr.next()){
str += '<CI>';
str += '<sys_id>'+gr.parent.sys_id+'</sys_id>';
str += '<name>'+gr.parent.name+'</name>';
str += '<relType>'+gr.type.name+'</relType>';

ret = this._recurs(gr.parent.sys_id);
if (ret) {
str += '<children>';
str += ret;
str += '</children>';
}

str += '</CI>';
}
return str;
},

_addParentCIs : function(value, CIs, currentDepth, classArr) {
if (this.added >= this.maxSize) {
// gs.print('reached threshold of ' + this.maxSize);
return;
} else {
currentDepth++;
// gs.print('current depth is ' + currentDepth);
var g = new Packages.com.glideapp.ecmdb.Relationships();
var al = g.getRelatedRecords(value, null, 'cmdb_ci', 'cmdb_ci', 'child'); // returns ArrayList

// first add the unique cis
var kids = new GlideRecord('cmdb_ci');
kids.addQuery('sys_id', al);
kids.query();
while (kids.next()) {
var str = kids.sys_id;
if (!CIs[str]) {
var rec = new GlideRecord('cmdb_ci');
rec.get(str);

for (var ciClass in classArr){
if ((kids.sys_class_name == classArr[ciClass]) || (classArr[0] == 'ALL')){
this._addCI(str, CIs);
}
}
if (this.added >= this.maxSize)
return;
if (currentDepth < this.maxDepth)
this._addParentCIs(str, CIs, currentDepth, classArr);
}
}
}
},

_addCI: function(id, CIs) {
CIs[id] = true;
this.added++;
},

unique: function(a) {
//Check all values in the incoming array and eliminate any duplicates
var r = new Array();
o:for(var i = 0, n = a.length; i < n; i++){
for(var x = 0, y = r.length; x < y; x++){
if(r[x]==a[i]){
continue o;
}
}
r[r.length] = a[i];
}
return r;
},

type: 'CIUtils2'
};


Another practical example for Change Management

Here’s an example UI action that I use for almost all of my clients. In change management, you’re often interested in the business services that will be impacted by a given change. While you can see this in a BSM map, it’s often useful to see this in a related list directly on your change form as well. By adding the ‘Impacted Services’ related list to your change form, you can populate this data. This UI action gathers all of the information about the CIs on your change request and adds the impacted services to the list.

‘Refresh Impacted Services’ UI Action
Name: Refresh Impacted Services
Table: Change request
Action name: refresh_impacted_services
Form context menu: True
Condition: gs.hasRole(‘itil’)
Script:

current.update();
action.setRedirectURL(current);
removeAffectedServices();
addAffectedServices();

function removeAffectedServices() {
var m2m = new GlideRecord('task_cmdb_ci_service');
m2m.addQuery('task',current.sys_id);
m2m.addQuery('manually_added','false');
m2m.query();
m2m.deleteMultiple();
}

function addAffectedServices() {
var ciu = new CIUtils2();
//Find all impacted business services
var services = ciu.cisAffectedByTask(current);
//var services = ciu.cisAffectedByTask(current, ["cmdb_ci_service", "cmdb_ci_windows_server"]);
//var services = ciu.cisAffectedByTask(current, ["ALL"]);
var m2m = new GlideRecord('task_cmdb_ci_service');
for (var i = 0; i < services.length; i++) {
m2m.initialize();
m2m.task = current.sys_id;
m2m.cmdb_ci_service = services[i];
m2m.manually_added = 'false';
m2m.insert();
}
}

Comments

Posted On
Apr 01, 2010
Posted By
Precious

We have recently implemented Service now and I have been tasked to put together the configuration management process using the service now tool. Has anyone done this before and are there any points or guidelines apart from the ones mentioned above?

Posted On
Sep 20, 2010
Posted By
Matt Gaide

This was really helpful!

Thanks!!

Posted On
Oct 06, 2011
Posted By
Erite

Thank you it works very well.
However what we really want to do is not to return all the impacted Services but only the one immediately linked to the CI.
How could I do that?
For instance if a CI is related to Service A and Service A is related to another upstream Service called Service B. Both Service A and Service B are returned. However we just want to return Service A (the one which is immediately connected to the CI).

Looking at the Script Include I have found this:
” initialize : function() {
this.maxDepth = gs.getProperty(‘glide.relationship.max_depth’,10); // how deep to look
this.currentDepth = 0;”

So I believe if I set relationship.max_depth to 1 it will only return the Services immediately linked to the CI?

Posted On
Oct 06, 2011
Posted By
Mark Stanger

Yes, that should work, but it will use that depth for everything else as well. If it were me, I would probably adjust the code to accept another parameter indicating the max depth to go to. That way you have the flexibility to do whatever depth you want for whatever class you want.

Posted On
Oct 07, 2011
Posted By
Erite

Mark,

What we have done is to create an other Script Includes called CIUtils3 with max depth set to 1 within the Script Includes. It looks like that:

initialize : function() {
this.maxDepth = gs.getProperty(‘glide.relationship.max_depth’,1); // how deep to look
this.currentDepth = 0;

We have modified our business rule to use CIUtils3 as opposed to CIUtils2.

Although we haven’t fully tested it, it seems to work as expected. For instance the ‘Refresh Impacted Services’ UI Action on Change gets you all the Business Services where our business rule gets you only the Business Services directly linked to the CI.

Do you see any issue with that option? We want to make sure that max depth set to 1 is not applied everywhere but only for our business rule.

Thanks in advance

Posted On
Oct 07, 2011
Posted By
Mark Stanger

That looks great. Good idea!

Posted On
Jan 12, 2012
Posted By
Rob

Hi Mark,

We are trying to implement this script includes, but our customers would like to look upstream and downstream. Can I adjust the script include to look both ways? If so, how would I do that?

Is there an equivalent to the addParentCIs function and “var al = g.getRelatedRecords(value, null, ‘cmdb_ci’, ‘cmdb_ci’, ‘child’); // returns ArrayList” statement that I can use to search for Child CIs?

Thanks in advance.

Posted On
Jan 12, 2012
Posted By
Mark Stanger

Hey Rob,

That isn’t built into this script include and, for what it’s worth, I haven’t ever seen a really good reason for doing what you’re asking for so I would reconsider that requirement if I were you. Having said that, you could accomplish it by setting up a new script include that would be identical except for the name of the script include and switching every instance of ‘parent’ and ‘child’ in the script for each other.

Posted On
Jan 12, 2012
Posted By
Rob

Our customers want the flexibility to return CIs upstream and downstream. For example, a Server going down may impact an Application with a depends on relationship (upstream), but a switch going out may impact a network circuit with a connects to relationship (downstream). Is there a better way of doing this maybe? Basically our CMDB relationships look like the following for network circuits:

IP Switch > Network Adapter > Port > Circuit

Our network team would like to be able to list the Switch as the CI and show the Circuits that will be unavailable due to the switch outage. Is there a way to do this with the code as is?

If not, would I create a duplicate function called addChildCIs (in addition to the addParentCIs) in the new script includes?

Posted On
Jan 13, 2012
Posted By
Mark Stanger

Tough to say what the best solution would be without a full understanding of the environment, but I would look at the CMDB relationships first and see if there’s a way for you to get them all going in the same direction. If possible, you want everything to flow down from the business services. If you can’t do that then you would need to do as I said previously and create a new script include that flips all of the logic by switching ‘parent’ and ‘child’.

Leave a Reply


Notify me of followup comments via e-mail. You can also subscribe without commenting.

Latest Comments

  • Aric: Finally figured out what I was doing wrong, incase someone else wants to do this. Mark is correct about needing...
  • Mark Stanger: You’ll probably need at least 3 total ACLs then. You’ll need one...
  • Mark Stanger: Hey Paul, I assume you’re talking about ‘task_ci’, not ‘task_group. If so, the...
  • ND: Hi Mark, This is cool. How can I do same at the form level. I have created a slushbucket variable and I want to...