1251 lines
32 KiB
C++
1251 lines
32 KiB
C++
// NeLNS - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif // HAVE_CONFIG_H
|
|
|
|
#ifndef NELNS_CONFIG
|
|
#define NELNS_CONFIG ""
|
|
#endif // NELNS_CONFIG
|
|
|
|
#ifndef NELNS_LOGS
|
|
#define NELNS_LOGS ""
|
|
#endif // NELNS_LOGS
|
|
|
|
#include "nel/misc/types_nl.h"
|
|
|
|
#include <string>
|
|
#include <list>
|
|
|
|
#ifdef NL_OS_WINDOWS
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
typedef unsigned long ulong;
|
|
#endif
|
|
|
|
#include <mysql.h>
|
|
#include <mysql_version.h>
|
|
|
|
#include "nel/misc/debug.h"
|
|
#include "nel/misc/config_file.h"
|
|
#include "nel/misc/path.h"
|
|
#include "nel/misc/command.h"
|
|
|
|
#include "nel/net/service.h"
|
|
#include "nel/net/varpath.h"
|
|
#include "nel/net/email.h"
|
|
|
|
#include "connection_web.h"
|
|
|
|
|
|
//
|
|
// Namespaces
|
|
//
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
using namespace NLNET;
|
|
|
|
|
|
//
|
|
// NeL Variables (for config file, etc)
|
|
//
|
|
|
|
// this variable should be used in conjunction with UseExplicitAESRegistration.
|
|
// the AS / AES registration process works as follows:
|
|
// - aes creates a layer 5 connection to as
|
|
// - as gets a serviceUp callback and looks in the database to try to find a match for the AES
|
|
// - if the match fails then AS sends a reject message to the AES
|
|
// - when the AES receives the reject message they check their UseExplicitAESRegistration flag - if it's set they
|
|
// attempt an explicit connection, sending the info required by the AS that would normally come from the database
|
|
// - when the AS receives an explicit registration, it verifies the state of the AllowExplicitAESRegistration flag
|
|
// and completes the registration work that failed earlier due to the database access failure
|
|
CVariable<bool> AllowExplicitAESRegistration("as","AllowExplicitAESRegistration","flag to allow AES services to register explicitly",false,0,true);
|
|
|
|
// this variable allows one to launch an AS on a machine that doesn't have a database setup
|
|
// the functionality of the AS is reduced (particularly in respect to alarms and graphs which are configured via the database)
|
|
CVariable<bool> DontUseDataBase("as","DontUseDataBase","if this flag is set calls to the database will be ignored",false,0,true);
|
|
|
|
|
|
//
|
|
// Structures
|
|
//
|
|
|
|
struct CRequest
|
|
{
|
|
CRequest (uint32 id, TSockId from) : Id(id), NbWaiting(0), NbReceived(0), From(from), NbRow(0), NbLines(1)
|
|
{
|
|
Time = CTime::getSecondsSince1970 ();
|
|
}
|
|
|
|
uint32 Id;
|
|
uint NbWaiting;
|
|
uint32 NbReceived;
|
|
TSockId From;
|
|
uint32 Time; // when the request was ask
|
|
|
|
uint32 NbRow;
|
|
uint32 NbLines;
|
|
|
|
vector<vector<string> > Array; // it's the 2 dimensional array that will be send to the php for variables
|
|
vector<string> Log; // this log contains the answer if a command was asked, othewise, Array contains the results
|
|
|
|
uint32 getVariable(const string &variable)
|
|
{
|
|
for (uint32 i = 0; i < NbRow; i++)
|
|
if (Array[i][0] == variable)
|
|
return i;
|
|
|
|
// need to add the variable
|
|
vector<string> NewRow;
|
|
NewRow.resize (NbLines);
|
|
NewRow[0] = variable;
|
|
Array.push_back (NewRow);
|
|
return NbRow++;
|
|
}
|
|
|
|
void addLine ()
|
|
{
|
|
for (uint32 i = 0; i < NbRow; i++)
|
|
Array[i].push_back("");
|
|
|
|
NbLines++;
|
|
}
|
|
|
|
void display ()
|
|
{
|
|
if (Log.empty())
|
|
{
|
|
nlinfo ("Display answer array for request %d: %d row %d lines", Id, NbRow, NbLines);
|
|
for (uint i = 0; i < NbLines; i++)
|
|
{
|
|
for (uint j = 0; j < NbRow; j++)
|
|
{
|
|
nlassert (Array.size () == NbRow);
|
|
InfoLog->displayRaw ("%-20s", Array[j][i].c_str());
|
|
}
|
|
InfoLog->displayRawNL ("");
|
|
}
|
|
InfoLog->displayRawNL ("End of the array");
|
|
}
|
|
else
|
|
{
|
|
nlinfo ("Display the log for request %d: %d lines", Id, Log.size());
|
|
for (uint i = 0; i < Log.size(); i++)
|
|
{
|
|
InfoLog->displayRaw ("%s", Log[i].c_str());
|
|
}
|
|
InfoLog->displayRawNL ("End of the log");
|
|
}
|
|
}
|
|
};
|
|
|
|
struct CAdminExecutorService
|
|
{
|
|
CAdminExecutorService (const string &shard, const string &name, TServiceId sid) : Shard(shard), SId(sid), Name(name) { }
|
|
|
|
string Shard; /// Name of the shard
|
|
TServiceId SId; /// uniq number to identify the AES
|
|
string Name; /// name of the admin executor service
|
|
|
|
vector<uint32> WaitingRequestId; /// contains all request that the server hasn't reply yet
|
|
|
|
};
|
|
|
|
typedef list<CAdminExecutorService> TAdminExecutorServices;
|
|
typedef list<CAdminExecutorService>::iterator AESIT;
|
|
|
|
|
|
//
|
|
// Variables
|
|
//
|
|
|
|
TAdminExecutorServices AdminExecutorServices;
|
|
|
|
MYSQL *DatabaseConnection = NULL;
|
|
|
|
vector<CRequest> Requests;
|
|
|
|
uint32 RequestTimeout = 5; // in second
|
|
|
|
// cumulate 5 seconds of alert
|
|
sint32 AdminAlertAccumlationTime = 5;
|
|
|
|
|
|
//
|
|
// Functions
|
|
//
|
|
|
|
AESIT findAES (TServiceId sid, bool asrt = true)
|
|
{
|
|
AESIT aesit;
|
|
for (aesit = AdminExecutorServices.begin(); aesit != AdminExecutorServices.end(); aesit++)
|
|
if ((*aesit).SId == sid)
|
|
break;
|
|
|
|
if (asrt)
|
|
nlassert (aesit != AdminExecutorServices.end());
|
|
return aesit;
|
|
}
|
|
|
|
AESIT findAES (const string &name, bool asrt = true)
|
|
{
|
|
AESIT aesit;
|
|
for (aesit = AdminExecutorServices.begin(); aesit != AdminExecutorServices.end(); aesit++)
|
|
if ((*aesit).Name == name)
|
|
break;
|
|
|
|
if (asrt)
|
|
nlassert (aesit != AdminExecutorServices.end());
|
|
|
|
return aesit;
|
|
}
|
|
|
|
|
|
//
|
|
// SQL helpers
|
|
//
|
|
|
|
MYSQL_RES *sqlCurrentQueryResult = NULL;
|
|
|
|
MYSQL_ROW sqlQuery (const char *format, ...)
|
|
{
|
|
if (DontUseDataBase)
|
|
return 0;
|
|
|
|
char *query;
|
|
NLMISC_CONVERT_VARGS (query, format, 1024);
|
|
|
|
if (DatabaseConnection == 0)
|
|
{
|
|
nlwarning ("MYSQL: mysql_query (%s) failed: DatabaseConnection is 0", query);
|
|
return NULL;
|
|
}
|
|
|
|
int ret = mysql_query (DatabaseConnection, query);
|
|
if (ret != 0)
|
|
{
|
|
nlwarning ("MYSQL: mysql_query () failed for query '%s': %s", query, mysql_error(DatabaseConnection));
|
|
return 0;
|
|
}
|
|
|
|
sqlCurrentQueryResult = mysql_store_result(DatabaseConnection);
|
|
if (sqlCurrentQueryResult == 0)
|
|
{
|
|
nlwarning ("MYSQL: mysql_store_result () failed for query '%s': %s", query, mysql_error(DatabaseConnection));
|
|
return 0;
|
|
}
|
|
|
|
MYSQL_ROW row = mysql_fetch_row(sqlCurrentQueryResult);
|
|
if (row == 0)
|
|
{
|
|
nlwarning ("MYSQL: mysql_fetch_row () failed for query '%s': %s", query, mysql_error(DatabaseConnection));
|
|
}
|
|
|
|
nldebug ("MYSQL: sqlQuery(%s) returns %d rows", query, mysql_num_rows(sqlCurrentQueryResult));
|
|
|
|
return row;
|
|
}
|
|
|
|
MYSQL_ROW sqlNextRow ()
|
|
{
|
|
if (DontUseDataBase)
|
|
return 0;
|
|
|
|
if (sqlCurrentQueryResult == 0)
|
|
return 0;
|
|
|
|
return mysql_fetch_row(sqlCurrentQueryResult);
|
|
}
|
|
|
|
void sqlFlushResult()
|
|
{
|
|
if (DontUseDataBase)
|
|
return;
|
|
|
|
if (sqlCurrentQueryResult == NULL)
|
|
return;
|
|
|
|
mysql_free_result(sqlCurrentQueryResult);
|
|
sqlCurrentQueryResult = NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// Admin functions
|
|
//
|
|
|
|
string Email;
|
|
uint32 FirstEmailTime = 0;
|
|
|
|
void sendAdminAlert (const char *format, ...)
|
|
{
|
|
char *text;
|
|
NLMISC_CONVERT_VARGS (text, format, 4096);
|
|
|
|
if (AdminAlertAccumlationTime == -1)
|
|
{
|
|
// we don't send email so just display a warning
|
|
nlwarning ("ALERT: %s", text);
|
|
}
|
|
else
|
|
{
|
|
if(Email.empty() && FirstEmailTime == 0)
|
|
{
|
|
Email += text;
|
|
FirstEmailTime = CTime::getSecondsSince1970();
|
|
}
|
|
else
|
|
{
|
|
Email += "\n";
|
|
Email += text;
|
|
}
|
|
nldebug ("ALERT: pushing email into queue: %s", text);
|
|
}
|
|
}
|
|
|
|
void updateSendAdminAlert ()
|
|
{
|
|
if(!Email.empty() && FirstEmailTime != 0 && AdminAlertAccumlationTime >=0 && CTime::getSecondsSince1970() > FirstEmailTime + AdminAlertAccumlationTime)
|
|
{
|
|
vector<string> lines;
|
|
explode (Email, string("\n"), lines, true);
|
|
|
|
if (!lines.empty())
|
|
{
|
|
|
|
if (IService::getInstance()->ConfigFile.exists("SysLogPath") && IService::getInstance()->ConfigFile.exists("SysLogParams"))
|
|
{
|
|
// syslog
|
|
string param;
|
|
if (lines.size() > 1)
|
|
{
|
|
param = "Multiple problems, first is: ";
|
|
}
|
|
param += lines[0];
|
|
string res = toString(IService::getInstance()->ConfigFile.getVar("SysLogParams").asString().c_str(), param.c_str());
|
|
launchProgram(IService::getInstance()->ConfigFile.getVar("SysLogPath").asString(), res);
|
|
}
|
|
|
|
if (IService::getInstance()->ConfigFile.exists("AdminEmail"))
|
|
{
|
|
// email
|
|
string subject;
|
|
if (lines.size() == 1)
|
|
{
|
|
subject = lines[0];
|
|
}
|
|
else
|
|
{
|
|
subject = "Multiple problems";
|
|
}
|
|
|
|
std::string from;
|
|
if(IService::getInstance()->ConfigFile.exists("AdminEmailFrom"))
|
|
from = IService::getInstance()->ConfigFile.getVar("AdminEmailFrom").asString();
|
|
CConfigFile::CVar &var = IService::getInstance()->ConfigFile.getVar("AdminEmail");
|
|
for (uint i = 0; i < var.size(); i++)
|
|
{
|
|
if (!sendEmail ("", from, var.asString(i), subject, Email))
|
|
{
|
|
nlwarning ("Can't send email to '%s'", var.asString(i).c_str());
|
|
}
|
|
else
|
|
{
|
|
nlinfo ("ALERT: Sent email to admin %s the subject: %s", var.asString(i).c_str(), subject.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Email = "";
|
|
FirstEmailTime = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static void cbAdminEmail (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
|
{
|
|
string str;
|
|
msgin.serial(str);
|
|
sendAdminAlert (str.c_str());
|
|
}
|
|
|
|
static void cbGraphUpdate (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
|
{
|
|
uint32 CurrentTime;
|
|
msgin.serial (CurrentTime);
|
|
|
|
while (msgin.getPos() < (sint32)msgin.length())
|
|
{
|
|
string var, service;
|
|
sint32 val;
|
|
msgin.serial (service, var, val);
|
|
|
|
AESIT aesit = findAES (sid);
|
|
|
|
string shard, server;
|
|
shard = (*aesit).Shard;
|
|
server = (*aesit).Name;
|
|
|
|
if (!shard.empty() && !server.empty() && !service.empty() && !var.empty())
|
|
{
|
|
string path = CPath::standardizePath (IService::getInstance()->ConfigFile.getVar("RRDVarPath").asString());
|
|
string rrdfilename = path + shard+"."+server+"."+service+"."+var+".rrd";
|
|
|
|
string arg;
|
|
|
|
if (!NLMISC::CFile::fileExists(rrdfilename))
|
|
{
|
|
MYSQL_ROW row = sqlQuery ("select graph_update from variable where path like '%%%s' and graph_update!=0", var.c_str());
|
|
if (row != NULL)
|
|
{
|
|
uint32 freq = atoi(row[0]);
|
|
arg = "create "+rrdfilename+" --step "+toString(freq)+" DS:var:GAUGE:"+toString(freq*2)+":U:U RRA:AVERAGE:0.5:1:1000 RRA:AVERAGE:0.5:10:1000 RRA:AVERAGE:0.5:100:1000";
|
|
launchProgram(IService::getInstance()->ConfigFile.getVar("RRDToolPath").asString(), arg);
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("Can't create the rrd because no graph_update in database");
|
|
}
|
|
sqlFlushResult();
|
|
}
|
|
|
|
arg = "update " + rrdfilename + " " + toString (CurrentTime) + ":" + toString(val);
|
|
launchProgram(IService::getInstance()->ConfigFile.getVar("RRDToolPath").asString(), arg);
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("Shard server service var val is empty");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Request functions
|
|
//
|
|
|
|
uint32 newRequest (TSockId from)
|
|
{
|
|
static uint32 NextId = 5461231;
|
|
|
|
Requests.push_back (CRequest(NextId, from));
|
|
|
|
return NextId++;
|
|
}
|
|
|
|
void addRequestWaitingNb (uint32 rid)
|
|
{
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
if (Requests[i].Id == rid)
|
|
{
|
|
Requests[i].NbWaiting++;
|
|
Requests[i].Time = CTime::getSecondsSince1970 ();
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("REQUEST: Received an answer from an unknown resquest %d (perhaps due to a AS timeout)", rid);
|
|
}
|
|
|
|
void subRequestWaitingNb (uint32 rid)
|
|
{
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
if (Requests[i].Id == rid)
|
|
{
|
|
Requests[i].NbWaiting--;
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("REQUEST: Received an answer from an unknown resquest %d (perhaps due to a AS timeout)", rid);
|
|
}
|
|
|
|
void addRequestReceived (uint32 rid)
|
|
{
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
if (Requests[i].Id == rid)
|
|
{
|
|
Requests[i].NbReceived++;
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("REQUEST: Received an answer from an unknown resquest %d (perhaps due to a AS timeout)", rid);
|
|
}
|
|
|
|
void addRequestAnswer (uint32 rid, const vector<string> &variables, const vector<string> &values)
|
|
{
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
Requests[i].addLine ();
|
|
if (Requests[i].Id == rid)
|
|
{
|
|
if (!variables.empty() && variables[0]=="__log")
|
|
{
|
|
nlassert (variables.size() == 1);
|
|
|
|
for (uint j = 0; j < values.size(); j++)
|
|
{
|
|
Requests[i].Log.push_back (values[j]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nlassert (variables.size() == values.size ());
|
|
for (uint j = 0; j < variables.size(); j++)
|
|
{
|
|
uint32 pos = Requests[i].getVariable (variables[j]);
|
|
Requests[i].Array[pos][Requests[i].NbLines-1] = values[j];
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
nlwarning ("REQUEST: Received an answer from an unknown resquest %d (perhaps due to a AS timeout)", rid);
|
|
}
|
|
|
|
bool emptyRequest (uint32 rid)
|
|
{
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
if (Requests[i].Id == rid && Requests[i].NbWaiting != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void cleanRequest ()
|
|
{
|
|
uint32 currentTime = CTime::getSecondsSince1970 ();
|
|
|
|
bool timeout;
|
|
|
|
for (uint i = 0 ; i < Requests.size ();)
|
|
{
|
|
// the AES doesn't answer quickly
|
|
timeout = (currentTime >= Requests[i].Time+RequestTimeout);
|
|
|
|
if (Requests[i].NbWaiting <= Requests[i].NbReceived || timeout)
|
|
{
|
|
// the request is over, send to the php
|
|
|
|
string str;
|
|
|
|
if (timeout)
|
|
{
|
|
nlwarning ("REQUEST: Request %d timeouted, only %d on %d services have replied", Requests[i].Id, Requests[i].NbReceived, Requests[i].NbWaiting);
|
|
}
|
|
|
|
if (Requests[i].Log.empty())
|
|
{
|
|
if (Requests[i].NbRow == 0 && timeout)
|
|
{
|
|
str = "1 ((TIMEOUT))";
|
|
}
|
|
else
|
|
{
|
|
str = toString(Requests[i].NbRow) + " ";
|
|
for (uint k = 0; k < Requests[i].NbLines; k++)
|
|
{
|
|
for (uint j = 0; j < Requests[i].NbRow; j++)
|
|
{
|
|
nlassert (Requests[i].Array.size () == Requests[i].NbRow);
|
|
if (Requests[i].Array[j][k].empty ())
|
|
str += "??? ";
|
|
else
|
|
{
|
|
str += Requests[i].Array[j][k];
|
|
if (timeout)
|
|
str += "((TIMEOUT))";
|
|
str += " ";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint k = 0; k < Requests[i].Log.size(); k++)
|
|
{
|
|
str += Requests[i].Log[k];
|
|
if (timeout)
|
|
str += "((TIMEOUT))";
|
|
}
|
|
}
|
|
|
|
sendString (Requests[i].From, str);
|
|
|
|
// set to 0 to erase it
|
|
Requests[i].NbWaiting = 0;
|
|
}
|
|
|
|
if (Requests[i].NbWaiting == 0)
|
|
{
|
|
Requests.erase (Requests.begin ()+i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// SQL functions
|
|
//
|
|
|
|
void sqlInit ()
|
|
{
|
|
if (DontUseDataBase)
|
|
return;
|
|
|
|
MYSQL *db = mysql_init(NULL);
|
|
if(db == NULL)
|
|
{
|
|
nlerror ("mysql_init() failed");
|
|
}
|
|
|
|
my_bool opt = true;
|
|
if (mysql_options (db, MYSQL_OPT_RECONNECT, &opt))
|
|
{
|
|
mysql_close(db);
|
|
DatabaseConnection = 0;
|
|
nlerror("mysql_options() failed for database connection to '%s'", IService::getInstance()->ConfigFile.getVar("DatabaseHost").asString().c_str());
|
|
return;
|
|
}
|
|
|
|
DatabaseConnection = mysql_real_connect(db,
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseHost").asString().c_str(),
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseLogin").asString().c_str(),
|
|
IService::getInstance()->ConfigFile.getVar("DatabasePassword").asString().c_str(),
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseName").asString().c_str(),
|
|
0,NULL,0);
|
|
if (DatabaseConnection == NULL || DatabaseConnection != db)
|
|
{
|
|
nlerror ("mysql_real_connect() failed to '%s' with login '%s' and database name '%s' with %s",
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseHost").asString().c_str(),
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseLogin").asString().c_str(),
|
|
IService::getInstance()->ConfigFile.getVar("DatabaseName").asString().c_str(),
|
|
(IService::getInstance()->ConfigFile.getVar("DatabasePassword").asString().empty()?"empty password":"password")
|
|
);
|
|
}
|
|
|
|
#if MYSQL_VERSION_ID < 50019
|
|
opt = true;
|
|
if (mysql_options (DatabaseConnection, MYSQL_OPT_RECONNECT, &opt))
|
|
{
|
|
mysql_close(db);
|
|
DatabaseConnection = 0;
|
|
nlerror("mysql_options() failed for database connection to '%s'", IService::getInstance()->ConfigFile.getVar("DatabaseHost").asString().c_str());
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////// CONNECTION TO THE AES ///////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void sendAESInformation (TServiceId sid)
|
|
{
|
|
AESIT aesit = findAES (sid);
|
|
|
|
vector<string> information;
|
|
|
|
CMessage msgout("AES_INFO");
|
|
|
|
//
|
|
// send services that should be running on this AES
|
|
//
|
|
information.clear ();
|
|
MYSQL_ROW row = sqlQuery ("select name from service where server='%s'", (*aesit).Name.c_str());
|
|
while (row != NULL)
|
|
{
|
|
string service = row[0];
|
|
nlinfo ("Adding '%s' in registered services to AES-%hu", row[0], sid.get());
|
|
information.push_back (service);
|
|
row = sqlNextRow ();
|
|
}
|
|
sqlFlushResult();
|
|
msgout.serialCont (information);
|
|
|
|
//
|
|
// send variable alarms for services that should running on this AES
|
|
//
|
|
information.clear ();
|
|
row = sqlQuery ("select path, error_bound, alarm_order from variable where error_bound!=-1");
|
|
while (row != NULL)
|
|
{
|
|
nlinfo ("Adding '%s' '%s' '%s' in alarm to AES-%hu", row[0], row[1], row[2], sid.get());
|
|
information.push_back (row[0]);
|
|
information.push_back (row[1]);
|
|
information.push_back (row[2]);
|
|
row = sqlNextRow ();
|
|
}
|
|
sqlFlushResult();
|
|
msgout.serialCont (information);
|
|
|
|
//
|
|
// send graph update for services that should running on this AES
|
|
//
|
|
information.clear ();
|
|
row = sqlQuery ("select path, graph_update from variable where graph_update!=0");
|
|
while (row != NULL)
|
|
{
|
|
CVarPath varpath (row[0]);
|
|
|
|
for(uint i = 0; i < varpath.Destination.size(); i++)
|
|
{
|
|
string a = varpath.Destination[i].first, b = (*aesit).Shard;
|
|
if(varpath.Destination[i].first == "*" || varpath.Destination[i].first == (*aesit).Shard)
|
|
{
|
|
CVarPath varpath2 (varpath.Destination[i].second);
|
|
|
|
for(uint j = 0; j < varpath2.Destination.size(); j++)
|
|
{
|
|
string c = varpath2.Destination[j].first, d = (*aesit).Name;
|
|
if(varpath2.Destination[j].first == "*" || varpath2.Destination[j].first == (*aesit).Name)
|
|
{
|
|
nlinfo ("Adding '%s' '%s' in graph to AES-%hu", row[0], row[1], sid.get());
|
|
information.push_back (row[0]);
|
|
information.push_back (row[1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
row = sqlNextRow ();
|
|
}
|
|
sqlFlushResult();
|
|
msgout.serialCont (information);
|
|
|
|
nlinfo ("Sending all information about %s AES-%hu (hostedservices, alarms,grapupdate)", (*aesit).Name.c_str(), (*aesit).SId.get());
|
|
CUnifiedNetwork::getInstance ()->send (sid, msgout);
|
|
}
|
|
|
|
void rejectAES(TServiceId sid, const string &res)
|
|
{
|
|
CMessage msgout("REJECTED");
|
|
msgout.serial ((string &)res);
|
|
CUnifiedNetwork::getInstance ()->send (sid, msgout);
|
|
}
|
|
|
|
// i'm connected to a new admin executor service
|
|
static void cbNewAESConnection (const std::string &serviceName, TServiceId sid, void *arg)
|
|
{
|
|
TSockId from;
|
|
CCallbackNetBase *cnb = CUnifiedNetwork::getInstance ()->getNetBase (sid, from);
|
|
const CInetAddress &ia = cnb->hostAddress (from);
|
|
|
|
AESIT aesit = findAES (sid, false);
|
|
|
|
if (aesit != AdminExecutorServices.end ())
|
|
{
|
|
nlwarning ("Connection of an AES that is already in the list (%s)", ia.asString ().c_str ());
|
|
rejectAES (sid, "This AES is already in the AS list");
|
|
return;
|
|
}
|
|
|
|
MYSQL_ROW row = sqlQuery ("select name from server where address='%s'", ia.ipAddress().c_str());
|
|
if (row == NULL)
|
|
{
|
|
if (!AllowExplicitAESRegistration)
|
|
{
|
|
nlwarning ("Connection of an AES that is not in database server list (%s)", ia.asString ().c_str ());
|
|
}
|
|
else
|
|
{
|
|
nlinfo ("Rejecting auto-connection of an AES (%s) - this should provke explicitly reconnect", ia.asString ().c_str ());
|
|
}
|
|
rejectAES (sid, "This AES is not registered in the database");
|
|
sqlFlushResult();
|
|
return;
|
|
}
|
|
string server = row[0];
|
|
sqlFlushResult();
|
|
|
|
row = sqlQuery ("select shard from service where server='%s'", server.c_str());
|
|
if (row == NULL)
|
|
{
|
|
nlwarning ("Connection of an AES that is not in database server list (%s)", ia.asString ().c_str ());
|
|
rejectAES (sid, "This AES is not registered in the database");
|
|
sqlFlushResult();
|
|
return;
|
|
}
|
|
string shard = row[0];
|
|
sqlFlushResult();
|
|
|
|
AdminExecutorServices.push_back (CAdminExecutorService(shard, server, sid));
|
|
|
|
nlinfo ("%s-%hu, server name %s, for shard %s connected and added in the list", serviceName.c_str(), sid.get(), server.c_str(), shard.c_str());
|
|
|
|
// send him services that should run on this server
|
|
sendAESInformation (sid);
|
|
}
|
|
|
|
// i'm disconnected from an admin executor service
|
|
static void cbNewAESDisconnection (const std::string &serviceName, TServiceId sid, void *arg)
|
|
{
|
|
TSockId from;
|
|
CCallbackNetBase *cnb = CUnifiedNetwork::getInstance ()->getNetBase (sid, from);
|
|
const CInetAddress &ia = cnb->hostAddress (from);
|
|
|
|
AESIT aesit = findAES (sid, false);
|
|
|
|
if (aesit == AdminExecutorServices.end ())
|
|
{
|
|
nlwarning ("Disconnection of %s-%hu that is not in my list (%s)", serviceName.c_str (), sid.get(), ia.asString ().c_str ());
|
|
return;
|
|
}
|
|
|
|
nlinfo ("%s-%hu, shard name %s, disconnected and removed from the list", serviceName.c_str(), sid.get(), (*aesit).Name.c_str ());
|
|
|
|
// we need to remove pending request
|
|
|
|
for(uint i = 0; i < (*aesit).WaitingRequestId.size (); i++)
|
|
{
|
|
subRequestWaitingNb ((*aesit).WaitingRequestId[i]);
|
|
}
|
|
|
|
AdminExecutorServices.erase (aesit);
|
|
}
|
|
|
|
// we receive an explicit registration message from an AES
|
|
void cbRegisterAES(CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
|
{
|
|
if (!AllowExplicitAESRegistration)
|
|
{
|
|
nlwarning("Ignoring attempted AES registration because AllowExplicitAESRegistration==false");
|
|
return;
|
|
}
|
|
|
|
string server;
|
|
string shard;
|
|
try
|
|
{
|
|
msgin.serial(server);
|
|
msgin.serial(shard);
|
|
}
|
|
catch(...)
|
|
{
|
|
nlwarning("Ignoring attempted AES registration due to execption during message decoding");
|
|
return;
|
|
}
|
|
|
|
AdminExecutorServices.push_back (CAdminExecutorService(shard, server, sid));
|
|
|
|
nlinfo ("%s-%hu, server name %s, for shard %s connected and added in the list", serviceName.c_str(), sid.get(), server.c_str(), shard.c_str());
|
|
|
|
// send him services that should run on this server
|
|
sendAESInformation (sid);
|
|
}
|
|
|
|
static void cbView (CMessage &msgin, const std::string &serviceName, TServiceId sid)
|
|
{
|
|
uint32 rid;
|
|
msgin.serial (rid);
|
|
|
|
AESIT aesit = findAES (sid);
|
|
|
|
for (uint i = 0; i < (*aesit).WaitingRequestId.size();)
|
|
{
|
|
if ((*aesit).WaitingRequestId[i] == rid)
|
|
{
|
|
(*aesit).WaitingRequestId.erase ((*aesit).WaitingRequestId.begin ()+i);
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
MYSQL_ROW row = sqlQuery ("select distinct shard from service where server='%s'", (*aesit).Name.c_str ());
|
|
|
|
// shard name is find using the "service" table, so, if there s no shard name in it, it returns ???
|
|
string shardName;
|
|
if (row != NULL) shardName = row[0];
|
|
else shardName = DontUseDataBase? aesit->Shard: "???";
|
|
|
|
vector<string> vara, vala;
|
|
|
|
while ((uint32)msgin.getPos() < msgin.length())
|
|
{
|
|
vara.clear ();
|
|
vala.clear ();
|
|
|
|
// adding default row
|
|
vara.push_back ("shard");
|
|
vara.push_back ("server");
|
|
|
|
vala.push_back (shardName);
|
|
vala.push_back ((*aesit).Name);
|
|
|
|
uint32 i, nb;
|
|
string var, val;
|
|
|
|
msgin.serial (nb);
|
|
for (i = 0; i < nb; i++)
|
|
{
|
|
msgin.serial (var);
|
|
if (var == "__log")
|
|
{
|
|
vara.clear ();
|
|
vala.clear ();
|
|
}
|
|
vara.push_back (var);
|
|
}
|
|
|
|
if (vara.size() > 0 && vara[0] == "__log")
|
|
vala.push_back ("----- Result from Shard "+shardName+" Server "+(*aesit).Name+"\n");
|
|
|
|
msgin.serial (nb);
|
|
for (i = 0; i < nb; i++)
|
|
{
|
|
msgin.serial (val);
|
|
vala.push_back (val);
|
|
}
|
|
addRequestAnswer (rid, vara, vala);
|
|
}
|
|
sqlFlushResult();
|
|
|
|
// inc the NbReceived counter
|
|
addRequestReceived (rid);
|
|
}
|
|
|
|
TUnifiedCallbackItem CallbackArray[] =
|
|
{
|
|
{ "REGISTER_AES", cbRegisterAES },
|
|
{ "VIEW", cbView },
|
|
{ "ADMIN_EMAIL", cbAdminEmail },
|
|
{ "GRAPH_UPDATE", cbGraphUpdate },
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////// CONNECTION TO THE CLIENT ////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void addRequest (const string &rawvarpath, TSockId from)
|
|
{
|
|
nlinfo ("addRequest from %s: '%s'", from->asString ().c_str (), rawvarpath.c_str ());
|
|
|
|
if(rawvarpath.empty ())
|
|
{
|
|
// send an empty string to say to php that there's nothing
|
|
string str;
|
|
sendString (from, str);
|
|
}
|
|
|
|
//
|
|
// special cases
|
|
//
|
|
|
|
if(rawvarpath == "reload")
|
|
{
|
|
// it means the we have to resend the list of services managed by AES from the mysql tables
|
|
for (AESIT aesit = AdminExecutorServices.begin(); aesit != AdminExecutorServices.end(); aesit++)
|
|
{
|
|
sendAESInformation ((*aesit).SId);
|
|
}
|
|
|
|
// send an empty string to say to php that there's nothing
|
|
string str;
|
|
sendString (from, str);
|
|
return;
|
|
}
|
|
|
|
//
|
|
// normal cases
|
|
//
|
|
|
|
CVarPath varpath (rawvarpath);
|
|
|
|
uint32 rid = newRequest (from);
|
|
|
|
for (uint i = 0; i < varpath.Destination.size (); i++)
|
|
{
|
|
string shard = varpath.Destination[i].first;
|
|
|
|
CVarPath subvarpath (varpath.Destination[i].second);
|
|
|
|
for (uint j = 0; j < subvarpath.Destination.size (); j++)
|
|
{
|
|
string server = subvarpath.Destination[j].first;
|
|
|
|
if (shard == "*" && server == "*")
|
|
{
|
|
// Send the request to all online servers of all online shards
|
|
|
|
AESIT aesit;
|
|
for (aesit = AdminExecutorServices.begin(); aesit != AdminExecutorServices.end(); aesit++)
|
|
{
|
|
addRequestWaitingNb (rid);
|
|
(*aesit).WaitingRequestId.push_back (rid);
|
|
|
|
CMessage msgout("AES_GET_VIEW");
|
|
msgout.serial (rid);
|
|
msgout.serial (subvarpath.Destination[j].second);
|
|
CUnifiedNetwork::getInstance ()->send ((*aesit).SId, msgout);
|
|
nlinfo ("REQUEST: Sent view '%s' to shard name %s 'AES-%hu'", subvarpath.Destination[j].second.c_str(), (*aesit).Name.c_str(), (*aesit).SId.get());
|
|
}
|
|
}
|
|
else if (shard == "*" && server == "#")
|
|
{
|
|
// Select all shard all server including offline one
|
|
|
|
MYSQL_ROW row = sqlQuery ("select distinct server, shard from service");
|
|
|
|
while (row != NULL)
|
|
{
|
|
AESIT aesit = findAES (row[0], false);
|
|
|
|
if (aesit != AdminExecutorServices.end())
|
|
{
|
|
addRequestWaitingNb (rid);
|
|
(*aesit).WaitingRequestId.push_back (rid);
|
|
|
|
CMessage msgout("AES_GET_VIEW");
|
|
msgout.serial (rid);
|
|
msgout.serial (subvarpath.Destination[j].second);
|
|
CUnifiedNetwork::getInstance ()->send ((*aesit).SId, msgout);
|
|
nlinfo ("REQUEST: Sent view '%s' to shard name %s 'AES-%hu'", subvarpath.Destination[j].second.c_str(), (*aesit).Name.c_str(), (*aesit).SId.get());
|
|
|
|
}
|
|
else if (server == "#")
|
|
{
|
|
vector<string> vara, vala;
|
|
|
|
// adding default row
|
|
vara.push_back ("shard");
|
|
vala.push_back (row[1]);
|
|
|
|
vara.push_back ("server");
|
|
vala.push_back (row[0]);
|
|
|
|
vara.push_back ("service");
|
|
vala.push_back ("AES");
|
|
|
|
vara.push_back ("State");
|
|
vala.push_back ("Offline");
|
|
|
|
addRequestAnswer (rid, vara, vala);
|
|
}
|
|
row = sqlNextRow ();
|
|
}
|
|
sqlFlushResult();
|
|
}
|
|
else if (server == "*" || server == "#")
|
|
{
|
|
// Send the request to all online server of a specific shard
|
|
|
|
MYSQL_ROW row = sqlQuery ("select distinct server from service where shard='%s'", shard.c_str ());
|
|
|
|
while (row != NULL)
|
|
{
|
|
AESIT aesit = findAES (row[0], false);
|
|
|
|
if (aesit != AdminExecutorServices.end())
|
|
{
|
|
addRequestWaitingNb (rid);
|
|
(*aesit).WaitingRequestId.push_back (rid);
|
|
|
|
CMessage msgout("AES_GET_VIEW");
|
|
msgout.serial (rid);
|
|
msgout.serial (subvarpath.Destination[j].second);
|
|
CUnifiedNetwork::getInstance ()->send ((*aesit).SId, msgout);
|
|
nlinfo ("REQUEST: Sent view '%s' to shard name %s 'AES-%hu'", subvarpath.Destination[j].second.c_str(), (*aesit).Name.c_str(), (*aesit).SId.get());
|
|
|
|
}
|
|
else if (server == "#")
|
|
{
|
|
vector<string> vara, vala;
|
|
|
|
// adding default row
|
|
vara.push_back ("shard");
|
|
vala.push_back (shard);
|
|
|
|
vara.push_back ("server");
|
|
vala.push_back (row[0]);
|
|
|
|
vara.push_back ("service");
|
|
vala.push_back ("AES");
|
|
|
|
vara.push_back ("State");
|
|
vala.push_back ("Offline");
|
|
|
|
addRequestAnswer (rid, vara, vala);
|
|
}
|
|
row = sqlNextRow ();
|
|
}
|
|
|
|
sqlFlushResult();
|
|
}
|
|
else
|
|
{
|
|
AESIT aesit = findAES (server, false);
|
|
|
|
if (aesit != AdminExecutorServices.end())
|
|
{
|
|
addRequestWaitingNb (rid);
|
|
(*aesit).WaitingRequestId.push_back (rid);
|
|
|
|
CMessage msgout("AES_GET_VIEW");
|
|
msgout.serial (rid);
|
|
msgout.serial (subvarpath.Destination[j].second);
|
|
CUnifiedNetwork::getInstance ()->send ((*aesit).SId, msgout);
|
|
nlinfo ("REQUEST: Sent view '%s' to shard name %s 'AES-%hu'", subvarpath.Destination[j].second.c_str(), (*aesit).Name.c_str(), (*aesit).SId.get());
|
|
}
|
|
else
|
|
{
|
|
nlwarning ("Server %s is not found in the list", server.c_str ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void varRequestTimeout(CConfigFile::CVar &var)
|
|
{
|
|
RequestTimeout = var.asInt();
|
|
nlinfo ("Request timeout is now after %d seconds", RequestTimeout);
|
|
}
|
|
|
|
static void varAdminAlertAccumlationTime (CConfigFile::CVar &var)
|
|
{
|
|
AdminAlertAccumlationTime = var.asInt();
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////// SERVICE IMPLEMENTATION //////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
class CAdminService : public IService
|
|
{
|
|
public:
|
|
|
|
/// Init the service, load the universal time.
|
|
void init ()
|
|
{
|
|
setDefaultEmailParams (ConfigFile.getVar ("SMTPServer").asString (), ConfigFile.getVar("DefaultEmailFrom").asString(), "");
|
|
|
|
sqlInit ();
|
|
|
|
connectionWebInit ();
|
|
|
|
//CVarPath toto ("[toto");
|
|
|
|
//CVarPath toto ("*.*.*.*");
|
|
//CVarPath toto ("[srv1,srv2].*.*.*");
|
|
//CVarPath toto ("[svr1.svc1,srv2.svc2].*.*");
|
|
//CVarPath toto ("[svr1.[svc1,svc2].*.var1,srv2.svc2.fe*.var2].toto");
|
|
//CVarPath toto ("[svr1.svc1.*.toto,srv2.svc2.*.tata]");
|
|
|
|
CUnifiedNetwork::getInstance ()->setServiceUpCallback ("AES", cbNewAESConnection);
|
|
CUnifiedNetwork::getInstance ()->setServiceDownCallback ("AES", cbNewAESDisconnection);
|
|
|
|
varRequestTimeout (ConfigFile.getVar ("RequestTimeout"));
|
|
ConfigFile.setCallback("RequestTimeout", &varRequestTimeout);
|
|
|
|
varAdminAlertAccumlationTime (ConfigFile.getVar ("AdminAlertAccumlationTime"));
|
|
ConfigFile.setCallback("AdmimAlertAccumlationTime", &varAdminAlertAccumlationTime);
|
|
|
|
}
|
|
|
|
bool update ()
|
|
{
|
|
cleanRequest ();
|
|
connectionWebUpdate ();
|
|
|
|
updateSendAdminAlert ();
|
|
return true;
|
|
}
|
|
|
|
void release ()
|
|
{
|
|
connectionWebRelease ();
|
|
}
|
|
};
|
|
|
|
|
|
/// Admin Service
|
|
NLNET_SERVICE_MAIN (CAdminService, "AS", "admin_service", 49996, CallbackArray, NELNS_CONFIG, NELNS_LOGS);
|
|
|
|
|
|
NLMISC_COMMAND (getViewAS, "send a view and receive an array as result", "<varpath>")
|
|
{
|
|
string cmd;
|
|
for (uint i = 0; i < args.size(); i++)
|
|
{
|
|
if (i != 0) cmd += " ";
|
|
cmd += args[i];
|
|
}
|
|
|
|
addRequest (cmd, NULL);
|
|
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND (clearRequests, "clear all pending requests", "")
|
|
{
|
|
if(args.size() != 0) return false;
|
|
|
|
// for all request, set the NbWaiting to NbReceived, next cleanRequest() will send answer and clear all request
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
if (Requests[i].NbWaiting <= Requests[i].NbReceived)
|
|
{
|
|
Requests[i].NbWaiting = Requests[i].NbReceived;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND (displayRequests, "display all pending requests", "")
|
|
{
|
|
if(args.size() != 0) return false;
|
|
|
|
log.displayNL ("Display %d pending requests", Requests.size ());
|
|
for (uint i = 0 ; i < Requests.size (); i++)
|
|
{
|
|
log.displayNL ("id: %d wait: %d recv: %d from: %s nbrow: %d", Requests[i].Id, Requests[i].NbWaiting, Requests[i].NbReceived, Requests[i].From->asString ().c_str (), Requests[i].NbRow);
|
|
}
|
|
log.displayNL ("End of display pending requests");
|
|
|
|
return true;
|
|
}
|
|
|
|
NLMISC_COMMAND (generateAlert, "generate an alert", "<text>")
|
|
{
|
|
if(args.size() != 1) return false;
|
|
|
|
sendAdminAlert (args[0].c_str());
|
|
|
|
return true;
|
|
}
|