6 Commits

Author SHA1 Message Date
8d528f0f55 SO_SNDTIMEO and SO_RCVTIMEO socket options have turned out not to be reliable.
Use select() for detecting timeout on socket (read and write).
2023-08-14 14:35:54 +02:00
66d0214720 Always look for {"error": "ISMAS is offline"} first.
Allow for empty update-trgger (try again)
rsync: mkdir -p the necessary directories.
2023-08-14 14:33:12 +02:00
86064979b4 Add memeber-variable for exitCode of executed process. 2023-08-14 14:28:23 +02:00
86c996d7ac Set final version to 1.1.1w 2023-08-11 12:24:51 +02:00
effe41bac9 Use finalResult() to sent a final message to ISMAS about the result of the update process. 2023-08-11 12:20:09 +02:00
4968942cc2 Added finalResult() -> send final message to ISMAS in any case. 2023-08-11 12:19:10 +02:00
6 changed files with 211 additions and 77 deletions

View File

@@ -15,7 +15,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
VERSION=1.0.0
VERSION=1.1.1
INCLUDEPATH += plugins

View File

@@ -137,7 +137,12 @@ IsmasClient::sendRequestReceiveResponse(int port, QString const &request) {
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
int maxfdp1;
fd_set rset;
fd_set wset;
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger));
// no reliable, but does not harm, as we use select() as well
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
int flag = 1;
@@ -151,24 +156,56 @@ IsmasClient::sendRequestReceiveResponse(int port, QString const &request) {
int loop = 0;
int bytesWritten = 0;
while (bytesWritten < bytesToWrite) {
int n = ::sendto(sockfd, buf+bytesWritten, bytesToWrite-bytesWritten, 0, NULL, 0);
if (n >= 0) {
bytesWritten += n;
} else {
if (errno == EWOULDBLOCK) {
if (++loop < 10) {
QThread::msleep(500);
continue;
}
printErrorMessage(port, clientIP, clientPort,
QString("WRITE TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
} else
errno = 0;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
maxfdp1 = sockfd + 1;
tv.tv_sec = 60; /* 60 secs timeout for read and write -> APISM cuts the connection after 30s */
tv.tv_usec = 0;
int const w = select(maxfdp1, NULL, &wset, NULL, &tv);
if (w < 0) { // error
if (errno == EINTR) {
printErrorMessage(port, clientIP, clientPort,
QString("WRITE INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")");
QString("INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")");
continue;
} else {
printErrorMessage(port, clientIP, clientPort,
QString("SELECT-ERROR (WRITE) %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
}
} else
if (w == 0) { // timeout
printErrorMessage(port, clientIP, clientPort,
QString("SELECT-TIMEOUT (WRITE) %1(").arg(loop) + strerror(errno) + ")");
if (++loop < 10) {
QThread::msleep(500);
continue;
}
::close(sockfd);
return std::nullopt;
} else
if (w > 0) {
int n = ::sendto(sockfd, buf+bytesWritten, bytesToWrite-bytesWritten, 0, NULL, 0);
if (n >= 0) {
bytesWritten += n;
} else {
if (errno == EWOULDBLOCK) {
if (++loop < 10) {
QThread::msleep(500);
continue;
}
printErrorMessage(port, clientIP, clientPort,
QString("WRITE TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
} else
if (errno == EINTR) {
printErrorMessage(port, clientIP, clientPort,
QString("WRITE INTERRUPTED BY SIGNAL (1) (") + strerror(errno) + ")");
continue;
}
}
}
}
@@ -188,33 +225,66 @@ IsmasClient::sendRequestReceiveResponse(int port, QString const &request) {
int bytesRead = 0;
while (bytesRead < bytesToRead) {
errno = 0;
int n = ::recvfrom(sockfd, buf+bytesRead, bytesToRead-bytesRead,
0, NULL, NULL);
if (n > 0) { //
bytesRead += n;
} else
if (n == 0) {
// The return value will be 0 when the peer has performed an orderly shutdown.
printErrorMessage(port, clientIP, clientPort,
QString("PEER CLOSED CONNECTION (") + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
} else
if (n < 0) {
if (errno == EWOULDBLOCK) {
if (++loop < 10) {
QThread::msleep(500);
continue;
}
printErrorMessage(port, clientIP, clientPort,
QString("READ TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
}
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
maxfdp1 = sockfd + 1;
tv.tv_sec = 60; /* 60 secs timeout for read and write */
tv.tv_usec = 0;
int const r = select(maxfdp1, &rset, NULL, NULL, &tv);
if (r < 0) { // error
if (errno == EINTR) {
printErrorMessage(port, clientIP, clientPort,
QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")");
continue;
} else {
printErrorMessage(port, clientIP, clientPort,
QString("SELECT-ERROR (READ) %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
}
} else
if (r == 0) { // timeout
printErrorMessage(port, clientIP, clientPort,
QString("SELECT-TIMEOUT (READ) %1(").arg(loop) + strerror(errno) + ")");
if (++loop < 10) {
QThread::msleep(500);
continue;
}
::close(sockfd);
return std::nullopt;
} else
if (r > 0) {
if (FD_ISSET(sockfd, &rset)) {
int n = ::recvfrom(sockfd, buf+bytesRead, bytesToRead-bytesRead,
0, NULL, NULL);
if (n > 0) { //
bytesRead += n;
} else
if (n == 0) {
// The return value will be 0 when the peer has performed an orderly shutdown.
printErrorMessage(port, clientIP, clientPort,
QString("PEER CLOSED CONNECTION (") + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
} else
if (n < 0) {
if (errno == EWOULDBLOCK) { // check just in case
if (++loop < 10) {
QThread::msleep(500);
continue;
}
printErrorMessage(port, clientIP, clientPort,
QString("READ TIMEOUT %1(").arg(loop) + strerror(errno) + ")");
::close(sockfd);
return std::nullopt;
}
if (errno == EINTR) {
printErrorMessage(port, clientIP, clientPort,
QString("INTERRUPTED BY SIGNAL (2) (") + strerror(errno) + ")");
continue;
}
}
}
}
@@ -762,6 +832,27 @@ QString IsmasClient::jsonParseFailed(int resultCode, QString reason, QString con
version.toStdString().c_str());
}
std::optional<QString> IsmasClient::finalResult(int resultCode, QString reason, QString const &version) {
m_progressInPercent = 0;
if (resultCode == RESULT_CODE::SUCCESS) {
return updateNewsToIsmas("U0002",
m_progressInPercent,
RESULT_CODE::SUCCESS,
"FINAL-UPDATE-RESULT",
"(re-)set WAIT state",
version.toStdString().c_str());
}
if (resultCode == RESULT_CODE::INSTALL_ERROR) {
return updateNewsToIsmas("U0003",
m_progressInPercent,
resultCode,
"FINAL-UPDATE-RESULT",
reason.toStdString().c_str(),
version.toStdString().c_str());
}
return std::nullopt;
}
QString IsmasClient::updateOfPSAFailed(int resultCode, QString step,
QString reason, QString const &version) {
return updateNewsToIsmas("U0003",

View File

@@ -4,6 +4,8 @@
#include <QObject>
#include <QString>
#include <optional>
struct PSAInstalled {
struct Tariff {
QString name;
@@ -159,6 +161,7 @@ public:
QString updateOfPSAFailed(int resultCode, QString step, QString reason, QString const &version = QString());
QString sanityCheckFailed(int resultCode, QString reason, QString const &version = QString());
QString jsonParseFailed(int resultCode, QString reason, QString const &version = QString());
std::optional<QString> finalResult(int resultCode, QString reason, QString const &version = QString());
QString updateOfPSASendVersion(PSAInstalled const &psa);

View File

@@ -8,7 +8,8 @@ Command::Command(QString const &command, int start_timeout, int finish_timeout)
: m_command(command.trimmed())
, m_commandResult("")
, m_waitForStartTimeout(start_timeout)
, m_waitForFinishTimeout(finish_timeout) {
, m_waitForFinishTimeout(finish_timeout)
, m_exitCode(-1) {
}
QString Command::getCommandResult() const {
@@ -62,7 +63,7 @@ bool Command::execute(QString workingDirectory, QStringList args) {
if (p->waitForFinished(m_waitForFinishTimeout)) {
//qDebug() << "PROCESS" << m_command << "FINISHED";
if (p->exitStatus() == QProcess::NormalExit) {
if (p->exitCode() == 0) {
if ((m_exitCode = p->exitCode()) == 0) {
return true;
} else {
qCritical() << "EXECUTED" << m_command << "with code" << p->exitCode();

View File

@@ -16,15 +16,17 @@ class Command : public QObject {
QString m_commandResult;
int m_waitForStartTimeout;
int m_waitForFinishTimeout;
int m_exitCode;
public:
explicit Command(QString const &command,
int start_timeout = 100000,
int finish_timeout = 100000);
QString getCommandResult() const;
QString command() const { return m_command; }
bool execute(QString workingDirectory, QStringList args = QStringList());
int exitCode() const { return m_exitCode; }
private slots:
void readyReadStandardOutput();

View File

@@ -327,20 +327,38 @@ void Worker::privateUpdate() {
}
m_ismasClient.setProgressInPercent(100);
setProgress(100);
if (m_returnCode != 0) {
stopProgressLoop();
emit appendText(QString("UPDATE "), UPDATE_STEP_FAIL);
m_updateStatus = UpdateStatus(UPDATE_STATUS::UPDATE_PROCESS_FAILURE,
QString("Update process failed"));
if (std::optional<QString> s = m_ismasClient.finalResult(IsmasClient::RESULT_CODE::INSTALL_ERROR,
"FINAL-UPDATE-RESULT",
m_updateStatus.m_statusDescription)) {
IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
QString("#M=APISM#C=CMD_EVENT#J=") + s.value());
}
} else {
emit appendText(QString("UPDATE "), UPDATE_STEP_SUCCESS);
m_updateStatus = UpdateStatus(UPDATE_STATUS::UPDATE_PROCESS_SUCCESS,
QString("Update process succeeded"));
if (std::optional<QString> s = m_ismasClient.finalResult(IsmasClient::RESULT_CODE::SUCCESS,
"FINAL-UPDATE-RESULT",
m_updateStatus.m_statusDescription)) {
IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
QString("#M=APISM#C=CMD_EVENT#J=") + s.value());
}
}
if (!sentIsmasLastVersionNotification) {
// try even if the backend is not connected
sendIsmasLastVersionNotification();
}
if (m_returnCode != 0) {
stopProgressLoop();
emit appendText(QString("UPDATE "), UPDATE_STEP_FAIL);
} else {
emit appendText(QString("UPDATE "), UPDATE_STEP_SUCCESS);
}
setProgress(100);
m_updateProcessRunning = false;
emit enableExit();
emit restartExitTimer();
@@ -479,12 +497,14 @@ bool Worker::backendConnected() {
return false;
}
#define CHECK_UPDATE_TRIGGER_SET "Check update trigger ..."
bool Worker::updateTriggerSet() {
if (m_withoutIsmasDirectPort) { // useful for testing
return true;
}
emit appendText("\nUpdate trigger set ...");
emit appendText("\n" CHECK_UPDATE_TRIGGER_SET);
QString triggerValue("");
for (int repeat = 0; repeat < 50; ++repeat) {
@@ -500,7 +520,7 @@ bool Worker::updateTriggerSet() {
QString msg = result.value();
qInfo() << "REPEAT" << repeat << "APISM RESPONSE" << msg;
qInfo() << "REPEAT" << repeat << "APISM RESPONSE (" << msg << ")";
QJsonParseError parseError;
QJsonDocument document(QJsonDocument::fromJson(msg.toUtf8(), &parseError));
@@ -519,7 +539,7 @@ bool Worker::updateTriggerSet() {
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger",
QString("invalid json ") + msg.mid(0, 20));
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
if (!document.isObject()) {
@@ -532,13 +552,25 @@ bool Worker::updateTriggerSet() {
m_ismasClient.jsonParseFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", QString("not a json object") + msg);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
progress += 1;
setProgress(progress);
QJsonObject obj = document.object();
// always look for an 'error' first
if (obj.contains("error")) {
progress += 1;
setProgress(progress);
QString value = obj.value("error").toString();
emit showErrorMessage("check update trigger", QString("REPEAT %1 error=<").arg(repeat) + value + ">");
qInfo() << "REPEAT" << repeat << "In updateTriggerSet() error=<"
<< value << ">";
QThread::sleep(6);
continue;
}
// sanity check: cust_nr and machine_nr of PSA correct ?
// note: this check has to be done here, as the cust_nr and the machine_nr
// of the PSA are sent by ISMAS.
@@ -561,7 +593,7 @@ bool Worker::updateTriggerSet() {
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
if (machineNr != m_machineNr) {
@@ -574,7 +606,7 @@ bool Worker::updateTriggerSet() {
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
@@ -589,7 +621,7 @@ bool Worker::updateTriggerSet() {
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
} else {
@@ -601,7 +633,7 @@ bool Worker::updateTriggerSet() {
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
} else {
@@ -613,7 +645,7 @@ bool Worker::updateTriggerSet() {
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
progress += 1;
@@ -646,19 +678,28 @@ bool Worker::updateTriggerSet() {
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.updateTriggerSet(m_updateStatus.m_statusDescription, ""));
emit replaceLast("Update trigger set ...", UPDATE_STEP_OK);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_DONE);
return true;
} else
if (QRegExp("\\s*").exactMatch(triggerValue)) { // check for whitespace
stopProgressLoop();
int progress = (m_mainWindow->progressValue()/10) + 10;
progress += 1;
setProgress(progress);
emit showErrorMessage("check update trigger", "empty update-trigger");
QThread::sleep(6);
continue;
} else {
// if the download-button once has the wrong value, it will never recover
setProgress(100);
m_updateStatus = UpdateStatus(UPDATE_STATUS::ISMAS_WAIT_STATE_CHECK_FAILURE,
QString("TRIGGER-VALUE ") + triggerValue + " NOT 'WAIT'");
QString("TRIGGER-VALUE=<") + triggerValue + "> NOT 'WAIT'");
IsmasClient::sendRequestReceiveResponse(IsmasClient::APISM::DB_PORT,
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
} else {
@@ -669,7 +710,7 @@ bool Worker::updateTriggerSet() {
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
return false;
}
@@ -681,7 +722,7 @@ bool Worker::updateTriggerSet() {
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
return false;
}
@@ -693,20 +734,10 @@ bool Worker::updateTriggerSet() {
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.sanityCheckFailed(IsmasClient::RESULT_CODE::INSTALL_ERROR,
m_updateStatus.m_statusDescription));
emit replaceLast("Update trigger set ...", UPDATE_STEP_FAIL);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
emit showErrorMessage("check update trigger", m_updateStatus.m_statusDescription);
return false;
}
if (obj.contains("error")) {
progress += 1;
setProgress(progress);
QString value = obj.value("error").toString();
emit showErrorMessage("check update trigger", QString("REPEAT %1 error=<").arg(repeat) + value + ">");
qInfo() << "REPEAT" << repeat << "In updateTriggerSet() error=<"
<< value << ">";
QThread::sleep(6);
}
} else {
stopProgressLoop();
int progress = (m_mainWindow->progressValue()/10) + 10;
@@ -725,7 +756,7 @@ bool Worker::updateTriggerSet() {
QString("#M=APISM#C=CMD_EVENT#J=") +
m_ismasClient.errorUpdateTrigger(m_updateStatus.m_statusDescription, ""));
emit replaceLast("Update trigger set ...", UPDATE_STEP_OK);
emit replaceLast(CHECK_UPDATE_TRIGGER_SET, UPDATE_STEP_FAIL);
return false;
}
@@ -901,10 +932,16 @@ bool Worker::updateFiles(quint8 percent) {
}
bool Worker::syncCustomerRepositoryAndFS() {
// this step is currently needed only for updating tariff-files
setProgress(0);
emit appendText("\nSync customer environment with filesystem ...");
if (QDir(m_customerRepository).exists()) {
if (QDir::setCurrent(m_customerRepository)) {
Command md("bash");
if (!md.execute(m_customerRepository,
QStringList() << "-c" << "mkdir -p /etc/psa_config /etc/psa_update /etc/dc /etc/psa_tariff")) {
qCritical() << "COULD NOT EXECUTE '" << md.command() << "' exitCode=(" << md.exitCode() << ")";
}
int progress = 10;
setProgress(progress);
QString const params("-vv "
@@ -935,7 +972,7 @@ bool Worker::syncCustomerRepositoryAndFS() {
}
} else {
Utils::printCriticalErrorMsg(QString("CMD ") + cmd + " FAILED: "
+ c.getCommandResult());
+ c.getCommandResult() + QString(" EXIT_CODE=(%1)").arg(c.exitCode()));
error = true;
}
}