***********************
** FILE NAME : message.cpp
** SYSTEM NAME : Gnucomo - Gnu Computer Monitoring
-** VERSION NUMBER : $Revision: 1.15 $
+** VERSION NUMBER : $Revision: 1.19 $
**
** DESCRIPTION : Implementation of the message handling classes
**
********************************
** ORIGINAL AUTHOR : Arjen Baart - arjen@andromeda.nl
** CREATION DATE : Sep 16, 2002
-** LAST UPDATE : Jul 24, 2003
+** LAST UPDATE : Nov 02, 2007
** MODIFICATIONS :
**************************************************************************/
/*****************************
$Log: message.cpp,v $
- Revision 1.15 2003-10-27 11:28:27 arjen
+ Revision 1.19 2011-03-24 10:20:37 arjen
+ Added some debug info
+
+ Revision 1.18 2007/11/03 10:23:53 arjen
+ Handling of parameters is greatly improved.
+ When creating a new parameter from an XML report which is fed into
+ gcm_input, the class definition is used as a template to fill in
+ the default values for the properties.
+ The class is also used as a template when a new property is added
+ to an existing parameter.
+
+ DYNAMIC properties are now handled properly. Instead of making a
+ 'changed property' notification, the value is checked against the
+ defined range for the property. An 'out of range' notification
+ is created when this condition is detected.
+
+ Revision 1.17 2005/05/31 05:51:41 arjen
+ Textual changes in parameter notifications
+
+ Revision 1.16 2003/12/04 10:38:09 arjen
+ Major redesign. All input is handled through XML. Raw input data is first
+ transformed into an XML document for further processing.
+ A collection of polymorphic classes handle the transformation of various
+ input formats into XML.
+ Classifying input data is done with a finite improbability calculation.
+
+ Revision 1.15 2003/10/27 11:28:27 arjen
Do not add another parameter_notification record is the notification
already exists for that parameter.
*****************************/
-static const char *RCSID = "$Id: message.cpp,v 1.15 2003-10-27 11:28:27 arjen Exp $";
+static const char *RCSID = "$Id: message.cpp,v 1.19 2011-03-24 10:20:37 arjen Exp $";
#include <algorithm>
#include <libxml/xpath.h>
extern bool verbose; /* Defined in the main application */
extern bool testmode;
extern bool incremental;
-extern std::ostream *log;
+extern std::ostream *Log;
/* Utility functions */
return input_ok;
}
+/*=========================================================================
+** NAME : client_message
+** SYNOPSIS : client_message(std::istream *in, gnucomo_database db)
+** PARAMETERS :
+** RETURN VALUE : None
+**
+** DESCRIPTION : Client message constructor.
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 04, 2002
+**=========================================================================
+*/
+
client_message::client_message(std::istream *in, gnucomo_database db)
{
input.from(in);
certainty = 0.0;
}
-static const String mail_date_re("[[:alpha:]]{3}, [ 123]?[0-9] [[:alpha:]]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}");
static const String unix_date_re("[[:alpha:]]{3} [[:alpha:]]{3} [ 123][0-9] [0-9]{2}:[0-9]{2}:[0-9]{2} [0-9]{4}");
static const regex re_PGP("-----BEGIN PGP MESSAGE-----");
static const regex re_rpm("[[:alnum:]+-]+-[0-9][[:alnum:].-]");
static const regex re_uxmail_from("^From [^ \t]+[ ]+" + unix_date_re);
-static const regex re_mail_From("^From:[[:blank:]]+");
-static const regex re_mail_Date("^Date:[[:blank:]]+" + mail_date_re);
-static const regex re_mail_MsId("^Message-Id:[[:blank:]]+");
-static const regex re_email_address("[[:alnum:]_.-]+@[[:alnum:]_.-]+");
-static const regex re_email_user("[[:alnum:]_.-]+@");
static const regex re_xml_header("xml .*\?>$");
/*=========================================================================
-** NAME : readXMLinput
-** SYNOPSIS : int readXMLinput(String first_line)
+** NAME : extractHeader
+** SYNOPSIS : void extractHeader()
** PARAMETERS :
-** RETURN VALUE : Parse the XML input and extract the header information
+** RETURN VALUE : True if the mandatory header elements are available.
**
-** DESCRIPTION :
+** DESCRIPTION : Extract the header information from the XML DOM tree.
**
** VARS USED :
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Jul 24, 2003
+** LAST MODIFIED : Nov 26, 2003
**=========================================================================
*/
-int client_message::readXMLinput(String first_line)
+bool client_message::extractHeader()
{
- xmlParserCtxtPtr ctxt;
- String line;
xmlNodePtr root, item;
xmlNsPtr namespaces[1];
xmlXPathObjectPtr res;
xmlXPathContextPtr pathcontext;
-
- ctxt = xmlCreatePushParserCtxt(NULL, NULL, first_line, ~first_line, NULL);
- while (input >> line)
- {
- xmlParseChunk(ctxt, line, ~line, 0);
- }
- xmlParseChunk(ctxt, "", 0, 1);
- xmlDom = ctxt->myDoc;
- xmlFreeParserCtxt(ctxt);
+ bool header_OK = true;
root = xmlDocGetRootElement(xmlDom);
namespaces[0] = root->ns;
#endif
res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:messagetype/text()", pathcontext);
- if (res->nodesetval != NULL)
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
{
-#ifdef DEBUG
- xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
-#endif
item = *res->nodesetval->nodeTab;
// Select a line cooker based on the message type.
#ifdef DEBUG
- std::cout << "Looking for a line cooker for " << item->content << "\n";
+ *Log << "Looking for a line cooker for " << item->content << "\n";
#endif
- std::list<line_cooker *>::iterator lci = kitchen.begin();
- pan = 0;
- while (pan == 0 && lci != kitchen.end())
+ pan.lc = 0;
+ std::list<xform>::iterator lci = kitchen.begin();
+ while (pan.lc == 0 && lci != kitchen.end())
{
- pan = *lci;
- if (pan->message_type() != (const char *)(item->content))
+ if (lci->lc->message_type() == (const char *)(item->content))
{
- pan = 0;
+ pan.lc = lci->lc;
}
lci++;
}
- if (pan == 0)
+ if (pan.lc == 0)
{
- *log << "Can not find a line cooker for message type " << item->content << "\n";
+ *Log << "Can not find a line cooker for message type " << item->content << "\n";
+ header_OK = false;
}
}
else
{
- *log << "Message type not found in XML header.\n";
+ *Log << "Message type not found in XML header.\n";
+ header_OK = false;
}
res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:hostname/text()", pathcontext);
- if (res->nodesetval != NULL)
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
{
-#ifdef DEBUG
- xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
-#endif
item = *res->nodesetval->nodeTab;
hostname = (const char *)item->content;
}
else
{
- *log << "Hostname not found in XML header.\n";
+ *Log << "Can not determine the hostname where the message came from.\n";
+ header_OK = false;
}
res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:service/text()", pathcontext);
- if (res->nodesetval != NULL)
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
{
item = *res->nodesetval->nodeTab;
service = (const char *)item->content;
}
res = xmlXPathEval((const xmlChar *)"gcmt:header/gcmt:time/text()", pathcontext);
- if (res->nodesetval != NULL)
+ if (res->nodesetval != NULL && res->nodesetval->nodeTab != NULL)
{
item = *res->nodesetval->nodeTab;
arrival = String((char *)item->content);
+ if (!arrival.proper())
+ {
+ *Log << "Arrival time is not properly stated.\n";
+ header_OK = false;
+ }
}
//xmlDebugDumpNodeList(stdout, *res->nodesetval->nodeTab, 0);
+ return header_OK;
}
+
/*=========================================================================
** NAME : classify
** SYNOPSIS : double classify(String host, date arriv_d, hour arriv_t, String serv)
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Aug 11, 2003
+** LAST MODIFIED : Nov 27, 2003
**=========================================================================
*/
arrival = arriv;
service = serv;
+ const double epsilon = 0.1; // Threshold for uncertainty
+ const double P = 0.5; // Probability of a wrong match
+
+ double uncertainty;
+
+#ifdef DEBUG
+ *Log << "Checking for a mail header.\n";
+#endif
+
/* First, check if the message has a mail header. */
if (input >> line && line == re_uxmail_from)
mail_header = true;
- /* Scan ahead for the hostname and date of arrival. */
+ /* Skip the mail header until the first empty line. */
while (input >> line && line != "")
{
- if (line == re_mail_From)
- {
- from_address = line(re_email_address);
- from_address(re_email_user) = ""; // Remove the user part;
- if (from_address != "" && ~hostname < ~from_address)
- {
- *log << "Detected hostname " << from_address << "\n";
- hostname = from_address;
- }
- }
- if (line == re_mail_MsId)
- {
- from_address = line(re_email_address);
- from_address(re_email_user) = ""; // Remove the user part;
- if (from_address != "" && ~hostname < ~from_address)
- {
- *log << "Detected hostname " << from_address << "\n";
- hostname = from_address;
- }
- }
- if (line == re_mail_Date)
- {
- arrival = UTC(line(regex(mail_date_re)));
- }
}
}
else
}
- pan = 0;
-
/*
* Now that we have the mail header out of the way, try to figure
* out what the content of the message is.
*/
+#ifdef DEBUG
+ *Log << "Classifying message.\n";
+#endif
+
- while (input >> line && certainty < 0.9)
+ uncertainty = 1.0;
+
+ while (input >> line && uncertainty > epsilon)
{
if (verbose)
{
- *log << " testing: " << line << "\n";
+ *Log << " testing: " << line << "\n";
}
- if (line == re_xml_header)
- {
- certainty = 1.0;
- classification = XML;
- if (verbose)
- {
- *log << "XML input detected.\n";
- }
- readXMLinput(line);
- }
- else if (line == re_PGP)
+ if (line == re_PGP)
{
- certainty = 1.0;
+ uncertainty = 0.0;
gpg_encrypted = true;
- *log << "The message is PGP/GnuPG encrypted.\n";
- }
- else if (line == re_dump)
- {
- certainty = 1.0;
- if (verbose)
- {
- *log << "DUMP output detected.\n";
- }
- }
- else if (line == re_rpm)
- {
- certainty = 1.0;
- classification = RPMLIST;
- service = "";
- if (verbose)
- {
- *log << "RPM package list detected.\n";
- }
+ *Log << "The message is PGP/GnuPG encrypted.\n";
}
else
{
// Scan the list of line cookers if there is anything familiar.
- std::list<line_cooker *>::iterator lci = kitchen.begin();
- pan = 0;
- while (pan == 0 && lci != kitchen.end())
+ std::list<xform>::iterator lci = kitchen.begin();
+ uncertainty = 1.0;
+ while (lci != kitchen.end())
{
- pan = *lci;
- if (!pan->check_pattern(line))
+ if (lci->lc->check_pattern(line))
{
- pan = 0;
+ // We have a match; decrease the uncertainty
+
+ lci->uncertainty *= P;
+ if (uncertainty > lci->uncertainty)
+ {
+ uncertainty = lci->uncertainty;
+ pan = *lci;
+ }
+ if (verbose)
+ {
+ *Log << lci->lc->message_type() << " detected with "
+ << lci->uncertainty << " uncertainty.\n";
+ }
}
lci++;
}
- if (pan != 0)
- {
- certainty = 1.0;
- classification = COOKER_OBJECT;
- if (verbose)
- {
- *log << "Detected message type " << pan->message_type() << "\n";
- }
- }
+ classification = COOKER_OBJECT;
}
}
+
input.rewind();
- if (hostname == "")
- {
- *log << "Can not determine the hostname where the message came from.\n";
- certainty = 0.0;
- }
- else if (!arrival.proper())
- {
- *log << "Arrival time is not known.\n";
- certainty = 0.0;
- }
- else
- {
- certainty = 1.0;
- }
+ certainty = 1.0 - uncertainty;
return certainty;
}
** VARS CHANGED :
** FUNCTIONS USED :
** SEE ALSO :
-** LAST MODIFIED : Jul 24, 2003
+** LAST MODIFIED : Nov 02, 2007
**=========================================================================
*/
+struct param_property
+{
+ String name;
+ String value;
+ String type;
+ double minimum;
+ double maximum;
+};
+
void client_message::enterXML()
{
+ //TODO : return the number of elements that are handled.
+
xmlXPathObjectPtr res;
xmlXPathContextPtr pathcontext;
xmlNsPtr namespaces[1];
/* Try to find the host in the database */
String objectid;
+ String remark; // For notifications
objectid = database.find_host(hostname);
if (objectid == "")
{
- *log << "Please define the host " << hostname << " in the database.\n";
+ *Log << "Please define the host " << hostname << " in the database.\n";
return;
}
if (verbose)
{
- *log << "Object id for " << hostname << " is " << objectid << "\n";
+ *Log << "Object id for " << hostname << " is " << objectid << "\n";
}
pathcontext = xmlXPathNewContext(xmlDom);
String raw("");;
String log_service;
- if (strcmp((char *)node->name, "raw") == 0)
+ if (strcmp((char *)node->name, "raw") == 0 && node->children != NULL)
{
item = node->children;
- if (pan == 0)
+ if (pan.lc == 0)
{
- *log << "Can not cook this type of <raw> log element.\n";
+ *Log << "Can not cook this type of <raw> log element.\n";
}
else
{
raw = String((const char *)item->content);
- if (pan->cook_this(raw, arrival))
+ if (pan.lc->cook_this(raw, arrival))
{
- log_hostname = pan->hostname();
+ log_hostname = pan.lc->hostname();
if (log_hostname == "")
{
log_hostname = hostname;
}
- log_service = pan->service();
- log_date = pan->timestamp();
+ log_service = pan.lc->service();
+ log_date = pan.lc->timestamp();
}
else
{
- *log << "Log line " << raw << " does not match.\n";
+ *Log << "gcm_input WARNING: Not a valid line: " << raw << "\n";
raw = "";
}
}
if (verbose)
{
- *log << "Analyzing cooked element.\n";
+ *Log << "Analyzing cooked element.\n";
}
pathcontext->node = node;
log_hostname = (const char *)item->content;
if (log_hostname != hostname(0, ~log_hostname))
{
- *log << "Hostname " << log_hostname << " does not match.\n";
+ *Log << "Hostname " << log_hostname << " does not match.\n";
log_hostname = "";
}
}
}
else
{
- *log << "<timestamp> missing from cooked log element.\n";
+ *Log << "<timestamp> missing from cooked log element.\n";
}
res = xmlXPathEval((const xmlChar *)"raw/text()", pathcontext);
}
else
{
- *log << "<raw> missing from cooked log element.\n";
+ *Log << "<raw> missing from cooked log element.\n";
}
}
if (testmode)
{
- *log << insertion << "\n";
+ *Log << insertion << "\n";
}
else
{
database.Query(insertion);
}
- if (verbose)
- {
- *log << "\n\n";
- }
}
}
node = node->next;
}
}
- else
+ else if (strcmp((char *)node->name, "parameters") == 0)
{
- *log << "Data element " << node->name << " is not supported.\n";
- }
- }
- else
- {
- *log << "Data node not found.\n";
- }
-}
-
-/*=========================================================================
-** NAME : enter
-** SYNOPSIS : int enter()
-** PARAMETERS :
-** RETURN VALUE : The number of lines successfully parsed from the input
-**
-** DESCRIPTION :
-**
-** VARS USED :
-** VARS CHANGED :
-** FUNCTIONS USED :
-** SEE ALSO :
-** LAST MODIFIED : Jul 24, 2003
-**=========================================================================
-*/
-
-int client_message::enter()
-{
- if (classification == XML)
- {
- enterXML();
- return 1;
- }
-
- long nr_lines = 0;
- String line;
- String qry;
-
- String change_notification("");
- String create_notification("");
- bool initial_entry = false;
-
- std::list<String> packages;
-
-
- /* Double-check the classification of the message */
-
- if (classification == UNKNOWN || certainty < 0.9 || gpg_encrypted)
- {
- return 0;
- }
-
- if (mail_header)
- {
- // Skip the mail header.
-
- while (input >> line && line != "");
- }
-
- /* Try to find the host in the database */
-
- String objectid;
-
- objectid = database.find_host(hostname);
- if (objectid == "")
- {
- *log << "Please define the host " << hostname << " in the database.\n";
- return 0;
- }
- if (verbose)
- {
- *log << "Object id for " << hostname << " is " << objectid << "\n";
- }
-
- if (classification == RPMLIST)
- {
-
- int n_packages;
+ // Each child contains a parameter entry, with at least one property
+
+ String qry;
+ String insertion;
+ String change_notification("");
+ String out_of_range_notification("");
+ String create_notification("");
+ String remove_notification("");
+ bool initial_entry = false;
+ String param_class((const char *)xmlGetProp(node, (const xmlChar *)"class"));
+
+ std::list<param_property> parameter_template;
+ int nr_properties;
+
+ // Obtain a list of properties from the parameter's class.
+ // This list is used to create new parameters.
+
+ qry = "select * from parameter_class where name='";
+ qry += param_class + "'";
+ nr_properties = database.Query(qry);
+ for (int i = 0; i < nr_properties; i++)
+ {
+ param_property pp;
- /* Read all packages, so we will know which ones are */
- /* missing at the end. */
+ pp.name = database.Field(i, "property_name");
+ pp.type = database.Field(i, "property_type");
+ pp.minimum = database.Field(i, "min");
+ pp.maximum = database.Field(i, "max");
- qry = "select name from parameter where objectid='";
- qry += objectid + "' and class='package'";
- n_packages = database.Query(qry);
- initial_entry = n_packages == 0;
+ parameter_template.push_back(pp);
+ }
#ifdef DEBUG
- *log << n_packages << " packages in database.\n";
+ *Log << "Entering a list of " << param_class << " parameters.\n";
#endif
- for (int t = 0; t < n_packages; t++)
- {
- packages.push_back(database.Field(t, "name"));
- }
-#ifdef DEBUG
- *log << "Package list built: " << packages.size() << ".\n";
-#endif
- }
+ pathcontext->node = node;
- /* Scan the input line by line, entring records into the database */
+ // If we don't have any parameters of this class, this will be
+ // an initial entry.
- String rest; // Rest of the line to be parsed
- regex re_any(".*");
+ qry = "select name from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
+ initial_entry = database.Query(qry) == 0;
- while (input >> line)
- {
- if (verbose)
- {
- *log << line << "\n";
- }
-
-
- /* Check each line if it contains valid information */
-
- const regex *check;
-
- switch (classification)
- {
- case RPMLIST:
- check = &re_rpm;
- break;
- case COOKER_OBJECT:
- check = &re_any;
- break;
- }
-
- if (line == *check)
- {
- date log_date;
- hour log_time;
- int i;
-
- String insertion("insert into log (objectid, servicecode,"
- " object_timestamp, timestamp, rawdata, processed) values (");
- String datestring;
-
- switch (classification)
+ node = node->children;
+ while (node != NULL)
{
- case COOKER_OBJECT:
-#ifdef DEBUG
- std::cerr << "\ncooker check: " << pan->check_pattern(line) << "\n";
-#endif
- if (pan->cook_this(line, arrival))
+ if (node->type == XML_ELEMENT_NODE &&
+ strcmp((char *)node->name, "parameter") == 0)
{
- if (pan->hostname() == hostname(0,~pan->hostname()))
- {
+ String param_name((const char *)xmlGetProp(node, (const xmlChar *)"name"));
#ifdef DEBUG
- std::cerr << " Information from cooker:\n";
- std::cerr << " timestamp = " << pan->timestamp() << "\n";
- std::cerr << " hostname = " << pan->hostname() << "\n";
- std::cerr << " service = " << pan->service() << "\n";
+ *Log << "Parameter with name " << param_name << "\n";
#endif
- /* Insert a new record into the log table */
+ std::list<param_property> properties;
+ param_property prop;
+ xmlNodePtr item;
- insertion += "'" + objectid + "',";
- insertion += "'" + pan->service() + "',";
- insertion += "'" + pan->timestamp().format("%Y-%m-%d %T") + "',";
- insertion += "'" + arrival.format("%Y-%m-%d %T") + "',";
- insertion += "'" + SQL_Escape(line) + "',FALSE";
- insertion += ")";
+ String paramid;
- if (testmode)
- {
- *log << insertion << "\n";
- }
- else
- {
- database.Query(insertion);
- }
- if (verbose)
+ // Collect the parameter's properties.
+
+ item = node->children;
+ while (item != NULL)
+ {
+ if (item->type == XML_ELEMENT_NODE &&
+ strcmp((char *)item->name, "property") == 0)
{
- *log << "\n\n";
+ prop.name = (const char *)xmlGetProp(item, (const xmlChar *)"name");
+ if (item->children != NULL)
+ {
+ prop.value = (const char *)item->children->content;
+ properties.push_back(prop);
+ }
+ else
+ {
+ *Log << "WARNING: Property " << prop.name << " has no value.\n";
+ }
}
- nr_lines++;
- }
- else
- {
- *log << " Hostname " << pan->hostname() << " does not match.\n";
- }
- }
- else
- {
- *log << "gcm_input WARNING: Not a valid line: " << line << "\n";
- }
- break;
+ // TODO: Hanlde description element
+ item = item->next;
+ }
- case RPMLIST:
- // Scan a list of packages and versions from "rpm -a".
- // A similar listing can be created on IRIX 6.5 by using the
- // command "showprods -3 -n|awk '{printf "%s-%s\n",$2,$3}'|grep -v '^[-=]' \
- // |grep -v Version-Description".
- //
- // We have to separate the package name and the version.
- // The separation is marked by a '-', followed by a digit.
- // However, there may be other sequences of '-'digit in the package name,
- // do we have to scan ahead until there is at most one such sequence
- // left in the version string. The '-'digit seqeunce inside the
- // version usually separates the version and the release number.
+ // Check the parameter in the database.
- int version_start, next_version_start;
+ std::list<param_property>::iterator pi = properties.begin();
- i = line.index('-');
- version_start = i;
- next_version_start = i;
+ qry = "select paramid from parameter where objectid='";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
- while (i < ~line - 1)
- {
- while (i < ~line - 1 && !(line[i] == '-' && isdigit(line[i + 1])))
- {
- i++;
- }
- if (i < ~line - 1)
+ if (database.Query(qry) == 1)
{
- version_start = next_version_start;
- next_version_start = i;
- }
- i++;
- }
-
- if (!isdigit(line[version_start + 1]))
- {
- version_start = next_version_start;
- }
- String package(line(0,version_start));
- String version(line(version_start + 1, ~line));
- String paramid;
- String remark;
- String insert_h;
+ // The parameter exists in the database; check all properties.
- if (verbose)
- {
- *log << "Package is " << package;
- *log << ", version is " << version << "\n";
- }
+ bool param_changed = false;
+ bool out_of_range = false;
- // Construct a qry to check the package's existance
+ paramid = database.Field(0, "paramid");
+ while (pi != properties.end())
+ {
+ qry = "select * from property where paramid='";
+ qry += paramid + "' and name='";
+ qry += pi->name + "'";
+ if (database.Query(qry) == 0)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " does not exist.\n";
+ // Find the property in the template from the class.
- qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += package + "'";
+ String property_type("STATIC");
+ double property_minimum = 0.0;
+ double property_maximum = 0.0;
+ std::list<param_property>::iterator ti = parameter_template.begin();
- if (database.Query(qry) == 1)
- {
- std::list<String>::iterator lp;
+ while (ti != parameter_template.end() && ti->name != pi->name)
+ {
+ ti++;
+ }
+ if (ti != parameter_template.end())
+ {
+ property_type = ti->type;
+ property_minimum = ti->minimum;
+ property_maximum = ti->maximum;
+ }
- lp = find(packages.begin(), packages.end(), package);
- if (lp != packages.end())
- {
- packages.erase(lp);
- }
- else
- {
- *log << "Could NOT find " << package << " in list.\n";
- }
+ insertion = "insert into property (paramid, name, value, type, min, max) values ('";
+ insertion += paramid + "', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "', '";
+ insertion += property_type + "', '";
+ insertion += String(property_minimum) + "', '";
+ insertion += String(property_maximum) + "')";
+ database.Query(insertion);
- paramid = database.Field(0, "paramid");
- qry = "select value from property where paramid='";
- qry += paramid + "' and name='version'";
- if (database.Query(qry) == 0)
- {
- *log << "Database corruption: Package " << package;
- *log << " does not have a 'version' property.\n";
- }
- else if (database.Field(0, "value") != version)
- {
- if (verbose)
- {
- *log << " Parameter " << package << " has different version\n";
- }
- insertion = "update property set value='";
- insertion += version + "' where paramid='";
- insertion += paramid + "' and name='version'";
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'CREATED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+ database.Query(insertion);
+ }
+ else
+ {
+ param_property stored_property;
- insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
- insert_h += " values ('";
- insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'MODIFIED', 'version', '";
- insert_h += version + "')";
+ stored_property.value = database.Field(0, "value");
+ stored_property.type = database.Field(0, "type");
+ stored_property.minimum = database.Field(0, "min");
+ stored_property.maximum = database.Field(0, "max");
- database.Query(insertion);
- database.Query(insert_h);
+ if (stored_property.value != pi->value)
+ {
+ *Log << "Property " << pi->name << " of "
+ << param_name << " is different.\n";
+
+ insertion = "update property set value='";
+ insertion += pi->value + "' where paramid='";
+ insertion += paramid + "' and name='";
+ insertion += pi->name + "'";
+
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'MODIFIED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+
+ database.Query(insertion);
+ if (stored_property.type == "DYNAMIC")
+ {
+ // Check the value against the range of the property.
+
+ double numeric_value = pi->value;
+ if (numeric_value < stored_property.minimum || numeric_value > stored_property.maximum)
+ {
+ out_of_range = true;
+ }
+ }
+ else
+ {
+ // A STATIC property changed.
+ param_changed = true;
+ }
- if (change_notification == "")
- {
- remark = "Gnucomo detected a different version for package parameter(s) ";
- change_notification = database.new_notification(objectid, "property modified", remark);
- change_notification = database.new_notification(objectid, "property modified", remark);
+ }
+ }
+ pi++;
}
- if (change_notification != "")
+ if (param_changed)
{
- qry = "select * from parameter_notification where notificationid='";
- qry += change_notification + "' and paramid='";
- qry += paramid + "'";
+ if (change_notification == "")
+ {
+ remark = "Gnucomo detected a different property for parameter(s) ";
+ change_notification = database.new_notification(objectid,
+ "property modified", remark);
+ }
- if (database.Query(qry) == 0)
+ if (change_notification != "")
{
- insertion = "insert into parameter_notification (notificationid, paramid) values ('";
- insertion += change_notification + "', '";
- insertion += paramid + "')";
+ qry = "select * from parameter_notification where notificationid='";
+ qry += change_notification + "' and paramid='";
+ qry += paramid + "'";
- database.Query(insertion);
+ if (database.Query(qry) == 0)
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += change_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
}
}
- else
+
+ if (out_of_range)
{
- *log << "gcm_input ERROR: Cannot create 'property modified' notification.\n";
+ if (out_of_range_notification == "")
+ {
+ remark = "Gnucomo detected that a property value is out of range.";
+ out_of_range_notification = database.new_notification(objectid,
+ "property out of range", remark);
+ }
+
+ if (out_of_range_notification != "")
+ {
+ qry = "select * from parameter_notification where notificationid='";
+ qry += out_of_range_notification + "' and paramid='";
+ qry += paramid + "'";
+
+ if (database.Query(qry) == 0)
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += out_of_range_notification + "', '";
+ insertion += paramid + "')";
+
+ database.Query(insertion);
+ }
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'property out of range' notification.\n";
+ }
}
}
else
{
- if (verbose)
- {
- *log << " Parameter " << package << " has not changed.\n";
- }
- }
- }
- else
- {
+ // The parameter does not exist; create anew.
- if (verbose)
- {
- *log << " Parameter " << package << " does not exist.\n";
- }
- // Create a new package parameter, including version property and history record
+ // TODO: Insert description
- insertion = "insert into parameter (objectid, name, class, description) values ('";
- insertion += objectid + "', '" + package + "', 'package', 'RPM package " + package + "')";
- if (testmode)
- {
- paramid = "0";
- *log << insertion << "\n";
- }
- else
- {
+ insertion = "insert into parameter (objectid, name, class, description) values ('";
+ insertion += objectid + "', '" + param_name + "', '" + param_class + "', '')";
database.Query(insertion);
+
qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += package + "'";
+ qry += objectid + "' and class='";
+ qry += param_class + "' and name='";
+ qry += param_name + "'";
database.Query(qry);
paramid = database.Field(0, "paramid");
- }
- insertion = "insert into property (paramid, name, value, type) values ('";
- insertion += paramid + "', 'version', '";
- insertion += version + "', 'STATIC')";
- insert_h = "insert into history (paramid, modified, change_nature, changed_property, new_value)";
- insert_h += " values ('";
- insert_h += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'CREATED', 'version', '";
- insert_h += version + "')";
+ while (pi != properties.end())
+ {
+ // Find the property in the template from the class.
+
+ String property_type("STATIC");
+ double property_minimum = 0.0;
+ double property_maximum = 0.0;
+ std::list<param_property>::iterator ti = parameter_template.begin();
+
+ while (ti != parameter_template.end() && ti->name != pi->name)
+ {
+ ti++;
+ }
+ if (ti != parameter_template.end())
+ {
+ property_type = ti->type;
+ property_minimum = ti->minimum;
+ property_maximum = ti->maximum;
+ }
+
+ insertion = "insert into property (paramid, name, value, type, min, max) values ('";
+ insertion += paramid + "', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "', '";
+ insertion += property_type + "', '";
+ insertion += String(property_minimum) + "', '";
+ insertion += String(property_maximum) + "')";
+ database.Query(insertion);
+
+ insertion = "insert into history (paramid, modified,";
+ insertion += " change_nature, changed_property, new_value)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'CREATED', '";
+ insertion += pi->name + "', '";
+ insertion += pi->value + "')";
+ database.Query(insertion);
+
+ pi++;
+ }
- if (testmode)
- {
- *log << insertion << "\n" << insert_h << "\n";
- }
- else
- {
- database.Query(insertion);
- database.Query(insert_h);
if (!initial_entry)
{
if (create_notification == "")
{
- remark = "Gnucomo detected new parameter(s) of class package";
- create_notification = database.new_notification(objectid, "parameter created", remark);
+ remark = "Gnucomo detected new parameter(s) of class " + param_class;
+ create_notification = database.new_notification(objectid,
+ "parameter created", remark);
}
if (create_notification != "")
{
- insertion = "insert into parameter_notification (notificationid, paramid) values ('";
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
insertion += create_notification + "', '";
insertion += paramid + "')";
}
else
{
- *log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
+ *Log << "gcm_input ERROR: Cannot create 'parameter created' notification.\n";
}
}
}
}
- if (verbose)
- {
- *log << "\n";
- }
-
- nr_lines++;
- break;
-
+ node = node->next;
}
- }
- else
- {
- *log << "gcm_input WARNING: Not a valid line: " << line << "\n";
- }
- }
- if (classification == RPMLIST && !incremental)
- {
- std::list<String>::iterator lp;
- String remove_notification("");
+ if (!incremental)
+ {
+ // Check if any parameters in this class have disappeared.
- /*
- * If there are any packages left in the list, they seem to have
- * disappeared from the system.
- */
+#ifdef DEBUG
+ *Log << "Checking for disappeared parameters.\n";
+#endif
+ qry = "select name, paramid from parameter where objectid='";
+ qry += objectid + "' and class='" + param_class + "'";
- for (lp = packages.begin(); lp != packages.end(); lp++)
- {
- String paramid;
- String remark;
- String insert;
+ int nr_parameters = database.Query(qry);
+ pqxx::result parameter_set = database.Result();
- // Construct a qry to check the package's existance
+#ifdef DEBUG
+ *Log << nr_parameters << " parameters of class " << param_class << " found in the database.\n";
+#endif
+ for (int i = 0; i < nr_parameters; i++)
+ {
+ String XPath;
+ String param_name, paramid;
- qry = "select paramid from parameter where objectid='";
- qry += objectid + "' and class='package' and name='";
- qry += *lp + "'";
+ param_name = database.Field(parameter_set, i, "name");
+#ifdef DEBUG
+ *Log << "Looking for " << param_name << " in XML tree.\n";
+#endif
+ XPath = "gcmt:parameter[@name='" + param_name + "']";
- if (database.Query(qry) == 1)
- {
- paramid = database.Field(0, "paramid");
- qry ="select change_nature from history where paramid='";
- qry += paramid + "' order by modified desc";
- if (database.Query(qry) <= 0)
- {
- *log << "Database ERROR: no history record for parameter " << *lp << ".\n";
- }
- else if (database.Field(0, "change_nature") != "REMOVED")
- {
- if (verbose)
+ res = xmlXPathEval((const xmlChar *)(const char *)XPath, pathcontext);
+#ifdef DEBUG
+ *Log << "XPATH result: " << res->type << ".\n";
+ *Log << "Nr of nodes found: " << res->nodesetval->nodeNr << ".\n";
+#endif
+ if (res->nodesetval->nodeNr == 0)
{
- *log << "Removing parameter " << *lp << ".\n";
- }
+ // The parameter is in the database but not in the report
- insert = "insert into history (paramid, modified, change_nature)";
- insert += " values ('";
- insert += paramid + "', '" + arrival.format("%Y-%m-%d %T") + "', 'REMOVED')";
+#ifdef DEBUG
+ *Log << "Could not find " << XPath << " in XML tree.\n";
+#endif
+ paramid = database.Field(parameter_set, i, "paramid");
+ qry ="select change_nature from history where paramid='";
+ qry += paramid + "' order by modified desc";
+ if (database.Query(qry) <= 0)
+ {
+ *Log << "Database ERROR: no history record for parameter "
+ << param_name << ".\n";
+ }
+ else if (database.Field(0, "change_nature") != "REMOVED")
+ {
+ if (verbose)
+ {
+ *Log << "Removing parameter " << param_name << ".\n";
+ }
- database.Query(insert);
+ insertion = "insert into history (paramid, modified, change_nature)";
+ insertion += " values ('";
+ insertion += paramid + "', '" + arrival.format("%Y-%m-%d %T")
+ + "', 'REMOVED')";
- if (remove_notification == "")
- {
- remark = "Gnucomo detected that package(s) have disappeared ";
- remove_notification = database.new_notification(objectid, "parameter removed", remark);
- }
+ database.Query(insertion);
- if (remove_notification != "")
- {
- insert = "insert into parameter_notification (notificationid, paramid) values ('";
- insert += remove_notification + "', '";
- insert += paramid + "')";
+ if (remove_notification == "")
+ {
+ remark = "Gnucomo detected that " + param_class
+ + " parameters(s) have disappeared ";
+ remove_notification = database.new_notification(objectid,
+ "parameter removed", remark);
+ }
+
+ if (remove_notification != "")
+ {
+ insertion = "insert into parameter_notification";
+ insertion += " (notificationid, paramid) values ('";
+ insertion += remove_notification + "', '";
+ insertion += paramid + "')";
- database.Query(insert);
+ database.Query(insertion);
+ }
+ else
+ {
+ *Log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+ }
+ }
}
else
{
- *log << "gcm_input ERROR: Cannot create 'parameter removed' notification.\n";
+#ifdef DEBUG
+ *Log << XPath << " was found in XML tree.\n";
+#endif
}
}
}
}
+ else
+ {
+ *Log << "Data element " << node->name << " is not supported.\n";
+ }
}
+ else
+ {
+ *Log << "Data node not found.\n";
+ }
+}
- if (verbose)
+/*=========================================================================
+** NAME : enter
+** SYNOPSIS : int enter()
+** PARAMETERS :
+** RETURN VALUE : The number of lines successfully parsed from the input
+**
+** DESCRIPTION :
+**
+** VARS USED :
+** VARS CHANGED :
+** FUNCTIONS USED :
+** SEE ALSO :
+** LAST MODIFIED : Nov 26, 2003
+**=========================================================================
+*/
+
+int client_message::enter()
+{
+ pan.mf->set_message_type(pan.lc->message_type());
+
+ pan.mf->construct_XML(input, xmlBuffer);
+
+#ifdef DEBUG
+ *Log << "Constructed XML document:\n\n";
+ *Log << xmlBuffer.str();
+ *Log << "\n";
+#endif
+
+ xmlDom = xmlParseMemory(xmlBuffer.str(), xmlBuffer.pcount());
+
+ if (xmlDom)
{
- *log << nr_lines << " lines parsed from the log file.\n";
+ if (extractHeader())
+ {
+ enterXML();
+ }
}
- return nr_lines;
+ else
+ {
+ *Log << "XML parser FAILED.\n";
+ }
+
+ return 0;
+
}