Name
global.ApplicationDependencyMapping
Description
Detects applications communicating with each other, and creates relationships between them.
Script
// Discovery
var ApplicationDependencyMapping = Class.create();
ApplicationDependencyMapping.map = function(source, status, classificationProbe) {
var dh = SncDeviceHistory.getFromSourceAndStatusAndClassification(source, status, classificationProbe);
var adm = new ApplicationDependencyMapping();
adm.initializeForMapping('' + dh.getCmdbCi());
adm.map();
gs.eventQueue('discovery.device.adm.mapping_complete', 'discovery_device_history', '' + dh.getSysID(), dh.getCmdbCi());
};
ApplicationDependencyMapping.prototype = {
/**
* Initializes a new instance of this class. The given sensor must be the DiscoveryADMSensor that created
* this instance.
*/
initialize: function(sensor) {
if (typeof sensor == 'undefined') //no arg when used for mapping alone
return;
this.sensor = sensor;
// store all our matched running processes
this.matched_processes = this.get_matches();
},
/*
* Reset objects from classification in original sensor run
*/
initializeForMapping: function(ciId) {
var ciGr = new GlideRecord('cmdb_ci_computer');
ciGr.get(ciId);
this.ci_sys_id = '' + ciId;
this.ci_name = '' + ciGr.name;
this.classified = {};
this.procByID = {};
},
/**
* Called by DiscoveryADMSensor after running processes and connections have been enriched and saved to to
* the database.
*
* This function does two quite separate things. First, it classifies the running processes (this used to be
* done in the running process sensor, but was moved here so that it takes place after enrichment). Second,
* it does the actual application dependency mapping.
*/
process: function() {
// capture information about the CI...
this.ci_sys_id = '' + this.sensor.getCmdbCi();
var cigr = this.sensor.getCmdbRecord();
if (!cigr)
return;
this.clusterGr = this.getCluster();
this.clusterIPs = [];
if (JSUtil.notNil(this.clusterGr))
this.clusterIPs = this.getClusterIPs();
this.ci_table = '' + cigr.sys_class_name;
this.ci_name = '' + cigr.name;
this.indexByID();
this.classified = this.classify();
this.deferMapping();
},
deferMapping: function() {
if (gs.getProperty('glide.discovery.application_mapping') != 'true' || !g_device)
return;
g_device.setStopWorkflow(true);
g_device.setScratchpadValue('requires_adm', 'true');
g_device.setStopWorkflow(false);
},
// Returns the first valid cluster where this CI is a node
getCluster: function() {
var gr = new GlideRecord("cmdb_ci_cluster_node");
gr.addQuery("server", this.ci_sys_id);
gr.query();
while (gr.next()) {
var cluster = new GlideRecord("cmdb_ci_cluster");
if (!cluster.get(gr.cluster))
continue;
return cluster;
}
return;
},
// Return an array of Cluster IPs
getClusterIPs: function() {
var vips = {};
var gr = new GlideRecord("cmdb_ci_cluster_vip");
gr.addQuery("cluster", this.clusterGr.sys_id);
gr.query();
while (gr.next())
vips[gr.ip_address + ''] = true;
return vips;
},
/**
* Look for opportunities for application dependency mapping on the current CI. Any opportunties will result in
* a call to onConnection().
*/
map: function() {
// build classified (processes) object
this.classified = this.queryForConnections();
// build our (potentially quite large) query for connections...
var gr = this.queryForOtherSide();
// get all our matched connections...
var matches = this.matchConnections(gr);
// if we didn't find any matches, skip the rest of this...
if (matches.length == 0)
return;
this.findForeignClassifieds(matches);
this.findForeignProcesses(matches);
this.autoClassify(matches);
// make sure our dependencies all get recorded in the database...
this.ensureDependencies(matches);
},
/**
* Ensure that all the application dependencies in the given matches are actually recorded in the database.
*/
ensureDependencies: function(matches) {
var queryParent,
_this = this,
children = {},
gr = new GlideRecord('cmdb_rel_ci'),
relType = new DiscoveryFunctions().findOrCreateRelationshipType("cmdb_rel_type", "Depends on::Used by");
matches.sort(sortByParent);
matches.forEach(ensure);
updateDb(); // Call this one more time to handle the last parent
// The sort() above effectively groups records by parent. This code will collect child sys_id and port
// for children of a single parent. When it's called with a new parent it will write all relationships
// for the previous parent.
// This is a re-write of a previous less efficient implementation. That code grouped records when
// updating so I did the same. This would make sense if we wanted to delete children which are no
// longer present, but we don't do that.
function ensure(match) {
var parent = _this.classified[match.from.process].app,
child = _this.classified[match.to.process].app,
port = match.to.port;
if (parent != queryParent) {
updateDb();
queryParent = parent;
}
children[child] = port;
}
function updateDb() {
if (!queryParent)
return;
var name, child,
gr = new GlideRecord('cmdb_rel_ci');
// Update ports if necessary
gr.addQuery('parent', queryParent);
gr.addQuery('type', relType);
gr.addQuery('child', 'IN', Object.keys(children));
gr.query();
while (gr.next()) {
child = gr.child + '';
gr.port = children[child];
gr.update();
children[child] = undefined;
}
// And insert anything that's new
for (name in children) {
if (children[name] !== undefined) {
gr = new GlideRecord('cmdb_rel_ci');
gr.parent = queryParent;
gr.child = name;
gr.type = relType;
gr.port = children[name];
gr.insert();
}
}
// Reset to prepare for the next parent.
children = {};
}
function sortByParent(a, b) {
var aParent = _this.classified[a.from.process].app,
bParent = _this.classified[b.from.process].app;
if (aParent > bParent)
return 1;
if (aParent < bParent)
return -1;
return 0;
}
},
/**
* Attempt to auto-classify any processes in the matches that are not already classified. If auto-classification
* can't be done for any reason, delete the match.
*/
autoClassify: function(matches) {
var procsToAutoClassify = [];
// look at all our matches to see which ones need auto-classification...
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
// If a process is talking to itself, then let's take it out the match array and skip it
var talkingToMyself = (match.from.process == match.to.process);
if (talkingToMyself) {
matches.splice(i, 1);
i--;
continue;
}
// if both our matched processes are already classified, then we don't need to auto-classify...
var from_classified = !!this.classified[match.from.process];
var to_classified = !!this.classified[match.to.process];
if (from_classified && to_classified)
continue;
// if we need to auto-classify, but can't, then delete this match...
if (!this.shouldAutoClassify(match.from.process) || !this.shouldAutoClassify(match.to.process)) {
matches.splice(i, 1);
i--;
continue;
} else {
// Keep track of the ones we do need to auto classify
if (!from_classified)
procsToAutoClassify.push(match.from.process);
if (!to_classified)
procsToAutoClassify.push(match.to.process);
}
}
// If no process requires auto classification... just get out...
if (procsToAutoClassify.length == 0)
return;
var mutexName = '<<<-- ADM Pending Processor Classifier Mutex-->>>';
var mutex = new Mutex(mutexName, mutexName);
// limit our attempt to get a mutex to 120 seconds...
mutex.setSpinWait(500);
mutex.setMaxSpins(240);
mutex.setMutexExpires(120000); //120 seconds
if (mutex.get()) {
try {
for (var i = 0; i < procsToAutoClassify.length; i++)
this._autoClassify(procsToAutoClassify[i]);
} finally {
mutex.release();
}
} else {
//lock failed... not risk creating it... bail...
gs.log("Unable to get a lock to create auto classifiers");
}
},
/**
* Auto-classifies the process with the given sys_id.
*/
_autoClassify: function(proc_id) {
var proc_info = this.procByID[proc_id];
//Make sure we have all the proc info we need (may not if this came from a furriner)
if (!proc_info.command) {
var runningProcGr = new GlideRecord('cmdb_running_process');
runningProcGr.get(proc_info.sys_id);
proc_info.command = runningProcGr.command;
proc_info.key_parameters = runningProcGr.key_parameters;
}
var condition = makeClassifierCondition(this);
if (shouldSkipCondition(condition))
return;
// figure out whether we've got a process to treat as connecting to or connecting from...
var isTo = !!proc_info.listening_on;
var isFrom = !!proc_info.connecting_to;
// create our auto-classifier...
var cgr = new GlideRecord('discovery_classy_proc');
cgr.initialize();
cgr.setWorkflow(false);
cgr.order = 1000;
cgr.name = 'Pending: ' + makeName();
cgr.table = 'cmdb_ci_appl_pending';
cgr.relation_type = new DiscoveryFunctions().findOrCreateRelationshipType("cmdb_rel_type", "RUns on::Runs");
cgr.condition = condition;
cgr.test_with.getGlideList().setValue(this.ci_sys_id);
// It's possible to have another thread creating the exact same pending classifier, so we need to check to make sure
// we don't already have it. If we do, we just use the classifier record we've found instead.
var grr = new GlideRecord('discovery_classy_proc');
if (grr.get("condition", cgr.condition))
classifier = grr.sys_id;
else
classifier = '' + cgr.insert();
// And if a pending application record already has a record representing this process... then we're done! Otherwise create a new one.
var gap = new GlideRecord("cmdb_ci_appl_pending");
if (gap.get("running_process", proc_id)) {
app = gap.sys_id;
} else {
// create our application instance...
var computer_name = (!!proc_info.computer_name) ? proc_info.computer_name : this.ci_name;
var agr = new GlideRecord('cmdb_ci_appl_pending');
agr.initialize();
// We used to set category and subcategory here. See PRB1319943.
agr.classifier = classifier;
agr.running_process = proc_id;
agr.name = makeName() + '@' + computer_name;
agr.first_discovered = '' + new GlideDateTime().getDisplayValue();
agr.last_discovered = agr.first_discovered;
agr.discovery_source = gs.getProperty('glide.discovery.source_name', "ServiceNow");
var app = '' + agr.insert();
// create our "runs on" relationship...
var rgr = new GlideRecord('cmdb_rel_ci');
rgr.initialize();
rgr.parent = app;
rgr.child = proc_info.computer;
rgr.type = '' + cgr.relation_type;
rgr.insert();
}
// cache this in case we see it again...
proc_info.app = app;
this.classified[proc_id] = proc_info;
/**
* Returns a name for this classifier or application instance. For "from" processes:
* Pending: connecting to <ports this process is connecting to>
* For "to" processes:
* Pending: listening on <ports this process is listening on>
*/
function makeName() {
var name = proc_info.name + ' ';
if (isFrom && isTo)
name += 'connecting & listening';
else if (isFrom)
name += 'connecting to ' + listPorts(proc_info.connecting_to).join(', ');
else if (isTo)
name += 'listening on ' + listPorts(proc_info.listening_on).join(', ');
// Limit the name to the field size, which is about 100.
if (name.length > 90)
name = name.substring(0, 90) + "...";
return name;
}
/**
* Given a string of colon-separated ports (like ":80:443:"), return an array of port numbers.
*/
function listPorts(ports) {
if (!ports)
return [];
var result = ports.split(':');
if (result.length < 3)
return [];
// the first and last entries should always be empty, because of the framing colons...
return result.slice(1, -1);
}
/**
* Returns an encoded query that will look for processes like the current process.
*/
function makeClassifierCondition(that) {
var keyParams = proc_info.key_parameters;
return 'name=' + proc_info.name.substring(0, 40) +
'^command=' + proc_info.command +
(JSUtil.notNil(keyParams) ? '^key_parameters=' + keyParams : '') +
'^EQ';
}
/**
* If the condition matches a process handler that is marked as do not classify in process handler, then get out...
*/
function shouldSkipCondition(condition) {
var gr = new GlideRecord("discovery_proc_handler");
gr.addQuery("condition", condition);
gr.addQuery("classify", "false");
gr.setLimit(1);
gr.query();
if (gr.next())
return true;
return false;
}
},
/**
* Returns true if the given process sys_id should be auto-classified.
*/
shouldAutoClassify: function(proc_id) {
var shouldClassify = (this.procByID[proc_id] && (this.procByID[proc_id].classify == 'true'));
var shouldAuto = gs.getProperty('glide.discovery.auto_adm', 'false') == 'true';
return shouldClassify && shouldAuto;
},
/**
* Make a map (in this.procByID) of all running processes by ID.
*/
indexByID: function() {
this.procByID = {};
for (var i = 0; i < this.sensor.running_processes.length; i++) {
var proc = this.sensor.running_processes[i];
this.procByID[proc.sys_id] = proc;
}
},
/**
* Make records in this.classified for all the foreign (i.e., not from this computer) processes in the given list
* of matched connections that have been classified. We detect as classified those processes that have a matching
* new-style application instance. The matches list is in the form returned by this.matchConnections().
* This method also enhances the match object to include both a to.app and a from.app entry.
*/
findForeignClassifieds: function(matches) {
// query for any foreign classified application instances...
var procList = [];
var agr = new GlideRecord('classified_apps');
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
procList.push('' + match.from.process);
procList.push('' + match.to.process);
if (i % 100 == 99 || i == (matches.length - 1)) {
agr.addQuery('a_running_process', 'IN', procList);
agr.addQuery('p_computer', '!=', this.ci_sys_id);
agr.query();
// use our results to update the this.classified and this.procByID maps with our furriners...
while (agr.next()) {
// add the furriner entry to this.classified...
var tc = {};
tc.running_process = '' + agr.p_sys_id;
tc.classifier = '' + agr.c_sys_id;
tc.app = '' + agr.a_sys_id;
tc.computer = '' + agr.p_computer;
this.classified[tc.running_process] = tc;
// add the furriner entry to this.procByID...
var tp = {};
tp.sys_id = '' + agr.p_sys_id;
tp.computer = '' + agr.p_computer;
tp.name = '' + agr.p_name;
tp.listening_on = '' + agr.p_listening_on;
tp.connecting_to = '' + agr.p_connecting_to;
tp.classify = '' + agr.p_classify;
this.procByID[tp.sys_id] = tp;
}
procList = [];
agr.initialize();
}
}
},
/**
* Queries for process information for those foreign processes that have matched connections but which are not classified.
* Updates the procByID map.
*/
findForeignProcesses: function(matches) {
// first we make a list of all those processes we need to get info on...
var procs = [];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
handleProcess(this, match.to.process, procs);
handleProcess(this, match.from.process, procs);
}
// then we query for process information and fill it in...
var gr = new GlideRecord('cmdb_running_process');
gr.addQuery('sys_id', procs);
gr.query();
while (gr.next()) {
var proc_info = {};
proc_info.sys_id = '' + gr.sys_id;
proc_info.computer = '' + gr.computer;
proc_info.computer_name = '' + gr.computer.name;
proc_info.name = '' + gr.name;
proc_info.command = '' + gr.command;
proc_info.key_parameters = '' + gr.key_parameters;
proc_info.listening_on = '' + gr.listening_on;
proc_info.connecting_to = '' + gr.connecting_to;
proc_info.classify = '' + gr.classify;
this.procByID[proc_info.sys_id] = proc_info;
}
/**
* If the given process ID doesn't have informtion in procByID, add it to our list of procs to get information for.
*/
function handleProcess(that, proc_id, procs) {
if (!that.procByID[proc_id])
procs.push(proc_id);
}
},
/**
* Match the connections found on this computer (in this.sensor.connections) with other connections in the database
* (in the given GlideRecord instance gr), returning a list of matches with info objects whose properties are as follows:
*
* info object:
* from:
* process: sys_id of process (cmdb_running_process) on client side of connection
* ci: sys_id of the CI (cmdb_ci) running the client process
* to:
* process: sys_id of process (cmdb_running_process) on server side of connection
* ip: dotted-form v4 IP address of server device OR (if localhost) sys_id of CI (cmdb_ci)
* ci: sys_id of the CI (cmdb_ci) running the server process
* port: port number (1-65535) of port that server is listening on, and that client connected to
*/
matchConnections: function(gr) {
var deduper = {};
var matches = [];
while (gr.next()) {
// build our connection information object...
var cninfo = {
from: {
ci: null,
process: null
},
to: {
ci: null,
process: null,
ip: null,
port: null
}
};
if (gr.type == 'on') {
cninfo.to.ci = '' + gr.computer;
cninfo.to.process = '' + gr.process;
cninfo.to.ip = '' + gr.ip;
cninfo.to.port = '' + gr.port;
cninfo.from.ci = this.ci_sys_id;
cninfo.from.process = this.byKey[TCPKeyGenerator.getKey('to', gr.ip, this.ci_sys_id, gr.port)];
} else {
cninfo.from.ci = '' + gr.computer;
cninfo.from.process = '' + gr.process;
cninfo.to.ci = this.ci_sys_id;
cninfo.to.process = this.byKey[TCPKeyGenerator.getKey('on', gr.ip, gr.computer, gr.port)];
cninfo.to.ip = '' + gr.ip;
cninfo.to.port = '' + gr.port;
}
// eliminate one of the pair of connections we'll find if both ends are on this CI (could be any IP)...
var dd_key = cninfo.to.ci + ':' + cninfo.to.process + ':' + cninfo.to.ip + ':' + cninfo.to.port + ':' +
cninfo.from.ci + ':' + cninfo.from.process;
if (deduper[dd_key])
continue;
deduper[dd_key] = true;
// it's a real match, so add it to our results...
matches.push(cninfo);
}
return matches;
},
/*
* Query for the connections found on this computer, returning a GlideRecord instance on the cmdb_tcp table.
*/
queryForConnections: function() {
var classified = {},
grRel = new GlideRecord('cmdb_rel_ci'),
relType = new DiscoveryFunctions().findOrCreateRelationshipType("cmdb_rel_type", "Runs on::Runs");
grRel.addQuery('child', this.ci_sys_id);
grRel.addQuery('type', relType);
grRel.query();
while (grRel.next()) {
classified['' + grRel.parent.running_process] = {
"app": '' + grRel.parent,
"classifier": '' + grRel.parent.classifier,
"computer": this.ci_sys_id,
"running_process": '' + grRel.parent.running_process
};
}
return classified;
},
/**
* Query for the other side of connections found on this computer, returning a GlideRecord instance on the cmdb_tcp table.
*/
queryForOtherSide: function() {
var shouldAuto = gs.getProperty('glide.discovery.auto_adm', 'false') == 'true';
var mapLocal = gs.getProperty('glide.discovery.adm.map_local_connection', 'false') == 'true';
// iterate over all our connections, adding queries as we go and build our lookup map...
this.byKey = {};
this.keySet = [];
var grTcp = new GlideRecord('cmdb_tcp');
grTcp.addQuery('computer', this.ci_sys_id);
grTcp.addQuery('absent', false);
grTcp.query();
while (grTcp.next()) {
var ip = '' + grTcp.ip;
if (!shouldAuto && !this.classified[grTcp.process])
continue;
if (!mapLocal && (ip == '127.0.0.1' || ip == '::1') && !this.classified[grTcp.process])
continue;
// we're looking for the opposite type of the current connection, so we flip between the to and on,
// but we keep the ip and port the same. And obviously, if the ip is a localhost (127.0.0.1) address, then
// We need to specify the sys_id
var type = (grTcp.type == 'on') ? 'to' : 'on';
var matchKey = TCPKeyGenerator.getKey(type, ip, this.ci_sys_id, grTcp.port);
this.keySet.push(matchKey);
// and map it with a key we can recreate from the results, to find this connection again...
var fromkey = TCPKeyGenerator.getKey(grTcp.type, ip, this.ci_sys_id, grTcp.port);
this.byKey[fromkey] = '' + grTcp.process; // this used to be an entire 'connections' object entry but looks like we only consume the referenced process sys_id
}
// build our (potentially quite large) query for connections...
var gr = new GlideRecord('cmdb_tcp');
gr.addNotNullQuery('process');
gr.addQuery('absent', false);
gr.addQuery('key', this.keySet);
// now let's see what we caught in our net...
gr.query();
return gr;
},
/*
* If the CI is part of a cluster, based on VIPs, determine what processes should be treated as a process running on the cluster rather than the nodes
* The clustered PIDs require the active connections probe. Without it, it's not posssible to determine the PIDs.
*/
getClusteredPIDs: function() {
if (JSUtil.nil(this.clusterGr))
return {};
var pids = {};
for (var i = 0; i < this.sensor.connections.length; i++) {
var conn = this.sensor.connections[i];
if (conn.type != 'on')
continue;
if (conn.pid == 4) // Skipping Windows system process
continue;
if (this.clusterIPs[conn.ip])
pids[conn.pid] = true;
}
return pids;
},
/**
* Classify the running processes we have discovered and enriched. Returns a map of running process sys_ids to
* information objects containing these properties (for those running processes we have classified):
*
* running_process: sys_id of running process
* classifier: sys_id of classifier
* app: sys_id of application instance
* computer: sys_id of computer instance
* pid: pid of the running process
*/
classify: function() {
// make our empty results map...
// each object in here is mapped by running process sys_id, and contains these properties:
// running_process: sys_id of running process
//
var classified = {};
this.paramsToPass = {};
this.clusteredPIDs = this.getClusteredPIDs();
// save the running processes that matched with a classifier...
var matches = this.save_matches();
// get all the existing discovery-created relationships between our CI and application instances...
var existing_rels = this.get_existing_relationships();
// get any app instances that can be tied to our CI, but which don't have relationships...
var orphan_apps = this.get_orphaned_apps(matches, existing_rels);
// setup a couple of GlideRecords we'll be needing...
var app_gr;
var rel_gr = new GlideRecord('cmdb_rel_ci');
var accAgentId = this.sensor.accAgentId;
// Use the CMDB Identification API if the property is true or service mapping plugin is active otherwise default to old implementation
if (DiscoveryCMDBUtil.useCMDBIdentifiers())
createOrUpdateMatches(this);
else {
// match any new-style exact matches...
matchExactNewStyle(this);
// match any new-style apps where the process has changed since the last discovery...
matchFuzzyNewStyle(this);
// look for any old-style app instances that match, and convert them to new-style...
matchOldStyle(this);
// create any new app or relationship records needed (the ones we couldn't find to this point)...
createMatches(this);
}
// launch any probes we need to launch...
if (!this.isPatternExecutionEnabledOnAgent(accAgentId))
this.launchProbes(classified);
return classified;
// B E G I N I N N E R F U N C T I O N S
// iterate over our matches, looking first for exact new-style matches that we have existing relationships for...
function matchExactNewStyle(that) {
var currDateTime = '' + new GlideDateTime().getDisplayValue();
for (var key in matches) {
if (key.substr(0, 3) != 'EN:')
continue;
if (existing_rels[key])
updateExistingMatch();
}
// if there's a matching existing relationship, mark it handled and delete it...
function updateExistingMatch() {
app_gr = new GlideRecordUtil().getCIGR(existing_rels[key].parent);
if (app_gr) {
addScriptedFieldValuesToApp(app_gr, matches[key]);
app_gr.last_discovered = currDateTime;
app_gr.update();
}
matches[key]['__handled'] = true;
existing_rels[key].app = existing_rels[key].parent;
classified[matches[key].running_process] = existing_rels[key];
delete existing_rels[key];
}
}
// iterate over our matches again, now looking for fuzzy new-style matches that we have existing relationships for...
function matchFuzzyNewStyle(that) {
var currDateTime = '' + new GlideDateTime().getDisplayValue();
for (var key in matches) {
if ((key.substr(0, 3) != 'FN:') || matches[key].__handled)
continue;
// if there's a matching existing relationship, mark it handled, and delete it from our existing relationships and update the db...
if (existing_rels[key])
updateExistingMatch();
}
function updateExistingMatch() {
app_gr = new GlideRecordUtil().getCIGR(existing_rels[key].parent);
if (app_gr) {
var rp_gr = new GlideRecord("cmdb_running_process");
if (matches[key].running_process)
rp_gr.get('sys_id', matches[key].running_process);
if (JSUtil.notNil(rp_gr))
addRunningProcessInfoToApp(app_gr, rp_gr);
app_gr.running_process = matches[key].running_process;
addScriptedFieldValuesToApp(app_gr, matches[key]);
app_gr.last_discovered = currDateTime;
app_gr.update();
existing_rels[key].running_process = matches[key].running_process;
}
matches[key]['__handled'] = true;
existing_rels[key].app = existing_rels[key].parent;
classified[matches[key].running_process] = existing_rels[key];
delete existing_rels[key];
}
}
// iterate over our matches again, now looking for old-style matches that we have existing relationships for...
function matchOldStyle(that) {
var currDateTime = '' + new GlideDateTime().getDisplayValue();
for (var key in matches) {
if ((key.substr(0, 3) != 'EO:') || matches[key].__handled)
continue;
// if there's a matching existing relationship, mark it handled, delete it, and note need to update the app instance...
if (existing_rels[key]) {
app_gr = new GlideRecordUtil().getCIGR(existing_rels[key].parent);
if (app_gr) {
var rp_gr = new GlideRecord("cmdb_running_process");
if (matches[key].running_process)
rp_gr.get('sys_id', matches[key].running_process);
if (JSUtil.notNil(rp_gr))
addRunningProcessInfoToApp(app_gr, rp_gr);
app_gr.running_process = matches[key].running_process;
app_gr.classifier = matches[key].sys_id;
app_gr.correlation_id = 'NULL';
addScriptedFieldValuesToApp(app_gr, matches[key]);
app_gr.last_discovered = currDateTime;
app_gr.update();
existing_rels[key].running_process = matches[key].running_process;
existing_rels[key].classifier = matches[key].sys_id;
}
matches[key]['__handled'] = true;
existing_rels[key].app = existing_rels[key].parent;
classified[matches[key].running_process] = existing_rels[key];
delete existing_rels[key];
}
}
}
// now iterate over our unhandled matches with exact new-style match keys only, creating insert app instance and relationship records...
function createMatches(parent_this) {
var currDateTime = '' + new GlideDateTime().getDisplayValue();
for (var key in matches) {
var mk = matches[key];
if ((key.substr(0, 3) != 'EN:') || mk.__handled)
continue;
// add it to our classified map...
var cl_info = {};
cl_info.classifier = mk.sys_id;
cl_info.running_process = mk.running_process;
cl_info.computer = this.ci_sys_id;
classified[mk.running_process] = cl_info;
// if we've got an orphaned app here, just add a relationship for it...
var orphan_id = orphan_apps[mk.cid];
if (!orphan_id)
orphan_id = orphan_apps[mk.running_process];
if (orphan_id) {
rel_gr.initialize();
rel_gr.type = mk.relation_type;
rel_gr.child = parent_this.ci_sys_id;
rel_gr.parent = orphan_id;
rel_gr.insert();
cl_info.app = orphan_id;
continue;
}
// insert our app instance record...
app_gr = new GlideRecord(mk.table);
app_gr.name = (mk.name.replace('Pending: ', '')) + '@' + parent_this.ci_name;
app_gr.running_process = mk.running_process;
app_gr.classifier = matches[key].sys_id;
// We used to set category and subcategory here. See PRB1319943.
addScriptedFieldValuesToApp(app_gr, mk);
app_gr.first_discovered = currDateTime;
app_gr.last_discovered = currDateTime;
if (JSUtil.notNil(accAgentId))
app_gr.discovery_source = parent_this.sensor.getCmdbRecord().discovery_source;
else
app_gr.discovery_source = gs.getProperty('glide.discovery.source_name', "ServiceNow");
var rp_gr = new GlideRecord("cmdb_running_process");
if (mk.running_process)
rp_gr.get('sys_id', mk.running_process);
if (JSUtil.notNil(rp_gr))
addRunningProcessInfoToApp(app_gr, rp_gr);
app_sys_id = app_gr.insert();
// insert our relationship record...
rel_gr.initialize();
rel_gr.type = mk.relation_type;
rel_gr.child = parent_this.ci_sys_id;
rel_gr.parent = app_sys_id;
rel_gr.insert();
// Grab existing clustered application if appropriate
if (JSUtil.notNil(parent_this.clusterGr)) {
var existingClusteredAppSysId = new ADMUtil().reconcileExistingClusteredApp(parent_this.clusterGr, app_sys_id, mk.relation_type, parent_this.ci_sys_id + '');
if (existingClusteredAppSysId)
app_sys_id = existingClusteredAppSysId;
}
cl_info.app = '' + app_sys_id;
// Tie application to cluster if appropriate
if (parent_this.clusteredPIDs[mk.pid]) {
var clusterSysId = parent_this.clusterGr.sys_id;
var rel_type_gr = new GlideRecord("cmdb_rel_type");
var rel_type = rel_type_gr.get(mk.relation_type) ? '' + rel_type_gr.sys_name : "Runs on::Runs";
g_disco_functions.createRelationshipIfNotExists(app_sys_id, clusterSysId, rel_type);
}
}
}
// CMDB Identification API
function createOrUpdateMatches(parent_this) {
var currDateTime = '' + new GlideDateTime();
for (var key in matches) {
var mk = matches[key];
if ((key.substr(0, 3) != 'EN:'))
continue;
// Add it to our classified map...
var cl_info = {};
cl_info.classifier = mk.sys_id;
cl_info.running_process = mk.running_process;
cl_info.computer = this.ci_sys_id;
classified[mk.running_process] = cl_info;
// Handle orphaned apps
var orphan_id = orphan_apps[mk.cid];
if (!orphan_id)
orphan_id = orphan_apps[mk.running_process];
if (orphan_id) {
rel_gr.initialize();
rel_gr.type = mk.relation_type;
rel_gr.child = parent_this.ci_sys_id;
rel_gr.parent = orphan_id;
rel_gr.insert();
cl_info.app = orphan_id;
continue;
}
// We need the related process command and parameters for CMDB Identification
var rp_gr = new GlideRecord("cmdb_running_process");
if (!rp_gr.get('sys_id', mk.running_process))
continue;
// Checking if match active probe is of topic HorizontalDiscoveryProbe. If so, skipping creation of shell CI
if (skipShellCICreateForPattern(mk)) {
cl_info.app = '';
// skip Discovery log for pattern execution on agents
if (typeof agent_correlator != 'undefined')
DiscoveryLogger.info("Launching Pattern for CI type " + mk.table + ", skipping creation of Shell CI", "ApplicationDependencyMapping", g_probe.getEccQueueId());
continue;
}
// Create container app GlideRecord for CMDB Identification
app_gr = new GlideRecord(mk.table);
app_gr.name = (mk.name.replace('Pending: ', '')) + '@' + parent_this.ci_name;
app_gr.running_process = mk.running_process;
addRunningProcessInfoToApp(app_gr, rp_gr);
app_gr.classifier = mk.sys_id;
// We used to set category and subcategory here. See PRB1319943.
if (JSUtil.notNil(accAgentId))
app_gr.discovery_source = parent_this.sensor.getCmdbRecord().discovery_source;
else
app_gr.discovery_source = gs.getProperty('glide.discovery.source_name', "ServiceNow");
addScriptedFieldValuesToApp(app_gr, mk);
// Get the relationship type for CMDB Identification
var rel_type_gr = new GlideRecord("cmdb_rel_type");
if (!rel_type_gr.get(mk.relation_type))
continue;
var rel_type = '' + rel_type_gr.sys_name;
var host_sys_id = '' + parent_this.ci_sys_id;
var appSysId = DiscoveryCMDBUtil.createOrUpdateApp(app_gr, host_sys_id, rel_type);
if (JSUtil.nil(appSysId) || appSysId === 'Unknown') {
DiscoveryLogger.error("Failed to insert classified application of type: " + mk.table, "ApplicationDependencyMapping", g_probe.getEccQueueId() + '');
delete classified[mk.running_process];
continue;
}
if (JSUtil.notNil(parent_this.clusterGr)) {
var existingClusteredAppSysId = new ADMUtil().reconcileExistingClusteredApp(parent_this.clusterGr, appSysId, mk.relation_type, parent_this.ci_sys_id + '');
if (existingClusteredAppSysId) {
appSysId = existingClusteredAppSysId;
var clusterSysId = parent_this.clusterGr.sys_id;
g_disco_functions.createRelationshipIfNotExists(appSysId, clusterSysId, rel_type);
}
}
cl_info.app = appSysId;
}
}
function skipShellCICreateForPattern(match) {
var hdProbeId = "4f64c6389f230200fe2ab0aec32e7068";
var pgr = new GlideRecord('discovery_classifier_probe');
pgr.addQuery('classy', match.sys_id);
pgr.addActiveQuery();
pgr.query();
while (pgr.next()) {
var probeId = pgr.child;
if (probeId == hdProbeId)
return true;
}
return false;
}
function addScriptedFieldValuesToApp(app_gr, match) {
var added = match.__app_gr;
for (var field in added)
app_gr[field] = added[field];
}
function addRunningProcessInfoToApp(app_gr, rp_gr) {
// PRB688054: Send hashed running process info to IE payload only if oracle DB
if (GlideDBUtil.getPrimaryDBConfigurationParms().getRDBMS() + '' === 'oracle') {
app_gr.rp_command_hash = SNC.DiscoveryHasher.hash('' + rp_gr.command);
app_gr.rp_key_parameters_hash = SNC.DiscoveryHasher.hash('' + rp_gr.key_parameters);
} else {
app_gr.running_process_command = '' + rp_gr.command;
app_gr.running_process_key_parameters = '' + rp_gr.key_parameters;
}
}
// E N D I N N E R F U N C T I O N S
},
/**
* Returns a map, keyed by running process sys_id and correlation id (separately), of
* orphaned app instance sys_id values. An orphaned app instance is one whose correlation ID
* or running process pointer indicate that it once had a relationship to this computer,
* but for some reason it does not have one now.
*/
get_orphaned_apps: function(matches, existing_rels) {
// first iterate over our matches to build sets of running process sys_ids and correlation ids...
var rpcr_ids = {};
for (var key in matches)
rpcr_ids[matches[key].running_process] = matches[key].cid;
// delete all those that matched an existing relationship...
for (var key in existing_rels)
delete rpcr_ids[existing_rels[key].running_process];
// build lists of running process sys_ids and correlation ids that we can query for...
var rp_ids = [];
var cr_ids = [];
for (var key in rpcr_ids) {
rp_ids.push(key);
cr_ids.push(rpcr_ids[key]);
}
// query cmdb_ci_appl to see if we have any orphans, and make a result map...
var result = {};
var app_gr;
// once for running processes
app_gr = new GlideRecord('cmdb_ci_appl');
app_gr.addQuery('running_process', 'IN', rp_ids);
app_gr.query();
while (app_gr.next()) {
if (!app_gr.running_process.nil())
result['' + app_gr.running_process] = '' + app_gr.sys_id;
}
// once for correlation ids
app_gr = new GlideRecord('cmdb_ci_appl');
app_gr.addQuery('correlation_id', 'IN', cr_ids);
app_gr.query();
while (app_gr.next()) {
if (!app_gr.correlation_id.nil())
result['' + app_gr.correlation_id] = '' + app_gr.sys_id;
}
return result;
},
/**
* Returns an object whose properties are named according to lookup keys, and whose values are all
* objects containing cmdb_rel_ci record values plus some values from the related app instance.
* There are several different kinds of lookup keys populated (one per cmdb_rel_ci record). Note
* that duplicates and relationships to manually-entered app instances are ignored:
*
* Exact match for new style classified applications:
* key: 'EN:' + computer.sys_id + ':' + classifier.sys_id + ':' + running_process.sys_id
*
* Fuzzy match for new style classified applications (used when running process changed):
* key: 'FN:' + computer.sys_id + ':' + classifier.sys_id
*
* Exact match for old style classified applications:
* key 'EO:' + correlation ID
* Match no classifier (NC) applications ( PRB1198119: When ServiceMapping creates application without classifier) :
* key: 'NC:' + computer.sys_id + ':' + running_process.sys_id
*/
get_existing_relationships: function() {
//First determine which appl tables we care about for ADM,
// namely those tables which we populate via process classification
var appTableList = [];
var ga = new GlideAggregate("discovery_classy_proc");
ga.groupBy("table");
ga.query();
while (ga.next())
appTableList.push(ga.table + '');
//Now find all the apps which have a relationship with this computer CI
var result = {};
var rgr = new GlideRecord('cmdb_rel_ci');
if (JSUtil.notNil(this.clusterGr))
rgr.addQuery('child', [this.ci_sys_id, this.clusterGr.sys_id]); // IF it's a cluster, then look for the apps associated with it as well
else
rgr.addQuery('child', this.ci_sys_id);
rgr.addQuery('parent.sys_class_name', appTableList);
rgr.query();
while (rgr.next()) {
var er = {};
er.sys_id = '' + rgr.sys_id;
er.parent = '' + rgr.parent;
er.child = '' + rgr.child;
er.type = '' + rgr.type;
er.cid = '' + rgr.parent.correlation_id;
er.classifier = '' + rgr.parent.classifier;
er.running_process = '' + rgr.parent.running_process;
// make the right keys...
if (er.classifier) {
if (er.running_process)
result['EN:' + er.child + ':' + er.classifier + ':' + er.running_process] = er;
else
result['FN:' + er.child + ':' + er.classifier] = er;
} else if (er.cid) {
var cid = er.cid;
// Just because there's a correlation id, it doesn't mean it is the old format. The format we want to check for is
// something like checksum@sys_id (ex: 342452354@9e63a3d7ef03010098d5925495c0fb50)
var position = cid.indexOf("@"); //should have an @ sign
if (position <= 0)
continue;
if (!cid.substring(0, position).match(/^[0-9]+$/)) //if not all number, since checksum is all numbers
continue;
result['EO:' + er.cid] = er;
} else if (er.running_process) {
result['NC:' + er.child + ':' + er.running_process] = er;
}
}
return result;
},
/**
* Checks if we have a process classification match for running processes that we find.
* Stores all the running processes that have a classifier match in matched_processes.
*/
get_matches: function() {
// will hold all the matches we find...
var matches = [];
// load up all our process classifiers...
var classifiers = this.get_classifiers();
// get a combined filter from all our classifiers
var combined_filter = this.get_combined_filter(classifiers);
// iterate over all our running processes to classify them...
var rp_gr = new GlideRecord('cmdb_running_process');
var rps = this.sensor.running_processes;
for (var i = 0; i < rps.length; i++) {
var rp = rps[i];
// PRB636183: need to add the computer field to running process before process classification
// prior to Fuji, this was already done in RunningProcessReconciler..
rp.computer = this.sensor.getCmdbCi();
// if we're not supposed to classify this process, well, then, DON'T...
if (JSUtil.notNil(rp.classify) && ('' + rp.classify) != 'true')
continue;
// make a fake GlideRecord...
rp_gr.initialize();
for (var field in rp)
rp_gr[field] = rp[field];
// if none of our filters match, then don't check individual process classifiers..
if (!this.sensor.accAgentId && JSUtil.notNil(combined_filter) && !combined_filter.match(rp_gr, true))
continue;
// see if any of our conditions match...
for (var j = 0; j < classifiers.length; j++) {
var classifier = classifiers[j];
// if this classifier only checks parameters, and we don't have any then just skip it..
if (JSUtil.nil(rp_gr['parameters']) && classifier.is_param_only)
continue;
if (classifier.filter && classifier.filter.match(rp_gr, true)) {
rp.classifier = classifier;
matches.push(rp);
break;
}
}
}
return matches;
},
/**
* Returns an object whose properties are named according to lookup keys, and whose values are all
* objects containing information from the matching classifier and from the running process record
* that matched in get_matches() (above). There are four different kinds of lookup keys populated
* for each classifier match:
*
* Exact match for new style classified applications:
* key: 'EN:' + computer.sys_id + ':' + classifier.sys_id + ':' + running_process.sys_id
*
* Fuzzy match for new style classified applications (used when running process changed):
* key: 'FN:' + computer.sys_id + ':' + classifier.sys_id
*
* Exact match for old style classified applications:
* key 'EO:' + correlation ID
*
* Match no classifier (NC) applications ( PRB1198119: When ServiceMapping creates application without classifier) :
* key: 'NC:' + computer.sys_id + ':' + running_process.sys_id
*/
save_matches: function() {
// will hold all the matches we find...
var matches = {};
for (var i = 0; i < this.matched_processes.length; i++) {
var rp = this.matched_processes[i];
// if sys_id is null, then this is a forked process (deleted in EnrichProcessesAndConnections), so skip it
if (JSUtil.nil(rp.sys_id))
continue;
// make a fake GlideRecord...
var rp_gr = new GlideRecord('cmdb_running_process');
rp_gr.initialize();
for (var field in rp)
rp_gr[field] = rp[field];
this.matched(matches, rp_gr, rp_gr.classifier);
}
return matches;
},
/**
* Adds records as maps of information to each of the three keys described in save_matches() (above) to the
* given matches object for a match between the given classifier and the given running process.
*/
matched: function(matches, rp_gr, classifier) {
var match = {};
// clone the classifier...
for (var prop in classifier)
match[prop] = classifier[prop];
// capture everything we need from the process record...
match.running_process = '' + rp_gr.sys_id;
match.pid = '' + rp_gr.pid;
match.computer = '' + rp_gr.computer;
match.parent = '' + rp_gr.parent;
match.cid = new GlideChecksum('' + rp_gr.command + ' ' + rp_gr.key_parameters).get() + '@' + rp_gr.computer;
// run the "On classification" script now, as may add some data to our match...
this.runOnClassificationScript(match);
// make our four keys and stuff our value into each...
matches['EN:' + match.computer + ':' + match.sys_id + ':' + match.running_process] = match;
matches['FN:' + match.computer + ':' + match.sys_id] = match;
matches['EO:' + match.cid] = match;
matches['NC:' + match.computer + ':' + match.running_process] = match;
},
/**
* Run the "On classification" script for the given match. Changes to the application instance record made by
* the script are added to the __app_gr property as a name/value map.
*/
runOnClassificationScript: function(match) {
this.putScriptGlobals(match);
// now run the script...
eval(match.script);
// stuff any changed values for the application instance into the __app_gr property...
match.__app_gr = current;
// stuff any parametes to be passed on through g_probe_parameters by keeping track of the process
this.paramsToPass[match.running_process] = g_probe_parameters;
},
/**
* Puts the necessary global values into the script environment.
*/
putScriptGlobals: function(match) {
//Exposing deviceGR because some process classification scripts have come to expect it.
this.sensor['deviceGR'] = this.sensor.getCmdbRecord();
var proc = this.procByID[match.running_process];
// put the expected global variables in place...
current = {};
type = this.getOSType();
classifier_name = match.name;
if (proc) {
name = proc.name;
command = proc.command;
parameters = proc.key_parameters;
}
// Exposing the process object itself
g_process = proc;
// Exposing the process classifier GlideRecord
g_classification = match;
// Exposing the parameter for passing parameters through
g_probe_parameters = {};
//Expose values as a Java hashmap for historical reasons...
values = new Packages.java.util.HashMap();
for (var i in proc)
values.put(i, proc[i]);
values.put("type", type);
},
/**
* Launch any probes needed.
*/
launchProbes: function(classified) {
for (var rp in classified) {
var match = classified[rp];
// get the probes we need to trigger; bail out if there weren't any...
var probes = this.getTriggeredProbes(match);
if (!probes)
continue;
// we have at least one probe to launch, so gather the bits of information we'll need to do it...
var source = '' + this.sensor.getParameter('source');
var cmdb_ci = '' + match.app;
var classifier = match.classifier;
var proc = this.procByID[match.running_process];
var agent = '' + this.sensor.getAgent();
var ecc_id = '' + this.sensor.getEccQueueId();
var port = '' + this.sensor.getParameter('port');
var priority = this.sensor.getParameter('priority');
var mid_selector_details = this.sensor.getParameter('mid_selector_details');
// now iterate over our probe records, launching them...
while (probes.next()) {
var probe = new SncProbe(probes, proc);
probe.setSource(source);
probe.addParameter('classifier', classifier);
probe.addParameter('cmdb_ci', cmdb_ci);
probe.addParameter('os_type', this.getOSType());
if (port)
probe.addParameter('port', port);
if (priority)
probe.setEccPriority(priority);
if (mid_selector_details)
probe.setMidSelectDetails(mid_selector_details);
// Adding addition parameters from g_probe_parameter
this._addProbeParameters(probe, match);
probe.create(agent, ecc_id);
}
}
},
/**
* Returns a GlideRecord instance of the probes to be triggered for the given classifier ID, or null if there are none.
*/
getTriggeredProbes: function(match) {
// temporarily expose any probe parameters passed from on classification script of process classifier
var probe_parm = g_probe_parameters; // save it
g_probe_parameters = this.paramsToPass[match.running_process];
// first, figure out which probes, if any, we need to launch...
var probe_ids = [];
var pgr = new GlideRecord('discovery_classifier_probe');
pgr.addQuery('classy', match.classifier);
pgr.addActiveQuery();
pgr.query();
while (pgr.next())
if (this.triggerCondition(pgr))
probe_ids.push('' + pgr.child);
g_probe_parameters = probe_parm; // restore it
// get out of dodge if there are no probes needed...
if (probe_ids.length == 0)
return null;
// now get a glide record with just these probes in it...
var rgr = new GlideRecord('discovery_probes');
rgr.addQuery('sys_id', probe_ids);
rgr.query();
return rgr;
},
/**
* Evaluates the trigger condition on the given discovery_classifier_probe record and returns with the boolean result.
*/
triggerCondition: function(triggerGR) {
var conditionScript = '' + triggerGR.condition_script;
if (!conditionScript)
return true;
return eval('!!(' + conditionScript + '\n)');
},
/*
* Adding additional probe parameters from g_probe_parameters, which is stored in the paramsToPass variable
*/
_addProbeParameters: function(probe, match) {
var parameters = this.paramsToPass[match.running_process];
for (var param in parameters)
probe.addParameter(param, parameters[param]);
},
/**
* Returns the operating system type (windows, unix, or esx) based on the CI's type...
*/
getOSType: function() {
var ci_class = '' + this.sensor.getCmdbRecord().sys_class_name;
if (ci_class == 'cmdb_ci_esx_server')
return 'esx';
if (ci_class.indexOf('win_server') >= 0)
return 'windows';
if (ci_class == 'cmdb_ci_computer') {
var os = this.sensor.getCmdbRecord().os;
if (os.indexOf("Windows") >= 0)
return 'windows';
}
return 'unix';
},
/**
* Returns an array of all our process classifiers (as objects), in order. Also populates this.classifierByID with
* a map (by sys_id) of all our classifiers.
*/
get_classifiers: function() {
// load up all our process classifiers...
this.classifierByID = {};
var classifiers = [];
var pc_gr = new GlideRecord('discovery_classy_proc');
pc_gr.orderBy('order');
pc_gr.addActiveQuery();
if (gs.getProperty('glide.discovery.auto_adm', 'false') == 'false') //If auto_adm is not enabled, then don't get the pending classifiers
pc_gr.addQuery("name", "NOT LIKE", "Pending:%");
pc_gr.query();
while (pc_gr.next()) {
var classifier = {};
classifier.name = '' + pc_gr.name;
classifier.condition = '' + pc_gr.condition;
classifier.table = '' + pc_gr.table;
classifier.script = '' + pc_gr.script;
classifier.match_criteria = '' + pc_gr.match_criteria;
classifier.relation_type = '' + pc_gr.relation_type;
classifier.sys_id = '' + pc_gr.sys_id;
classifier.is_param_only = this.is_parameters_classifier(classifier);
//For efficiency, instantiate a "filter" object for checking the classifier conditions:
if (classifier.condition)
classifier.filter = new SNC.Filter(classifier.condition, "rule-condition");
classifiers.push(classifier);
this.classifierByID[classifier.sys_id] = classifier;
}
return classifiers;
},
/**
* Check if this classifier is a parameters only classifier.
* Returns true if parameters are required for this classifier.
*/
is_parameters_classifier: function(classifier) {
var conditions = classifier.condition.split(/\^EQ|\^NQ|\^OR|\^/);
for (var i = 0; i < conditions.length; i++) {
if (conditions[i] != '' && !conditions[i].startsWith("parameters"))
return false;
}
return true;
},
/**
* Combine all the process classifiers into one aggregate condition.
* Returns a filter that checks if any of our classifiers match.
*/
get_combined_filter: function(classifiers) {
if (gs.getProperty('glide.discovery.auto_adm', 'false') == 'true')
return null;
var combined = "";
for (var i = 0; i < classifiers.length; i++) {
var classifier = classifiers[i];
combined += classifier.condition.slice(0, classifier.condition.length - 3) + "^NQ";
}
combined = combined.slice(0, combined.length - 3) + "^EQ";
return new SNC.Filter(combined, "rule-condition");
},
/*
* Checks if pattern execution is enabled on agent & if a valid accAgentId exists
* Returns true/false
*/
isPatternExecutionEnabledOnAgent: function(accAgentId) {
return (gs.getProperty('sn_agent.appl_classification_behavior', 'simple') == 'full' && accAgentId);
},
/*
* Creates a probe to launch application patterns on a given agent
* Returns the output ID of the probe created
*/
createPatternProbeForAgent: function(targetAgent, match, procByID, probes) {
var agentId = '' + targetAgent.agentId; //accAgentId
var classifier = match.classifier;
var cmdbCi = '' + match.app;
var osType = '' + targetAgent.osType;
var mid = '' + targetAgent.midServerName;
var mid_selector_details = "{\"mode\":\"specific_mid\"}";
var proc = procByID;
var source = '' + targetAgent.ipAddress;
// now iterate over our probe records, launching them...
while (probes.next()) {
var probe = new SncProbe(probes, proc);
probe.setSource(source);
probe.addParameter('agent_id', agentId); // for ACC-F
probe.addParameter('classifier', classifier);
probe.addParameter('cmdb_ci', cmdbCi);
probe.addParameter('os_type', osType);
probe.setMidSelectDetails(mid_selector_details);
// Adding addition parameters from g_probe_parameter
this._addProbeParameters(probe, match);
probe.create(mid);
}
},
type: 'ApplicationDependencyMapping'
};
Sys ID
445d60709733200010cb1bd74b2975a1