mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Retry auto-install of modules that fail
- introduces autoInstallModulesRetry - default 30000 - backs off interval if repeated failures - fixes notification to the editor of an auto-reinstall
This commit is contained in:
parent
3a2f56cb95
commit
f9769a73fe
@ -37,7 +37,25 @@ RED.i18n = (function() {
|
|||||||
|
|
||||||
},
|
},
|
||||||
loadCatalog: function(namespace,done) {
|
loadCatalog: function(namespace,done) {
|
||||||
i18n.loadNamespace(namespace,done);
|
var languageList = i18n.functions.toLanguages(i18n.detectLanguage());
|
||||||
|
var toLoad = languageList.length;
|
||||||
|
languageList.forEach(function(lang) {
|
||||||
|
$.ajax({
|
||||||
|
headers: {
|
||||||
|
"Accept":"application/json"
|
||||||
|
},
|
||||||
|
cache: false,
|
||||||
|
url: 'locales/'+namespace+'?lng='+lang,
|
||||||
|
success: function(data) {
|
||||||
|
i18n.addResourceBundle(lang,namespace,data);
|
||||||
|
toLoad--;
|
||||||
|
if (toLoad === 0) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
loadNodeCatalogs: function(done) {
|
loadNodeCatalogs: function(done) {
|
||||||
|
@ -72,6 +72,10 @@
|
|||||||
// handled in ui/deploy.js
|
// handled in ui/deploy.js
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (notificationId === "node") {
|
||||||
|
// handled below
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (msg.text) {
|
if (msg.text) {
|
||||||
var text = RED._(msg.text,{default:msg.text});
|
var text = RED._(msg.text,{default:msg.text});
|
||||||
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
if (!persistentNotifications.hasOwnProperty(notificationId)) {
|
||||||
@ -98,11 +102,11 @@
|
|||||||
RED.view.redraw();
|
RED.view.redraw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
RED.comms.subscribe("node/#",function(topic,msg) {
|
RED.comms.subscribe("notification/node/#",function(topic,msg) {
|
||||||
var i,m;
|
var i,m;
|
||||||
var typeList;
|
var typeList;
|
||||||
var info;
|
var info;
|
||||||
if (topic == "node/added") {
|
if (topic == "notification/node/added") {
|
||||||
var addedTypes = [];
|
var addedTypes = [];
|
||||||
msg.forEach(function(m) {
|
msg.forEach(function(m) {
|
||||||
var id = m.id;
|
var id = m.id;
|
||||||
@ -118,7 +122,7 @@
|
|||||||
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
typeList = "<ul><li>"+addedTypes.join("</li><li>")+"</li></ul>";
|
||||||
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success");
|
||||||
}
|
}
|
||||||
} else if (topic == "node/removed") {
|
} else if (topic == "notification/node/removed") {
|
||||||
for (i=0;i<msg.length;i++) {
|
for (i=0;i<msg.length;i++) {
|
||||||
m = msg[i];
|
m = msg[i];
|
||||||
info = RED.nodes.removeNodeSet(m.id);
|
info = RED.nodes.removeNodeSet(m.id);
|
||||||
@ -127,7 +131,7 @@
|
|||||||
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
RED.notify(RED._("palette.event.nodeRemoved", {count:m.types.length})+typeList,"success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (topic == "node/enabled") {
|
} else if (topic == "notification/node/enabled") {
|
||||||
if (msg.types) {
|
if (msg.types) {
|
||||||
info = RED.nodes.getNodeSet(msg.id);
|
info = RED.nodes.getNodeSet(msg.id);
|
||||||
if (info.added) {
|
if (info.added) {
|
||||||
@ -142,7 +146,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (topic == "node/disabled") {
|
} else if (topic == "notification/node/disabled") {
|
||||||
if (msg.types) {
|
if (msg.types) {
|
||||||
RED.nodes.disableNodeSet(msg.id);
|
RED.nodes.disableNodeSet(msg.id);
|
||||||
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
typeList = "<ul><li>"+msg.types.join("</li><li>")+"</li></ul>";
|
||||||
|
@ -33,7 +33,7 @@ function handleStatus(event) {
|
|||||||
publish("status/"+event.id,event.status,true);
|
publish("status/"+event.id,event.status,true);
|
||||||
}
|
}
|
||||||
function handleRuntimeEvent(event) {
|
function handleRuntimeEvent(event) {
|
||||||
publish("notification/"+event.id,event,true);
|
publish("notification/"+event.id,event.payload||{},event.retain);
|
||||||
}
|
}
|
||||||
function init(_server,runtime) {
|
function init(_server,runtime) {
|
||||||
server = _server;
|
server = _server;
|
||||||
|
@ -15,12 +15,12 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var when = require("when");
|
var when = require("when");
|
||||||
var comms = require("./comms");
|
|
||||||
var locales = require("./locales");
|
var locales = require("./locales");
|
||||||
var redNodes;
|
var redNodes;
|
||||||
var log;
|
var log;
|
||||||
var i18n;
|
var i18n;
|
||||||
var settings;
|
var settings;
|
||||||
|
var events;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: function(runtime) {
|
init: function(runtime) {
|
||||||
@ -28,6 +28,7 @@ module.exports = {
|
|||||||
log = runtime.log;
|
log = runtime.log;
|
||||||
i18n = runtime.i18n;
|
i18n = runtime.i18n;
|
||||||
settings = runtime.settings;
|
settings = runtime.settings;
|
||||||
|
events = runtime.events;
|
||||||
},
|
},
|
||||||
getAll: function(req,res) {
|
getAll: function(req,res) {
|
||||||
if (req.get("accept") == "application/json") {
|
if (req.get("accept") == "application/json") {
|
||||||
@ -72,9 +73,9 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
promise.then(function(info) {
|
promise.then(function(info) {
|
||||||
if (isUpgrade) {
|
if (isUpgrade) {
|
||||||
comms.publish("node/upgraded",{module:node.module,version:node.version},false);
|
events.emit("runtime-event",{id:"node/upgraded",retain:false,payload:{module:node.module,version:node.version}});
|
||||||
} else {
|
} else {
|
||||||
comms.publish("node/added",info.nodes,false);
|
events.emit("runtime-event",{id:"node/added",retain:false,payload:info.nodes});
|
||||||
}
|
}
|
||||||
if (node.module) {
|
if (node.module) {
|
||||||
log.audit({event: "nodes.install",module:node.module,version:node.version},req);
|
log.audit({event: "nodes.install",module:node.module,version:node.version},req);
|
||||||
@ -113,7 +114,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promise.then(function(list) {
|
promise.then(function(list) {
|
||||||
comms.publish("node/removed",list,false);
|
events.emit("runtime-event",{id:"node/removed",retain:false,payload:list});
|
||||||
log.audit({event: "nodes.remove",module:mod},req);
|
log.audit({event: "nodes.remove",module:mod},req);
|
||||||
res.status(204).end();
|
res.status(204).end();
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
@ -245,7 +246,7 @@ function putNode(node, enabled) {
|
|||||||
|
|
||||||
return promise.then(function(info) {
|
return promise.then(function(info) {
|
||||||
if (info.enabled === enabled && !info.err) {
|
if (info.enabled === enabled && !info.err) {
|
||||||
comms.publish("node/"+(enabled?"enabled":"disabled"),info,false);
|
events.emit("runtime-event",{id:"node/"+(enabled?"enabled":"disabled"),retain:false,payload:info});
|
||||||
log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
|
log.info(" "+log._("api.nodes."+(enabled?"enabled":"disabled")));
|
||||||
for (var i=0;i<info.types.length;i++) {
|
for (var i=0;i<info.types.length;i++) {
|
||||||
log.info(" - "+info.types[i]);
|
log.info(" - "+info.types[i]);
|
||||||
|
@ -106,7 +106,7 @@ function start() {
|
|||||||
log.error("*****************************************************************");
|
log.error("*****************************************************************");
|
||||||
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=4"})+" *");
|
log.error("* "+log._("runtime.unsupported_version",{component:"Node.js",version:process.version,requires: ">=4"})+" *");
|
||||||
log.error("*****************************************************************");
|
log.error("*****************************************************************");
|
||||||
events.emit("runtime-event",{id:"runtime-unsupported-version",type:"error",text:"notification.errors.unsupportedVersion"});
|
events.emit("runtime-event",{id:"runtime-unsupported-version",payload:{type:"error",text:"notification.errors.unsupportedVersion"},retain:true});
|
||||||
}
|
}
|
||||||
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
log.info(os.type()+" "+os.release()+" "+os.arch()+" "+os.endianness());
|
||||||
return redNodes.load().then(function() {
|
return redNodes.load().then(function() {
|
||||||
@ -133,21 +133,23 @@ function start() {
|
|||||||
}
|
}
|
||||||
missingModules[missing.module].types = missingModules[missing.module].types.concat(missing.types);
|
missingModules[missing.module].types = missingModules[missing.module].types.concat(missing.types);
|
||||||
}
|
}
|
||||||
|
var moduleList = [];
|
||||||
var promises = [];
|
var promises = [];
|
||||||
|
var installingModules = [];
|
||||||
for (i in missingModules) {
|
for (i in missingModules) {
|
||||||
if (missingModules.hasOwnProperty(i)) {
|
if (missingModules.hasOwnProperty(i)) {
|
||||||
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
log.warn(" - "+i+" ("+missingModules[i].version+"): "+missingModules[i].types.join(", "));
|
||||||
if (settings.autoInstallModules && i != "node-red") {
|
if (settings.autoInstallModules && i != "node-red") {
|
||||||
redNodes.installModule(i,missingModules[i].version).otherwise(function(err) {
|
installingModules.push({id:i,version:missingModules[i].version});
|
||||||
// Error already reported. Need the otherwise handler
|
|
||||||
// to stop the error propagating any further
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!settings.autoInstallModules) {
|
if (!settings.autoInstallModules) {
|
||||||
log.info(log._("server.removing-modules"));
|
log.info(log._("server.removing-modules"));
|
||||||
redNodes.cleanModuleList();
|
redNodes.cleanModuleList();
|
||||||
|
} else if (installingModules.length > 0) {
|
||||||
|
reinstallAttempts = 0;
|
||||||
|
reinstallModules(installingModules);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (settings.settingsFile) {
|
if (settings.settingsFile) {
|
||||||
@ -161,6 +163,36 @@ function start() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reinstallAttempts;
|
||||||
|
var reinstallTimeout;
|
||||||
|
function reinstallModules(moduleList) {
|
||||||
|
var promises = [];
|
||||||
|
var failedModules = [];
|
||||||
|
for (var i=0;i<moduleList.length;i++) {
|
||||||
|
if (settings.autoInstallModules && i != "node-red") {
|
||||||
|
promises.push(redNodes.installModule(moduleList[i].id,moduleList[i].version));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when.settle(promises).then(function(results) {
|
||||||
|
var reinstallList = [];
|
||||||
|
for (var i=0;i<results.length;i++) {
|
||||||
|
if (results[i].state === 'rejected') {
|
||||||
|
reinstallList.push(moduleList[i]);
|
||||||
|
} else {
|
||||||
|
events.emit("runtime-event",{id:"node/added",retain:false,payload:results[i].value.nodes});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reinstallList.length > 0) {
|
||||||
|
reinstallAttempts++;
|
||||||
|
// First 5 at 1x timeout, next 5 at 2x, next 5 at 4x, then 8x
|
||||||
|
var timeout = (settings.autoInstallModulesRetry||30000) * Math.pow(2,Math.min(Math.floor(reinstallAttempts/5),3));
|
||||||
|
reinstallTimeout = setTimeout(function() {
|
||||||
|
reinstallModules(reinstallList);
|
||||||
|
},timeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function reportMetrics() {
|
function reportMetrics() {
|
||||||
var memUsage = process.memoryUsage();
|
var memUsage = process.memoryUsage();
|
||||||
|
|
||||||
@ -186,6 +218,9 @@ function stop() {
|
|||||||
clearInterval(runtimeMetricInterval);
|
clearInterval(runtimeMetricInterval);
|
||||||
runtimeMetricInterval = null;
|
runtimeMetricInterval = null;
|
||||||
}
|
}
|
||||||
|
if (reinstallTimeout) {
|
||||||
|
clearTimeout(reinstallTimeout);
|
||||||
|
}
|
||||||
started = false;
|
started = false;
|
||||||
return redNodes.stopFlows();
|
return redNodes.stopFlows();
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ function init(runtime) {
|
|||||||
log.info(log._("nodes.flows.registered-missing", {type:type}));
|
log.info(log._("nodes.flows.registered-missing", {type:type}));
|
||||||
activeFlowConfig.missingTypes.splice(i,1);
|
activeFlowConfig.missingTypes.splice(i,1);
|
||||||
if (activeFlowConfig.missingTypes.length === 0 && started) {
|
if (activeFlowConfig.missingTypes.length === 0 && started) {
|
||||||
events.emit("runtime-event",{id:"runtime-state"});
|
events.emit("runtime-event",{id:"runtime-state",retain: true});
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,13 +135,13 @@ function setFlows(_config,type,muteLog) {
|
|||||||
return stop(type,diff,muteLog).then(function() {
|
return stop(type,diff,muteLog).then(function() {
|
||||||
context.clean(activeFlowConfig);
|
context.clean(activeFlowConfig);
|
||||||
start(type,diff,muteLog).then(function() {
|
start(type,diff,muteLog).then(function() {
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",revision:flowRevision});
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
});
|
});
|
||||||
return flowRevision;
|
return flowRevision;
|
||||||
}).otherwise(function(err) {
|
}).otherwise(function(err) {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
events.emit("runtime-event",{id:"runtime-deploy",revision:flowRevision});
|
events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -251,7 +251,7 @@ function start(type,diff,muteLog) {
|
|||||||
log.info(log._("nodes.flows.missing-type-install-2"));
|
log.info(log._("nodes.flows.missing-type-install-2"));
|
||||||
log.info(" "+settings.userDir);
|
log.info(" "+settings.userDir);
|
||||||
}
|
}
|
||||||
events.emit("runtime-event",{id:"runtime-state",type:"warning",text:"notification.warnings.missing-types"});
|
events.emit("runtime-event",{id:"runtime-state",payload:{type:"warning",text:"notification.warnings.missing-types"},retain:true});
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
if (!muteLog) {
|
if (!muteLog) {
|
||||||
@ -310,7 +310,7 @@ function start(type,diff,muteLog) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.emit("nodes-started");
|
events.emit("nodes-started");
|
||||||
events.emit("runtime-event",{id:"runtime-state"});
|
events.emit("runtime-event",{id:"runtime-state",retain:true});
|
||||||
|
|
||||||
if (!muteLog) {
|
if (!muteLog) {
|
||||||
if (type !== "full") {
|
if (type !== "full") {
|
||||||
|
@ -30,8 +30,6 @@ var library = require("./library");
|
|||||||
|
|
||||||
var events = require("../events");
|
var events = require("../events");
|
||||||
|
|
||||||
var child_process = require('child_process');
|
|
||||||
|
|
||||||
var settings;
|
var settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +131,7 @@ function installModule(module,version) {
|
|||||||
resolve(require("./index").addModule(module).then(reportAddedModules));
|
resolve(require("./index").addModule(module).then(reportAddedModules));
|
||||||
} else {
|
} else {
|
||||||
log.info(log._("server.install.upgraded",{name:module, version:version}));
|
log.info(log._("server.install.upgraded",{name:module, version:version}));
|
||||||
events.emit("runtime-event",{id:"restart-required",type:"warning",text:"notification.warnings.restartRequired"});
|
events.emit("runtime-event",{id:"restart-required",payload:{type:"warning",text:"notification.warnings.restartRequired"},retain:true});
|
||||||
resolve(require("./registry").setModulePendingUpdated(module,version));
|
resolve(require("./registry").setModulePendingUpdated(module,version));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ function saveNodeList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hadPending && !hasPending) {
|
if (hadPending && !hasPending) {
|
||||||
events.emit("runtime-event",{id:"restart-required"});
|
events.emit("runtime-event",{id:"restart-required",retain: true});
|
||||||
}
|
}
|
||||||
if (settings.available()) {
|
if (settings.available()) {
|
||||||
return settings.set("nodes",moduleList);
|
return settings.set("nodes",moduleList);
|
||||||
|
@ -22,7 +22,6 @@ var sinon = require('sinon');
|
|||||||
var when = require('when');
|
var when = require('when');
|
||||||
|
|
||||||
var nodes = require("../../../red/api/nodes");
|
var nodes = require("../../../red/api/nodes");
|
||||||
var comms = require("../../../red/api/comms");
|
|
||||||
var locales = require("../../../red/api/locales");
|
var locales = require("../../../red/api/locales");
|
||||||
|
|
||||||
describe("nodes api", function() {
|
describe("nodes api", function() {
|
||||||
@ -35,6 +34,9 @@ describe("nodes api", function() {
|
|||||||
info: function(){},
|
info: function(){},
|
||||||
warn: function(){}
|
warn: function(){}
|
||||||
}
|
}
|
||||||
|
runtime.events = {
|
||||||
|
emit: function(){}
|
||||||
|
}
|
||||||
nodes.init(runtime);
|
nodes.init(runtime);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -49,15 +51,11 @@ describe("nodes api", function() {
|
|||||||
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
|
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)$/,nodes.putModule);
|
||||||
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
|
app.put(/\/nodes\/((@[^\/]+\/)?[^\/]+)\/([^\/]+)$/,nodes.putSet);
|
||||||
app.delete("/nodes/:id",nodes.delete);
|
app.delete("/nodes/:id",nodes.delete);
|
||||||
sinon.stub(comms,"publish");
|
|
||||||
sinon.stub(locales,"determineLangFromHeaders", function() {
|
sinon.stub(locales,"determineLangFromHeaders", function() {
|
||||||
return "en-US";
|
return "en-US";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function() {
|
|
||||||
comms.publish.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get nodes', function() {
|
describe('get nodes', function() {
|
||||||
it('returns node list', function(done) {
|
it('returns node list', function(done) {
|
||||||
|
@ -141,7 +141,7 @@ describe("runtime", function() {
|
|||||||
{ module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing
|
{ module:"node-red",enabled:true,loaded:false,types:["typeC","typeD"]} // missing
|
||||||
].filter(cb);
|
].filter(cb);
|
||||||
});
|
});
|
||||||
var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return when.resolve();});
|
var serverInstallModule = sinon.stub(redNodes,"installModule",function(name) { return when.resolve({nodes:[]});});
|
||||||
runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
runtime.init({testSettings: true, autoInstallModules:true, httpAdminRoot:"/", load:function() { return when.resolve();}});
|
||||||
sinon.stub(console,"log");
|
sinon.stub(console,"log");
|
||||||
runtime.start().then(function() {
|
runtime.start().then(function() {
|
||||||
|
Loading…
Reference in New Issue
Block a user