RESTlet调用MapReduce脚本示例
开发背景
bom批量传入修改接口,单个RESTlet超脚本治理用量限制(5000),可以考虑在RESTlet中调用MapReduce脚本提交作业实例,新作业不受用量限制,可以自包容处理超用量问题(白皮书:如果map /reduce作业违反了NetSuite治理的某些方面,则map/reduce框架会自动生成作业并重新安排其工作以供日后使用,而不会中断脚本)。
RESTlet脚本
define([ 'N/record', 'N/search', 'N/runtime', 'N/task', 'dao' ],
function(record
, search
, runtime
, task
, dao
) {
function doPut(requestBody
) {
log
.audit("Remaining governance units start: " + runtime
.getCurrentScript().getRemainingUsage());
var mapReduceScriptTask
= task
.create({
taskType
: task
.TaskType
.MAP_REDUCE
});
mapReduceScriptTask
.scriptId
= 'customscript_if_bom_update_mapreduce';
mapReduceScriptTask
.deploymentId
= 'customdeploy_if_bom_update_mapreduce';
mapReduceScriptTask
.params
= {
"custscript_requestbody" : requestBody
};
var mapReduceScriptTaskId
= mapReduceScriptTask
.submit();
var taskStatus
= task
.checkStatus({
taskId
: mapReduceScriptTaskId
});
log
.debug("taskStatus: " + taskStatus
.status
);
while (taskStatus
.status
=== 'PENDING' || taskStatus
.status
=== 'PROCESSING') {
sleep(5000);
taskStatus
= task
.checkStatus({
taskId
: mapReduceScriptTaskId
});
}
log
.debug("taskStatus: " + taskStatus
.status
);
if (taskStatus
.status
== 'COMPLETE')
log
.debug("Complete");
else if (taskStatus
.status
== 'FAILED')
log
.debug("Failed");
log
.audit("Remaining governance units end: " + runtime
.getCurrentScript().getRemainingUsage());
return {
'MapReduceScriptTaskStatus' : taskStatus
}
function sleep(milliseconds
) {
var start
= new Date().getTime();
for (var i
= 0; i
< 1e7; i
++) {
if ((new Date().getTime() - start
) > milliseconds
) {
break;
}
}
}
}
return {
put
: doPut
};
});
Map/Reduce脚本
define([ 'N/runtime', 'N/record', 'N/error', 'N/email', 'N/log', 'utils', 'dao' ],
function(runtime
, record
, error
, email
, log
, utils
, dao
) {
function handleErrorAndSendNotification(e
, stage
) {
log
.error('Stage: ' + stage
+ ' failed', e
);
var author
= 27;
var recipients
= '377132229@qq.com';
var subject
= 'Map/Reduce script ' + runtime
.getCurrentScript().id
+ ' failed for stage: ' + stage
;
var body
= 'An error occurred with the following information:\n' + 'Error code: ' + e
.name
+ '\n' + 'Error msg: ' + e
.message
;
email
.send({
author
: author
,
recipients
: recipients
,
subject
: subject
,
body
: body
});
}
function handleErrorIfAny(summary
) {
var inputSummary
= summary
.inputSummary
;
var mapSummary
= summary
.mapSummary
;
var reduceSummary
= summary
.reduceSummary
;
if (inputSummary
.error
) {
var e
= error
.create({
name
: 'INPUT_STAGE_FAILED',
message
: inputSummary
.error
});
handleErrorAndSendNotification(e
, 'getInputData');
}
handleErrorInStage('map', mapSummary
);
handleErrorInStage('reduce', reduceSummary
);
}
function handleErrorInStage(stage
, summary
) {
var errorMsg
= [];
summary
.errors
.iterator().each(function(key
, value
) {
var msg
= 'Failure update from BOM data: ' + key
+ '. Error was: ' + JSON.parse(value
).message
+ '\n';
errorMsg
.push(msg
);
return true;
});
if (errorMsg
.length
> 0) {
var e
= error
.create({
name
: 'BOM_UPDATE_FAILED',
message
: JSON.stringify(errorMsg
)
});
handleErrorAndSendNotification(e
, stage
);
}
}
function createSummaryRecord(summary
) {
try {
var seconds
= summary
.seconds
;
var usage
= summary
.usage
;
var yields
= summary
.yields
;
var rec
= record
.create({
type
: 'customrecord_summary',
});
rec
.setValue({
fieldId
: 'name',
value
: 'Summary for M/R script: ' + runtime
.getCurrentScript().id
});
rec
.setValue({
fieldId
: 'custrecord_time',
value
: seconds
});
rec
.setValue({
fieldId
: 'custrecord_usage',
value
: usage
});
rec
.setValue({
fieldId
: 'custrecord_yields',
value
: yields
});
rec
.save();
} catch (e) {
handleErrorAndSendNotification(e
, 'summarize');
}
}
function getInputData(context
) {
log
.audit("Remaining governance units start: " + runtime
.getCurrentScript().getRemainingUsage());
return utils
.string2JSONObject(runtime
.getCurrentScript().getParameter('custscript_requestbody'));
}
function map(context
) {
log
.debug({
title
: "mapContext 1",
details
: context
});
var i
= 0;
var bomList
= JSON.parse(context
.value
);
bomList
.forEach(function(b
) {
context
.write({
key
: i
,
value
: b
});
i
++;
});
}
function reduce(context
) {
log
.debug({
title
: "reduceContext context key",
details
: context
.key
});
log
.debug({
title
: "reduceContext context values[0] before JSON.parse",
details
: context
.values
[0]
});
log
.debug({
title
: "reduceContext context values[0] after JSON.parse",
details
: JSON.parse(context
.values
[0])
});
var result
= dao
.upsertBomAllRecord('PUT', JSON.parse(context
.values
[0]));
context
.write({
key
: context
.key
,
value
: result
});
log
.debug({
title
: "reduceContext result",
details
: context
});
log
.debug({
title
: "reduceContext context",
details
: 'key: ' + context
.key
+ ', value: ' + context
.value
});
}
function summarize(summary
) {
handleErrorIfAny(summary
);
createSummaryRecord(summary
);
log
.debug({
title
: "summary",
details
: summary
});
log
.audit("Remaining governance units end: " + runtime
.getCurrentScript().getRemainingUsage());
}
return {
getInputData
: getInputData
,
map
: map
,
reduce
: reduce
,
summarize
: summarize
};
});
遇到的问题
Map/Reduce不能被RESTlet成功调用进入map/reduce/summary阶段
参考: Answer Id: 80924 - Map/Reduce Script called by RESTlet Fails When calling a Map/Reduce Script from a RESTlet using task.create(), the Map/Reduce Script fails when using a non-admin role. To resolve this, the role used to execute the RESTlet should have the Documents and Files Permission. Here’s how to set that up: 解决:Navigate to (Administrator) Setup > Users/Roles > Manage Roles > Edit the Role used to execute the RESTlet Under the Permissions tab > Lists subtab > Add the Permission: Documents and Files and set the Level: Full Click Save