Name
global.TemplateTaskRunPopulator
Description
Script to populate reconcile_template_task_run records for the template runs created in draft state
Script
/**
* This script include serves the following purpose:
* 1. Get all the template runs in 'draft' state
* 2. For each run, populate the template column for tasks if there is a condition defined on the template
* 3. For each run, populate the task run records for the manual and conditionally added tasks in 'ready' state
* 4. For each run, populate the task run records for the skipped tasks in 'skipped' state
* 5. Update the template run state to 'ready'
*/
var TemplateTaskRunPopulator = Class.create();
TemplateTaskRunPopulator.prototype = {
MUTEX_EXPIRATION_TIME_PROPERTY: 'glide.cmdb.create_template_task_run.mutex_expiration_time_seconds',
TEMPLATE_TASK_RUN_POPULATOR_MAX_RUN_TIME_PROPERTY: 'glide.cmdb.template_task_run.max_run_time_seconds',
MUTEX_NAME_PREFIX: 'reconcile_duplicate_template_run_',
RECONCILE_DUPLICATE_TEMPLATE: 'reconcile_duplicate_template',
RECONCILE_DUPLICATE_TEMPLATE_RUN: 'reconcile_duplicate_template_run',
RECONCILE_DUPLICATE_TEMPLATE_TASK_RUN: 'reconcile_duplicate_template_task_run',
RECONCILE_DUPLICATE_TASK: 'reconcile_duplicate_task',
RECONCILE_DUPLICATE_TASK_DATA: 'reconcile_duplicate_task_data',
DIFFERENT_TEMPLATE: 'different_template',
TEMPLATE_TASK_NOT_COMPATIBLE: 'template_task_not_compatible',
UPDATE_FAILED: 'update_failed',
OPEN_STATE: 1,
BATCH_SIZE: 100,
GLIDE_AGGREGATES_MAX_LIMIT_PROPERTY: 'glide.db.max.aggregates',
initialize: function() {
this.MUTEX_EXPIRATION_TIME = gs.getProperty(this.MUTEX_EXPIRATION_TIME_PROPERTY, 4 * 60 * 60); // 4 hours
this.MAX_RUN_TIME = gs.getProperty(this.TEMPLATE_TASK_RUN_POPULATOR_MAX_RUN_TIME_PROPERTY, 1 * 60 * 60); // 1 hour
this.IS_DOMAIN_SUPPORT_ACTIVE = GlidePluginManager.isActive('com.glide.domain.msp_extensions.installer');
this.MAX_SUPPORTED_GLIDE_AGGREGATES = parseInt(gs.getProperty(this.GLIDE_AGGREGATES_MAX_LIMIT_PROPERTY, 100000));
},
populateRecords: function() {
var timer = new GlideStopWatch();
var maxRunTimeInMs = this.MAX_RUN_TIME * 1000;
while (timer.getTime() < maxRunTimeInMs) {
// Get the next template run currently in "draft" state
var templateRunObj = this._getTemplateRunInDraftState();
// No records in the "draft" state. Nothing to process
if (Object.keys(templateRunObj).length == 0) {
break;
}
/**
* 1. Try to acquire lock for the current template run_id.
* 2. If we are not able to acquire the lock, do NOT retry as some other job might be working on it
* 3. Proceed with the next run id and repeat the same steps
*/
var mutex = new GlideSelfCleaningMutex(this.mutexName + templateRunObj.run, 'TemplateTaskRunPopulator');
mutex.setMutexExpires(this.MUTEX_EXPIRATION_TIME * 1000);
mutex.setMaxSpins(0);
if (mutex.get()) {
try {
// Populate the task run records for each of the template runs
var response = this._populateTaskRunRecords(templateRunObj);
if (response.status == 'success') {
// Update template run "state" column to ready and total column to # of tasks in the template task run table
var templateRunGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE_RUN);
templateRunGr.get(templateRunObj.run);
templateRunGr.setValue('state', 'ready');
templateRunGr.setValue('total', response.total);
templateRunGr.setValue('skipped', response.skipped);
templateRunGr.update();
}
} finally {
mutex.release();
}
} else {
gs.info('Falied to acquire mutex for Template Run: ' + templateRunObj.run);
}
}
},
/**
* Get the next template run currently in "draft state"
*/
_getTemplateRunInDraftState: function() {
var templateRunObj = {};
var templateRunGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE_RUN);
templateRunGr.addQuery('state', 'draft');
templateRunGr.orderBy('sys_created_on');
templateRunGr.setLimit(1);
templateRunGr.query();
if (templateRunGr.next()) {
templateRunObj = {
run: templateRunGr.getValue('sys_id'),
template: templateRunGr.getValue('template'),
domain: templateRunGr.getValue('sys_domain')
};
}
return templateRunObj;
},
/**
* Populate the task run records for the given template run
*/
_populateTaskRunRecords: function(templateRunObj) {
var runId = templateRunObj.run;
var templateId = templateRunObj.template;
var domain = templateRunObj.domain;
var response = {};
// If the template does not exist, log error and return
var templateGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE);
if (!templateGr.get(templateId)) {
gs.error('No template exists for run id: ' + runId);
response.status = 'error';
response.total = 0;
response.skipped = 0;
return response;
}
// Condition defined to get tasks on the template
var condition = templateGr.getValue('task_conditions');
/**
* If there is a condition defined, populate the template for the tasks that match the condition
* Otherwise, get the list of manually added tasks for the template
*/
var resultTaskIds = {};
if (condition) {
resultTaskIds = this._populateTemplateForManualAndConditionalTasks(condition, templateId, domain);
} else {
resultTaskIds = this._getManualTaskIdsForTemplate(templateId);
}
// Populate the template task run records for the valid task ids with "state" as "ready"
var validTaskIds = Object.keys(resultTaskIds.validTaskIds);
for (var i = 0; i < validTaskIds.length; i++) {
var templateTaskRunGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE_TASK_RUN);
templateTaskRunGr.setValue('template', templateId);
templateTaskRunGr.setValue('task', validTaskIds[i]);
templateTaskRunGr.setValue('run', runId);
templateTaskRunGr.setValue('state', 'ready');
templateTaskRunGr.setValue('sys_domain', domain);
templateTaskRunGr.insert();
}
// Populate the template task run records for the skipped task ids with "state" as "skipped"
var skippedTaskIds = Object.keys(resultTaskIds.skippedTaskIds);
for (var j = 0; j < skippedTaskIds.length; j++) {
var taskId = skippedTaskIds[j];
var templateTaskRunSkippedGr = new GlideRecord(this.RECONCILE_DUPLICATE_TEMPLATE_TASK_RUN);
templateTaskRunSkippedGr.setValue('template', templateId);
templateTaskRunSkippedGr.setValue('task', taskId);
templateTaskRunSkippedGr.setValue('message', resultTaskIds.skippedTaskIds[taskId].message);
templateTaskRunSkippedGr.setValue('message_type', resultTaskIds.skippedTaskIds[taskId].messageType);
templateTaskRunSkippedGr.setValue('run', runId);
templateTaskRunSkippedGr.setValue('state', 'skipped');
templateTaskRunSkippedGr.setValue('sys_domain', domain);
templateTaskRunSkippedGr.insert();
}
response.status = 'success';
response.total = validTaskIds.length + skippedTaskIds.length;
response.skipped = skippedTaskIds.length;
return response;
},
/**
* For tasks that match the condition and do not have the template, populate the template
* For tasks that match the condition but already have a template, skip them
* For tasks that do not match the condition but already have a template, do nothing
* Return the set of valid (manual and conditional) and skipped task ids
*/
_populateTemplateForManualAndConditionalTasks: function(condition, templateId, domain) {
// Tasks whose template column is already populated with some other templateId
var skippedTaskIds = {};
// Tasks whose template column is/will be populated with the current templateId
var validTaskIds = {};
// Tasks whose template column is not populated yet
var noTemplateTaskIds = {};
// Populate the manually added task ids first
validTaskIds = this._getManualTaskIdsForTemplate(templateId).validTaskIds;
var currentDomain = GlideSession.get().getCurrentDomainID();
try {
if (this.IS_DOMAIN_SUPPORT_ACTIVE) {
// Change the domain to the template run's domain
GlideSession.get().setDomainID(domain);
}
var start = 0;
var end = this.MAX_SUPPORTED_GLIDE_AGGREGATES;
// Platform has a limitation on number of aggregates returned using query, workaround is to call multiple times with varying offset
// If glide aggregate returns less data rows than asked (end -start), it indicates there is no more data to query
// If it returned exact rows as asked, we need to query again as there is potential of more data available
do {
var singleBatchRowCount = 0;
// Get all de-duplication tasks by evaluating the specified condition on the reconcile_duplicate_task_data table
var duplicateTaskDataGr = this._getTaskDataRecords(condition, start, end);
/**
* If the template column is equal to the templateId, the task was manually added to the current template
* If the template column is not equal to the templateId and it not null, the task is already a part of different template
* If the template column is null, the task is not assigned to any template
*/
while (duplicateTaskDataGr.next()) {
var currentTemplateId = duplicateTaskDataGr.getValue('task.template');
var currentTaskId = duplicateTaskDataGr.getValue('task');
if (currentTemplateId == templateId) {
validTaskIds[currentTaskId] = {};
} else if (currentTemplateId) {
skippedTaskIds[currentTaskId] = {};
skippedTaskIds[currentTaskId].messageType = this.DIFFERENT_TEMPLATE;
skippedTaskIds[currentTaskId].message = 'Task is already added to a different template ' + currentTemplateId;
} else {
noTemplateTaskIds[currentTaskId] = {};
}
singleBatchRowCount++;
}
start = end + 1;
end = end + this.MAX_SUPPORTED_GLIDE_AGGREGATES;
} while (singleBatchRowCount == this.MAX_SUPPORTED_GLIDE_AGGREGATES);
} finally {
if (this.IS_DOMAIN_SUPPORT_ACTIVE) {
// Change the domain back to current domain
GlideSession.get().setDomainID(currentDomain);
}
}
// noTemplateTaskIds contains the tasks which matched the conidtion but do not have the template populated yet
// Check if these tasks can be added to the template
var canAddTasksToTemplateResult = sn_cmdb.DuplicateTemplate.canAddTasksToTemplate(Object.keys(noTemplateTaskIds), templateId);
noTemplateTaskIds = canAddTasksToTemplateResult.success.reduce(function(a, c) {
a[c] = true;
return a;
}, {});
// Add the tasks that cannot be added to template to the skippedTaskIds
for (var i in canAddTasksToTemplateResult.skipped) {
var skippedTaskId = canAddTasksToTemplateResult.skipped[i];
if (!skippedTaskIds[skippedTaskId]) {
skippedTaskIds[skippedTaskId] = {};
skippedTaskIds[skippedTaskId].messageType = this.TEMPLATE_TASK_NOT_COMPATIBLE;
skippedTaskIds[skippedTaskId].message = 'Class hierarchy for task and template do not match';
}
}
for (var j in canAddTasksToTemplateResult.failed) {
var failedTaskId = canAddTasksToTemplateResult.failed[j];
if (!skippedTaskIds[failedTaskId]) {
skippedTaskIds[failedTaskId] = {};
skippedTaskIds[failedTaskId].messageType = this.TEMPLATE_TASK_NOT_COMPATIBLE;
skippedTaskIds[failedTaskId].message = 'Cannot add task to template because of sn_cmdb.DuplicateTemplate.canAddTasksToTemplate API errors';
}
}
// Populate template column for the list of tasks with no template
var populateTemplateResult = this._populateTemplateForTaskIds(Object.keys(noTemplateTaskIds), templateId);
// Some tasks within noTemplateTaskIds might not get updated with template. Include them in the skippedTaskIds
for (var skipped in populateTemplateResult.skipped) {
if (!skippedTaskIds[skipped]) {
skippedTaskIds[skipped] = {};
skippedTaskIds[skipped].messageType = this.UPDATE_FAILED;
skippedTaskIds[skipped].message = 'Cannot update template column for task due to update errors';
}
}
// Add the tasks for which template column was populated in the set of validTaskIds
for (var success in populateTemplateResult.success) {
validTaskIds[success] = {};
}
var resultTaskIds = {
'validTaskIds': validTaskIds,
'skippedTaskIds': skippedTaskIds
};
return resultTaskIds;
},
/**
* Get the manually added task ids for the template
*/
_getManualTaskIdsForTemplate: function(templateId) {
var manuallyAddedTaskIds = {};
var duplicateTaskGr = new GlideRecord(this.RECONCILE_DUPLICATE_TASK);
duplicateTaskGr.addQuery('template', templateId);
duplicateTaskGr.addQuery('state', this.OPEN_STATE);
duplicateTaskGr.query();
while (duplicateTaskGr.next()) {
var taskId = duplicateTaskGr.getValue('sys_id');
manuallyAddedTaskIds[taskId] = {};
}
var resultTaskIds = {
'validTaskIds': manuallyAddedTaskIds,
'skippedTaskIds': {}
};
return resultTaskIds;
},
/**
* Get all the open tasks by evaluating the condition on the reconcile_duplicate_task_data table
*/
_getTaskDataRecords: function(condition, start, end) {
var duplicateTaskDataGr = new GlideAggregate(this.RECONCILE_DUPLICATE_TASK_DATA);
duplicateTaskDataGr.addEncodedQuery(condition);
duplicateTaskDataGr.groupBy('task');
duplicateTaskDataGr.groupBy('task.template');
duplicateTaskDataGr.addQuery('task.state', this.OPEN_STATE);
duplicateTaskDataGr.chooseWindow(start, end);
duplicateTaskDataGr.query();
return duplicateTaskDataGr;
},
/**
* Populate the "template" column for the list of task ids
*/
_populateTemplateForTaskIds: function(taskIds, templateId) {
var successTaskIds = {};
var skippedTaskIds = {};
for (var i = 0; i < taskIds.length; i += this.BATCH_SIZE) {
var chunk = taskIds.slice(i, i + this.BATCH_SIZE);
var duplicateTaskGr = new GlideRecord(this.RECONCILE_DUPLICATE_TASK);
duplicateTaskGr.addQuery('sys_id', 'IN', chunk);
duplicateTaskGr.addNullQuery('template');
duplicateTaskGr.setValue('template', templateId);
// updateMultiple() will still run individual update() operations because of BRs on the task table
// TODO: See if we can disable the BRs by analyzing what the OOB BRs are doing
duplicateTaskGr.updateMultiple();
// We need to check again if the update was successful because some of the tasks can be added to the templates (manually/through other template run)
duplicateTaskGr = new GlideRecord(this.RECONCILE_DUPLICATE_TASK);
duplicateTaskGr.addQuery('sys_id', 'IN', chunk);
duplicateTaskGr.addQuery('template', templateId);
duplicateTaskGr.query();
while (duplicateTaskGr.next()) {
var sysId = duplicateTaskGr.getValue('sys_id');
successTaskIds[sysId] = true;
}
// The tasks that were not updated with the template should be considered as skipped
for (var j in chunk) {
var taskId = chunk[j];
if (!successTaskIds[taskId]) {
skippedTaskIds[taskId] = true;
}
}
}
return {
'success': successTaskIds,
'skipped': skippedTaskIds
};
},
type: 'TemplateTaskRunPopulator'
};
Sys ID
50607c205b71211006447da52d81c766