diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/Makefile.am | 19 | ||||
-rw-r--r-- | server/README.xmlrpc | 24 | ||||
-rw-r--r-- | server/apache-rteval-wsgi.conf.tpl | 41 | ||||
-rw-r--r-- | server/configure.ac | 14 | ||||
-rw-r--r-- | server/database.py | 2 | ||||
-rwxr-xr-x | server/gen_config.sh | 6 | ||||
-rw-r--r-- | server/parser/pgsql.c | 65 | ||||
-rw-r--r-- | server/parser/xmlparser.c | 91 | ||||
-rw-r--r-- | server/parser/xmlparser.h | 11 | ||||
-rw-r--r-- | server/parser/xmlparser.xsl | 25 | ||||
-rw-r--r-- | server/rteval-parser.spec | 14 | ||||
-rw-r--r-- | server/rteval_xmlrpc.wsgi | 113 | ||||
-rw-r--r-- | server/rtevaldb.py | 6 | ||||
-rw-r--r-- | server/sql/delta-1.2_1.3.sql | 5 | ||||
-rw-r--r-- | server/sql/delta-1.3_1.4.sql | 5 | ||||
-rw-r--r-- | server/sql/rteval-1.3.sql | 206 | ||||
-rw-r--r-- | server/sql/rteval-1.4.sql | 207 | ||||
-rw-r--r-- | server/xmlrpc_API1.py | 2 |
18 files changed, 826 insertions, 30 deletions
diff --git a/server/Makefile.am b/server/Makefile.am index e7392b3..bf56ac4 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -1,6 +1,6 @@ # Makefile.am - autotools configuration file # -# Copyright 2009 David Sommerseth <davids@redhat.com> +# Copyright 2009-2011 David Sommerseth <davids@redhat.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -26,21 +26,32 @@ SUBDIRS = parser dist_doc_DATA = parser/README.parser \ sql/delta-1.0_1.1.sql \ sql/delta-1.1_1.2.sql \ + sql/delta-1.2_1.3.sql \ + sql/delta-1.3_1.4.sql \ sql/rteval-$(SQLSCHEMAVER).sql apache-rteval.conf: - [ -n $(XMLRPCROOT) ] && $(srcdir)/gen_config.sh $(XMLRPCROOT)/API1 +if ENAB_MODPYTHON + [ -n $(XMLRPCROOT) ] && $(srcdir)/gen_config.sh apache-rteval.conf $(XMLRPCROOT)/API1 +else + [ -n $(XMLRPCROOT) ] && $(srcdir)/gen_config.sh apache-rteval-wsgi.conf $(XMLRPCROOT)/API1 +endif clean-local: -rm -f apache-rteval.conf *~ dist-hook: - cp $(srcdir)/gen_config.sh $(srcdir)/apache-rteval.conf.tpl $(distdir)/ + cp $(srcdir)/gen_config.sh $(srcdir)/apache-rteval.conf.tpl $(srcdir)/apache-rteval-wsgi.conf.tpl $(distdir)/ -rm -f $(distdir)/apache-rteval.conf if ENAB_XMLRPC xmlrpcdir = $(XMLRPCROOT)/API1 BUILT_SOURCES = apache-rteval.conf dist_doc_DATA += README.xmlrpc apache-rteval.conf - dist_xmlrpc_DATA = rteval_xmlrpc.py xmlrpc_API1.py rtevaldb.py database.py + dist_xmlrpc_DATA = xmlrpc_API1.py rtevaldb.py database.py +if ENAB_MODPYTHON + dist_xmlrpc_DATA += rteval_xmlrpc.py +else + dist_xmlrpc_DATA += rteval_xmlrpc.wsgi +endif endif diff --git a/server/README.xmlrpc b/server/README.xmlrpc index 438657a..20bade2 100644 --- a/server/README.xmlrpc +++ b/server/README.xmlrpc @@ -21,7 +21,7 @@ track how each system changes behaviour on different kernels. ** Requirements ** - Apache web server - - mod_python-3.3.x + - mod_python-3.3.x OR mod_wsgi-3.2 or newer. - PostgreSQL v8.3 or later - rteval 1.12 or later @@ -37,7 +37,7 @@ The default path used for the rteval client is If you have a HTTP setup which will follow this scheme, you do not need to change any URLs at all. -When installing the rteval-xmlrpc-1.1 RPM on a Fedora/RHEL based box, +When installing the rteval-xmlrpc-1.4 RPM on a Fedora/RHEL based box, Apache will be automatically configured. But the Apache web server will need to be restarted when you have setup the database. @@ -56,7 +56,7 @@ All reports are saved in a database. If you have not used the rteval-xmlrpc interface before, you need to create the needed database user and database, execute the following command line: - # psql < /usr/share/doc/rteval-xmlrpc-1.1/rteval-1.2.sql + # psql < /usr/share/doc/rteval-xmlrpc-1.5/rteval-1.4.sql This script will first create a database user called 'rtevxmlrpc' and 'rtevparser', assign default password before creating the database called @@ -69,10 +69,11 @@ directory. You need to allow the xmlrpc user access from the web server. pg_hba.conf entry example: - +----------------------------------------------------------------- # TYPE DATABASE USER CIDR-ADDRESS METHOD hostssl rteval rtevxmlrpc 127.0.0.1/32 md5 hostssl rteval rtevparser 127.0.0.1/32 md5 +----------------------------------------------------------------- The XML-RPC database connector will always try to connect via SSL. To modify the default password, connect to the database with psql and @@ -106,12 +107,21 @@ To find out which schema version you using, do the following: This indicates that the database is at the schema version 1.1. If you do not have the rteval_info table, you are for sure on schema version 1.0. -* Update from schema version 1.0 to 1.1 +* Update from SQL schema version 1.0 to 1.1 psql rteval < /usr/share/doc/rteval-xmlrpc-1.1/delta-1.0_1.1.sql -* Update from schema version 1.2 to 1.2 +* Update from SQL schema version 1.1 to 1.2 psql rteval < /usr/share/doc/rteval-xmlrpc-1.1/delta-1.1_1.2.sql +* Update from SQL schema version 1.2 to 1.3 + psql rteval < /usr/share/doc/rteval-xmlrpc-1.1/delta-1.2_1.3.sql + +* Update from SQL schema version 1.3 to 1.4 + psql rteval < /usr/share/doc/rteval-xmlrpc-1.1/delta-1.3_1.4.sql + +You need to upgrade to the latest SQL schema available, and you must upgrade +sequentially through all the version in between your version and the latest. + ** ** Building an installing from source @@ -137,7 +147,7 @@ install'. The default install prefix is /usr/local, unless you changed it with --prefix=<path>. You will then find the rteval_parserd installed under /usr/local/bin/rteval_parserd and README files, the SQL scripts and an Apache config file will be found -under /usr/local/share/doc/rteval-parser-1.1/ +under /usr/local/share/doc/rteval-parser-1.5/ The Apache configuration file can be copied into the configuration directory Apache uses for its modules. On RHEL/Fedora based diff --git a/server/apache-rteval-wsgi.conf.tpl b/server/apache-rteval-wsgi.conf.tpl new file mode 100644 index 0000000..9bf5fea --- /dev/null +++ b/server/apache-rteval-wsgi.conf.tpl @@ -0,0 +1,41 @@ +# File: apache-rteval.conf +# +# Apache config entry to enable the rteval XML-RPC server +# +# Copyright 2011 David Sommerseth <davids@redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# For the avoidance of doubt the "preferred form" of this code is one which +# is in an open unpatent encumbered format. Where cryptographic key signing +# forms part of the process of creating an executable the information +# including keys needed to generate an equivalently functional executable +# are deemed to be part of the source code. +# + +WSGISocketPrefix /var/run/wsgi +WSGIDaemonProcess rtevalxmlrpc processes=3 threads=15 python-path={_INSTALLDIR_} +WSGIScriptAlias /rteval/API1 {_INSTALLDIR_}/rteval_xmlrpc.wsgi + +<Directory "{_INSTALLDIR_}"> + Options Indexes FollowSymLinks + AllowOverride None + Order allow,deny + Allow from all + + WSGIProcessGroup rtevalxmlrpc + WSGICallableObject rtevalXMLRPC_handler +</Directory> + diff --git a/server/configure.ac b/server/configure.ac index f68194b..d0721c3 100644 --- a/server/configure.ac +++ b/server/configure.ac @@ -1,6 +1,6 @@ # configure.ac - autotools configuration file # -# Copyright 2009 David Sommerseth <davids@redhat.com> +# Copyright 2009-2011 David Sommerseth <davids@redhat.com> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -25,8 +25,8 @@ # To create the ./configure script you need to run 'autoreconf --install' # -AC_INIT([rteval-xmlrpc], [1.3], [davids@redhat.com]) -SQLSCHEMAVER=1.2 +AC_INIT([rteval-xmlrpc], [1.5], [davids@redhat.com]) +SQLSCHEMAVER=1.4 AC_SUBST(SQLSCHEMAVER) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) @@ -40,6 +40,14 @@ AC_ARG_WITH([xmlrpc-webroot], AC_SUBST(XMLRPCROOT) AM_CONDITIONAL([ENAB_XMLRPC], [test ! -z $XMLRPCROOT]) +AC_ARG_ENABLE([mod-python], + [AS_HELP_STRING([--with-mod-python], + [Enable the older mod_python support instead of mod_wsgi])], + [MODPYTHON="$enableval"] +) +AC_SUBST(MODPYTHON) +AM_CONDITIONAL([ENAB_MODPYTHON], [test ! -z $MODPYTHON]) + # Simple macro to abort on missing functions in libraries AC_DEFUN([AX_msgMISSINGFUNC], AC_MSG_ERROR([Could not find function in library. Aborting])) diff --git a/server/database.py b/server/database.py index 389619c..c749593 100644 --- a/server/database.py +++ b/server/database.py @@ -168,7 +168,7 @@ class Database(object): # If no action is setup (mainly for debugging), return empty result set return {"table": table, "fields": [], "records": []} except Exception, err: - raise Exception, "** SQL ERROR *** %s\n** SQL ERROR ** Message: %s" % ((sql % where), str(err)) + raise Exception, "** SQL ERROR *** %s\n** SQL ERROR ** Message: %s" % (where and (sql % where) or sql, str(err)) # Extract field names fields = [] diff --git a/server/gen_config.sh b/server/gen_config.sh index 4b57353..23181ee 100755 --- a/server/gen_config.sh +++ b/server/gen_config.sh @@ -1,12 +1,12 @@ #/bin/sh -APACHECONF="apache-rteval.conf" -INSTALLDIR="$1" +APACHECONF="$1" +INSTALLDIR="$2" echo "Creating Apache config file: apache-rteval.conf" escinstpath="$(echo ${INSTALLDIR} | sed -e 's/\//\\\\\//g')" expr=$(echo "s/{_INSTALLDIR_}/${escinstpath}/") -eval "sed -e ${expr} ${APACHECONF}.tpl" > ${APACHECONF} +eval "sed -e ${expr} ${APACHECONF}.tpl" > apache-rteval.conf echo "Copy the apache apache-rteval.conf into your Apache configuration" echo "directory and restart your web server" echo diff --git a/server/parser/pgsql.c b/server/parser/pgsql.c index 44032e5..8ea5d08 100644 --- a/server/parser/pgsql.c +++ b/server/parser/pgsql.c @@ -44,6 +44,14 @@ #include <log.h> #include <statuses.h> +/** forward declaration, to be able to setup dbhelper_func pointers */ +static char * pgsql_BuildArray(LogContext *log, xmlNode *sql_n); + +/** Helper functions the xmlparser might beed */ +static dbhelper_func pgsql_helpers = { + .dbh_FormatArray = &(pgsql_BuildArray) +}; + /** * Connect to a database, based on the given configuration * @@ -106,7 +114,7 @@ dbconn *db_connect(eurephiaVALUES *cfg, unsigned int id, LogContext *log) { if( dbr ) { PQclear(dbr); } - + init_xmlparser(&pgsql_helpers); return ret; } @@ -414,6 +422,61 @@ eurephiaVALUES *pgsql_INSERT(dbconn *dbc, xmlDoc *sqldoc) { return res; } +/** + * @copydoc sqldataValueArray() + */ +static char * pgsql_BuildArray(LogContext *log, xmlNode *sql_n) { + char *ret = NULL, *ptr = NULL; + xmlNode *node = NULL; + size_t retlen = 0; + + ret = malloc_nullsafe(log, 2); + if( ret == NULL ) { + writelog(log, LOG_ERR, + "Failed to allocate memory for a new PostgreSQL array"); + return NULL; + } + strncat(ret, "{", 1); + + /* Iterate all ./value/value elements and build up a PostgreSQL specific array */ + foreach_xmlnode(sql_n->children, node) { + if( (node->type != XML_ELEMENT_NODE) + || xmlStrcmp(node->name, (xmlChar *) "value") != 0 ) { + // Skip uninteresting nodes + continue; + } + ptr = sqldataValueHash(log, node); + if( ptr ) { + retlen += strlen(ptr) + 4; + ret = realloc(ret, retlen); + if( ret == NULL ) { + writelog(log, LOG_ERR, + "Failed to allocate memory to expand " + "array to include '%s'", ptr); + free_nullsafe(ret); + free_nullsafe(ptr); + return NULL; + } + /* Newer PostgreSQL servers expects numbers to be without quotes */ + if( isNumber(ptr) == 0 ) { + /* Data is a string */ + strncat(ret, "'", 1); + strncat(ret, ptr, strlen(ptr)); + strncat(ret, "',", 2); + } else { + /* Data is a number */ + strncat(ret, ptr, strlen(ptr)); + strncat(ret, ",", 1); + } + free_nullsafe(ptr); + } + } + /* Replace the last comma with a close-array marker */ + ret[strlen(ret)-1] = '}'; + ret[strlen(ret)] = 0; + return ret; +} + /** * Start an SQL transaction (SQL BEGIN) diff --git a/server/parser/xmlparser.c b/server/parser/xmlparser.c index fb1330a..0ff6e72 100644 --- a/server/parser/xmlparser.c +++ b/server/parser/xmlparser.c @@ -42,6 +42,8 @@ #include <sha1.h> #include <log.h> +static dbhelper_func const * xmlparser_dbhelpers = NULL; + /** * Simple strdup() function which encapsulates the string in single quotes, * which is needed for XSLT parameter values @@ -84,6 +86,33 @@ static char *encapsInt(const unsigned int val) { /** + * Simple function to determine if the given string is a number or not + * + * @param str Pointer to the tring to be checked + * + * @returns Returns 0 if not a number and a non-null value if it is a number + */ +int isNumber(const char * str) +{ + char *ptr = NULL; + + if (str == NULL || *str == '\0' || isspace(*str)) + return 0; + + strtod (str, &ptr); + return *ptr == '\0'; +} + +/** + * Initialise the XML parser, setting some global variables + */ +void init_xmlparser(dbhelper_func const * dbhelpers) +{ + xmlparser_dbhelpers = dbhelpers; +} + + +/** * Parses any XML input document into a sqldata XML format which can be used by pgsql_INSERT(). * The transformation must be defined in the input XSLT template. * @@ -100,6 +129,11 @@ xmlDoc *parseToSQLdata(LogContext *log, xsltStylesheet *xslt, xmlDoc *indata_d, unsigned int idx = 0, idx_table = 0, idx_submid = 0, idx_syskey = 0, idx_rterid = 0, idx_repfname = 0; + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return NULL; + } + if( params->table == NULL ) { writelog(log, LOG_ERR, "Table is not defined"); return NULL; @@ -170,16 +204,17 @@ xmlDoc *parseToSQLdata(LogContext *log, xsltStylesheet *xslt, xmlDoc *indata_d, * @return Returns a pointer to a new buffer containing the value on success, otherwise NULL. * This memory buffer must be free'd after usage. */ -static inline char *sqldataValueHash(LogContext *log, xmlNode *sql_n) { +char * sqldataValueHash(LogContext *log, xmlNode *sql_n) { const char *hash = NULL, *isnull = NULL; SHA1Context shactx; uint8_t shahash[SHA1_HASH_SIZE]; char *ret = NULL, *ptr = NULL; int i; - if( !sql_n || (xmlStrcmp(sql_n->name, (xmlChar *) "value") != 0) - || (xmlStrcmp(sql_n->parent->name, (xmlChar *) "record") != 0) ) { - return NULL; + if( !(sql_n && (xmlStrcmp(sql_n->name, (xmlChar *) "value") == 0) + && (xmlStrcmp(sql_n->parent->name, (xmlChar *) "record") == 0) + || (xmlStrcmp(sql_n->parent->name, (xmlChar *) "value") == 0)) ) { + return NULL; } isnull = xmlGetAttrValue(sql_n->properties, "isnull"); @@ -214,6 +249,27 @@ static inline char *sqldataValueHash(LogContext *log, xmlNode *sql_n) { /** + * Extract the content of a //sqldata/records/record/value[@type='array']/value node set + * and format it in suitable array format for the database backend. + * + * @param log Log context + * @param sql_n sqldata values node containing the value to extract and format as an array. + * + * @return Returns a pointer to a new memory buffer containing the value as a string. + * On errors, NULL is returned. This memory buffer must be free'd after usage. + */ +static char * sqldataValueArray(LogContext *log, xmlNode *sql_n) +{ + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return NULL; + } + + return xmlparser_dbhelpers->dbh_FormatArray(log, sql_n); +} + + +/** * Extract the content of a '//sqldata/records/record/value' node. It will consider * both the 'hash' and 'type' attributes of the 'value' tag. * @@ -226,6 +282,11 @@ static inline char *sqldataValueHash(LogContext *log, xmlNode *sql_n) { char *sqldataExtractContent(LogContext *log, xmlNode *sql_n) { const char *valtype = xmlGetAttrValue(sql_n->properties, "type"); + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return NULL; + } + if( !sql_n || (xmlStrcmp(sql_n->name, (xmlChar *) "value") != 0) || (xmlStrcmp(sql_n->parent->name, (xmlChar *) "record") != 0) ) { return NULL; @@ -239,6 +300,8 @@ char *sqldataExtractContent(LogContext *log, xmlNode *sql_n) { chld_n = chld_n->next; } return xmlNodeToString(log, chld_n); + } else if( valtype && (strcmp(valtype, "array") == 0) ) { + return sqldataValueArray(log, sql_n); } else { return sqldataValueHash(log, sql_n); } @@ -259,6 +322,11 @@ char *sqldataExtractContent(LogContext *log, xmlNode *sql_n) { int sqldataGetFid(LogContext *log, xmlNode *sql_n, const char *fname) { xmlNode *f_n = NULL; + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return -2; + } + if( !sql_n || (xmlStrcmp(sql_n->name, (xmlChar *) "sqldata") != 0) ) { writelog(log, LOG_ERR, "sqldataGetFid: Input XML document is not a valid sqldata document"); @@ -310,6 +378,11 @@ char *sqldataGetValue(LogContext *log, xmlDoc *sqld, const char *fname, int reci xmlNode *r_n = NULL; int fid = -3, rc = 0; + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return NULL; + } + if( recid < 0 ) { writelog(log, LOG_ERR, "sqldataGetValue: Invalid recid"); return NULL; @@ -384,6 +457,11 @@ xmlDoc *sqldataGetHostInfo(LogContext *log, xsltStylesheet *xslt, xmlDoc *summar xmlDoc *hostinfo_d = NULL; parseParams prms; + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return NULL; + } + memset(&prms, 0, sizeof(parseParams)); prms.table = "systems_hostname"; prms.syskey = syskey; @@ -423,6 +501,11 @@ int sqldataGetRequiredSchemaVer(LogContext *log, xmlNode *sqldata_root) char *schver = NULL, *cp = NULL, *ptr = NULL; int majv = 0, minv = 0; + if( xmlparser_dbhelpers == NULL ) { + writelog(log, LOG_ERR, "Programming error: xmlparser is not initialised"); + return -1; + } + if( !sqldata_root || (xmlStrcmp(sqldata_root->name, (xmlChar *) "sqldata") != 0) ) { writelog(log, LOG_ERR, "sqldataGetRequiredSchemaVer: Invalid document node"); return -1; diff --git a/server/parser/xmlparser.h b/server/parser/xmlparser.h index f903037..79c79a1 100644 --- a/server/parser/xmlparser.h +++ b/server/parser/xmlparser.h @@ -39,6 +39,16 @@ typedef struct { unsigned int rterid; /**< References rtevalruns.rterid */ } parseParams; + +/** + * Database specific helper functions + */ +typedef struct { + char *(*dbh_FormatArray)(LogContext *log, xmlNode *sql_n); /** Formats data as arrays */ +} dbhelper_func; + +void init_xmlparser(dbhelper_func const * dbhelpers); +char * sqldataValueHash(LogContext *log, xmlNode *sql_n); xmlDoc *parseToSQLdata(LogContext *log, xsltStylesheet *xslt, xmlDoc *indata_d, parseParams *params); char *sqldataExtractContent(LogContext *log, xmlNode *sql_n); int sqldataGetFid(LogContext *log, xmlNode *sqld, const char *fname); @@ -46,4 +56,5 @@ char *sqldataGetValue(LogContext *log, xmlDoc *sqld, const char *fname, int reci xmlDoc *sqldataGetHostInfo(LogContext *log, xsltStylesheet *xslt, xmlDoc *summaryxml, int syskey, char **hostname, char **ipaddr); int sqldataGetRequiredSchemaVer(LogContext *log, xmlNode *sqldata_root); + #endif diff --git a/server/parser/xmlparser.xsl b/server/parser/xmlparser.xsl index 4330fec..2ee9370 100644 --- a/server/parser/xmlparser.xsl +++ b/server/parser/xmlparser.xsl @@ -23,6 +23,9 @@ <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> + <!-- Used for iterating CPU topology information --> + <xsl:key name="pkgkey" match="cpu" use="@physical_package_id"/> + <xsl:template match="/rteval"> <xsl:choose> <!-- TABLE: systems --> @@ -132,13 +135,15 @@ <xsl:text>Invalid 'rterid' parameter value: </xsl:text><xsl:value-of select="$rterid"/> </xsl:message> </xsl:if> - <sqldata schemaver="1.2" table="rtevalruns_details"> + <sqldata schemaver="1.4" table="rtevalruns_details"> <fields> <field fid="0">rterid</field> <field fid="1">numa_nodes</field> <field fid="2">num_cpu_cores</field> <field fid="3">num_cpu_sockets</field> <field fid="4">xmldata</field> + <field fid="5">annotation</field> + <field fid="6">cpu_core_spread</field> </fields> <records> <record> @@ -159,12 +164,20 @@ </value> <value fid="4" type="xmlblob"> <rteval_details> - <xsl:copy-of select="clocksource|services|kthreads|network_config|loads|cyclictest/command_line|run_info/annotate"/> + <xsl:copy-of select="clocksource|services|kthreads|network_config|loads|cyclictest/command_line"/> <hardware> <xsl:copy-of select="hardware/memory_size|hardware/cpu_topology"/> </hardware> </rteval_details> </value> + <value fid="5"><xsl:value-of select="run_info/annotate"/></value> + <value fid="6" type="array"> + <xsl:for-each select="hardware/cpu_topology/cpu[generate-id() = generate-id(key('pkgkey', @physical_package_id)[1])]"> + <xsl:call-template name="count_core_spread"> + <xsl:with-param name="pkgid" select="@physical_package_id"/> + </xsl:call-template> + </xsl:for-each> + </value> </record> </records> </sqldata> @@ -288,4 +301,12 @@ <value fid="3"><xsl:value-of select="@value"/></value> </record> </xsl:template> + + <!-- Helper "function" for generating a core per physical socket spread overview --> + <xsl:template name="count_core_spread"> + <xsl:param name="pkgid"/> + <value> + <xsl:value-of select="count(/rteval/hardware/cpu_topology/cpu[@physical_package_id = $pkgid])"/> + </value> + </xsl:template> </xsl:stylesheet> diff --git a/server/rteval-parser.spec b/server/rteval-parser.spec index 3e6dac8..a348b01 100644 --- a/server/rteval-parser.spec +++ b/server/rteval-parser.spec @@ -1,6 +1,6 @@ Name: rteval-parser -Version: 1.3 -%define sqlschemaver 1.2 +Version: 1.5 +%define sqlschemaver 1.4 Release: 1%{?dist} Summary: Report parser daemon for rteval XML-RPC %define pkgname rteval-xmlrpc-%{version} @@ -12,7 +12,7 @@ Source0: %{pkgname}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: postgresql-devel libxml2-devel libxslt-devel -Requires: postgresql httpd mod_python +Requires: postgresql httpd mod_wsgi Requires(post): chkconfig Requires(preun): chkconfig Requires(preun): /sbin/service @@ -87,6 +87,14 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Fri Oct 7 2011 David Sommerseth <dazo@users.sourceforge.net> - 1.5-1 +- Added support for storing data as arrays in PostgreSQL +- Updated SQL schema to store CPU topology/core spread as an array in the database + +* Fri Feb 4 2011 David Sommerseth <dazo@users.sourceforge.net> - 1.4-1 +- Added support for mod_wsgi +- Updated SQL schema, to add rteval annotations to an explicit database column + * Fri Apr 9 2010 David Sommerseth <davids@redhat.com> - 1.3-1 - Updated XML-RPC server, added Hello method diff --git a/server/rteval_xmlrpc.wsgi b/server/rteval_xmlrpc.wsgi new file mode 100644 index 0000000..f782657 --- /dev/null +++ b/server/rteval_xmlrpc.wsgi @@ -0,0 +1,113 @@ +# +# rteval_xmlrpc.wsgi +# XML-RPC handler for the rteval server, using mod_wsgi +# +# Copyright 2011 David Sommerseth <davids@redhat.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# For the avoidance of doubt the "preferred form" of this code is one which +# is in an open unpatent encumbered format. Where cryptographic key signing +# forms part of the process of creating an executable the information +# including keys needed to generate an equivalently functional executable +# are deemed to be part of the source code. +# + +from wsgiref.simple_server import make_server +import types +from xmlrpclib import dumps, loads, Fault +from xmlrpc_API1 import XMLRPC_API1 +from rteval.rtevalConfig import rtevalConfig + +def rtevalXMLRPC_Dispatch(method, args): + # Default configuration + defcfg = {'xmlrpc_server': { 'datadir': './var/lib/rteval', + 'db_server': 'localhost', + 'db_port': 5432, + 'database': 'rteval', + 'db_username': 'rtevxmlrpc', + 'db_password': 'rtevaldb' + } + } + + # Fetch configuration + cfg = rtevalConfig(defcfg) + cfg.Load(append=True) + + # Prepare an object for executing the query + xmlrpc = XMLRPC_API1(config=cfg.GetSection('xmlrpc_server')) + + # Exectute it + result = xmlrpc.Dispatch(method, args) + + # Send the result + if type(result) == types.TupleType: + return dumps(result, None, methodresponse=1) + else: + return dumps((result,), None, methodresponse=1) + + +def rtevalXMLRPC_handler(environ, start_response): + + # the environment variable CONTENT_LENGTH may be empty or missing + try: + request_body_size = int(environ.get('CONTENT_LENGTH', 0)) + except (ValueError): + request_body_size = 0 + + # When the method is POST the query string will be sent + # in the HTTP request body which is passed by the WSGI server + # in the file like wsgi.input environment variable. + try: + if (environ['REQUEST_METHOD'] != 'POST') or (request_body_size < 1): + raise Exception('Error in request') + + request_body = environ['wsgi.input'].read(request_body_size) + try: + args, method = loads(request_body) + except: + raise Exception('Invalid XML-RPC request') + + # Execute the XML-RPC call + status = '200 OK' + cont_type = 'text/xml' + response = [rtevalXMLRPC_Dispatch(method, args)] + except Exception, ex: + status = '500 Internal server error: %s' % str(ex) + cont_type = 'text/plain' + response = [ + '500 Internal server error\n', + 'ERROR: %s' % str(ex) + ] + import traceback, sys + traceback.print_exc(file=sys.stderr) + + response_headers = [('Content-Type', cont_type), + ('Content-Length', str(len("".join(response))))] + start_response(status, response_headers) + return response + + +if __name__ == '__main__': + # + # Simple stand-alone XML-RPC server, if started manually + # Not suitable for production environments, but for testing + # + httpd = make_server('localhost', 65432, rtevalXMLRPC_handler) + try: + httpd.serve_forever() + except KeyboardInterrupt: + print "\nShutting down" + diff --git a/server/rtevaldb.py b/server/rtevaldb.py index 7a0ccaf..e778358 100644 --- a/server/rtevaldb.py +++ b/server/rtevaldb.py @@ -66,9 +66,11 @@ def database_status(config, debug=False, noaction=False): return {"status": "Could not query database pgsql://%s:%s/%s" % (config.db_server, config.db_port, config.database)} + last_rterid = res['records'][0][1] and res['records'][0][1] or "(None)" + last_submid = res['records'][0][1] and res['records'][0][2] or "(None)" return {"status": "OK", "server_time": res['records'][0][0], - "last_rterid": res['records'][0][1], - "last_submid": res['records'][0][2] + "last_rterid": last_rterid, + "last_submid": last_submid } diff --git a/server/sql/delta-1.2_1.3.sql b/server/sql/delta-1.2_1.3.sql new file mode 100644 index 0000000..b869756 --- /dev/null +++ b/server/sql/delta-1.2_1.3.sql @@ -0,0 +1,5 @@ +-- SQL delta update from rteval-1.2.sql to rteval-1.3.sql + +UPDATE rteval_info SET value = '1.3' WHERE key = 'sql_schema_ver'; + +ALTER TABLE rtevalruns_details ADD COLUMN annotation TEXT; diff --git a/server/sql/delta-1.3_1.4.sql b/server/sql/delta-1.3_1.4.sql new file mode 100644 index 0000000..bfa4152 --- /dev/null +++ b/server/sql/delta-1.3_1.4.sql @@ -0,0 +1,5 @@ +-- SQL delta update from rteval-1.3.sql to rteval-1.4.sql + +UPDATE rteval_info SET value = '1.4' WHERE key = 'sql_schema_ver'; + +ALTER TABLE rtevalruns_details ADD COLUMN cpu_core_spread INTEGER[]; diff --git a/server/sql/rteval-1.3.sql b/server/sql/rteval-1.3.sql new file mode 100644 index 0000000..0723cc1 --- /dev/null +++ b/server/sql/rteval-1.3.sql @@ -0,0 +1,206 @@ +-- Create rteval database users +-- +CREATE USER rtevxmlrpc NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb'; +CREATE USER rtevparser NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb_parser'; + +-- Create rteval database +-- +CREATE DATABASE rteval ENCODING 'utf-8'; + +\c rteval + +-- TABLE: rteval_info +-- Contains information the current rteval XML-RPC and parser installation +-- + CREATE TABLE rteval_info ( + key varchar(32) NOT NULL, + value TEXT NOT NULL, + rtiid SERIAL, + PRIMARY KEY(rtiid) + ); + GRANT SELECT ON rteval_info TO rtevparser; + INSERT INTO rteval_info (key, value) VALUES ('sql_schema_ver','1.3'); + +-- Enable plpgsql. It is expected that this PL/pgSQL is available. + CREATE LANGUAGE 'plpgsql'; + +-- FUNCTION: trgfnc_submqueue_notify +-- Trigger function which is called on INSERT queries to the submissionqueue table. +-- It will send a NOTIFY rteval_submq on INSERTs. +-- + CREATE FUNCTION trgfnc_submqueue_notify() RETURNS TRIGGER + AS $BODY$ + DECLARE + BEGIN + NOTIFY rteval_submq; + RETURN NEW; + END + $BODY$ LANGUAGE 'plpgsql'; + + -- The user(s) which are allowed to do INSERT on the submissionqueue + -- must also be allowed to call this trigger function. + GRANT EXECUTE ON FUNCTION trgfnc_submqueue_notify() TO rtevxmlrpc; + +-- TABLE: submissionqueue +-- All XML-RPC clients registers their submissions into this table. Another parser thread +-- will pickup the records where parsestart IS NULL. +-- + CREATE TABLE submissionqueue ( + clientid varchar(128) NOT NULL, + filename VARCHAR(1024) NOT NULL, + status INTEGER DEFAULT '0', + received TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + parsestart TIMESTAMP WITH TIME ZONE, + parseend TIMESTAMP WITH TIME ZONE, + submid SERIAL, + PRIMARY KEY(submid) + ) WITH OIDS; + CREATE INDEX submissionq_status ON submissionqueue(status); + + CREATE TRIGGER trg_submissionqueue AFTER INSERT + ON submissionqueue FOR EACH STATEMENT + EXECUTE PROCEDURE trgfnc_submqueue_notify(); + + GRANT SELECT, INSERT ON submissionqueue TO rtevxmlrpc; + GRANT USAGE ON submissionqueue_submid_seq TO rtevxmlrpc; + GRANT SELECT, UPDATE ON submissionqueue TO rtevparser; + +-- TABLE: systems +-- Overview table over all systems which have sent reports +-- The dmidata column will keep the complete DMIdata available +-- for further information about the system. +-- + CREATE TABLE systems ( + syskey SERIAL NOT NULL, + sysid VARCHAR(64) NOT NULL, + dmidata xml NOT NULL, + PRIMARY KEY(syskey) + ) WITH OIDS; + + GRANT SELECT,INSERT ON systems TO rtevparser; + GRANT USAGE ON systems_syskey_seq TO rtevparser; + +-- TABLE: systems_hostname +-- This table is used to track the hostnames and IP addresses +-- a registered system have used over time +-- + CREATE TABLE systems_hostname ( + syskey INTEGER REFERENCES systems(syskey) NOT NULL, + hostname VARCHAR(256) NOT NULL, + ipaddr cidr + ) WITH OIDS; + CREATE INDEX systems_hostname_syskey ON systems_hostname(syskey); + CREATE INDEX systems_hostname_hostname ON systems_hostname(hostname); + CREATE INDEX systems_hostname_ipaddr ON systems_hostname(ipaddr); + + GRANT SELECT, INSERT ON systems_hostname TO rtevparser; + + +-- TABLE: rtevalruns +-- Overview over all rteval runs, when they were run and how long they ran. +-- + CREATE TABLE rtevalruns ( + rterid SERIAL NOT NULL, -- RTEval Run Id + submid INTEGER REFERENCES submissionqueue(submid) NOT NULL, + syskey INTEGER REFERENCES systems(syskey) NOT NULL, + kernel_ver VARCHAR(32) NOT NULL, + kernel_rt BOOLEAN NOT NULL, + arch VARCHAR(12) NOT NULL, + distro VARCHAR(64), + run_start TIMESTAMP WITH TIME ZONE NOT NULL, + run_duration INTEGER NOT NULL, + load_avg REAL NOT NULL, + version VARCHAR(4), -- Version of rteval + report_filename TEXT, + PRIMARY KEY(rterid) + ) WITH OIDS; + + GRANT SELECT,INSERT ON rtevalruns TO rtevparser; + GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser; + +-- TABLE rtevalruns_details +-- More specific information on the rteval run. The data is stored +-- in XML for flexibility +-- +-- Tags being saved here includes: /rteval/clocksource, /rteval/hardware, +-- /rteval/loads and /rteval/cyclictest/command_line +-- + CREATE TABLE rtevalruns_details ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + annotation TEXT, + num_cpu_cores INTEGER, + num_cpu_sockets INTEGER, + numa_nodes INTEGER, + xmldata xml NOT NULL, + PRIMARY KEY(rterid) + ); + GRANT INSERT ON rtevalruns_details TO rtevparser; + +-- TABLE: cyclic_statistics +-- This table keeps statistics overview over a particular rteval run +-- + CREATE TABLE cyclic_statistics ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + coreid INTEGER, -- NULL=system + priority INTEGER, -- NULL=system + num_samples BIGINT NOT NULL, + lat_min REAL NOT NULL, + lat_max REAL NOT NULL, + lat_mean REAL NOT NULL, + mode REAL NOT NULL, + range REAL NOT NULL, + median REAL NOT NULL, + stddev REAL NOT NULL, + mean_abs_dev REAL NOT NULL, + variance REAL NOT NULL, + cstid SERIAL NOT NULL, -- unique record ID + PRIMARY KEY(cstid) + ) WITH OIDS; + CREATE INDEX cyclic_statistics_rterid ON cyclic_statistics(rterid); + + GRANT INSERT ON cyclic_statistics TO rtevparser; + GRANT USAGE ON cyclic_statistics_cstid_seq TO rtevparser; + +-- TABLE: cyclic_histogram +-- This table keeps the raw histogram data for each rteval run being +-- reported. +-- + CREATE TABLE cyclic_histogram ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + core INTEGER, -- NULL=system + index INTEGER NOT NULL, + value BIGINT NOT NULL + ) WITHOUT OIDS; + CREATE INDEX cyclic_histogram_rterid ON cyclic_histogram(rterid); + + GRANT INSERT ON cyclic_histogram TO rtevparser; + +-- TABLE: cyclic_rawdata +-- This table keeps the raw data for each rteval run being reported. +-- Due to that it will be an enormous amount of data, we avoid using +-- OID on this table. +-- + CREATE TABLE cyclic_rawdata ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + cpu_num INTEGER NOT NULL, + sampleseq INTEGER NOT NULL, + latency REAL NOT NULL + ) WITHOUT OIDS; + CREATE INDEX cyclic_rawdata_rterid ON cyclic_rawdata(rterid); + + GRANT INSERT ON cyclic_rawdata TO rtevparser; + +-- TABLE: notes +-- This table is purely to make notes, connected to different +-- records in the database +-- + CREATE TABLE notes ( + ntid SERIAL NOT NULL, + reftbl CHAR NOT NULL, -- S=systems, R=rtevalruns + refid INTEGER NOT NULL, -- reference id, to the corresponding table + notes TEXT NOT NULL, + createdby VARCHAR(48), + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(ntid) + ) WITH OIDS; + CREATE INDEX notes_refid ON notes(reftbl,refid); diff --git a/server/sql/rteval-1.4.sql b/server/sql/rteval-1.4.sql new file mode 100644 index 0000000..048600c --- /dev/null +++ b/server/sql/rteval-1.4.sql @@ -0,0 +1,207 @@ +-- Create rteval database users +-- +CREATE USER rtevxmlrpc NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb'; +CREATE USER rtevparser NOSUPERUSER ENCRYPTED PASSWORD 'rtevaldb_parser'; + +-- Create rteval database +-- +CREATE DATABASE rteval ENCODING 'utf-8'; + +\c rteval + +-- TABLE: rteval_info +-- Contains information the current rteval XML-RPC and parser installation +-- + CREATE TABLE rteval_info ( + key varchar(32) NOT NULL, + value TEXT NOT NULL, + rtiid SERIAL, + PRIMARY KEY(rtiid) + ); + GRANT SELECT ON rteval_info TO rtevparser; + INSERT INTO rteval_info (key, value) VALUES ('sql_schema_ver','1.4'); + +-- Enable plpgsql. It is expected that this PL/pgSQL is available. + CREATE LANGUAGE 'plpgsql'; + +-- FUNCTION: trgfnc_submqueue_notify +-- Trigger function which is called on INSERT queries to the submissionqueue table. +-- It will send a NOTIFY rteval_submq on INSERTs. +-- + CREATE FUNCTION trgfnc_submqueue_notify() RETURNS TRIGGER + AS $BODY$ + DECLARE + BEGIN + NOTIFY rteval_submq; + RETURN NEW; + END + $BODY$ LANGUAGE 'plpgsql'; + + -- The user(s) which are allowed to do INSERT on the submissionqueue + -- must also be allowed to call this trigger function. + GRANT EXECUTE ON FUNCTION trgfnc_submqueue_notify() TO rtevxmlrpc; + +-- TABLE: submissionqueue +-- All XML-RPC clients registers their submissions into this table. Another parser thread +-- will pickup the records where parsestart IS NULL. +-- + CREATE TABLE submissionqueue ( + clientid varchar(128) NOT NULL, + filename VARCHAR(1024) NOT NULL, + status INTEGER DEFAULT '0', + received TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + parsestart TIMESTAMP WITH TIME ZONE, + parseend TIMESTAMP WITH TIME ZONE, + submid SERIAL, + PRIMARY KEY(submid) + ) WITH OIDS; + CREATE INDEX submissionq_status ON submissionqueue(status); + + CREATE TRIGGER trg_submissionqueue AFTER INSERT + ON submissionqueue FOR EACH STATEMENT + EXECUTE PROCEDURE trgfnc_submqueue_notify(); + + GRANT SELECT, INSERT ON submissionqueue TO rtevxmlrpc; + GRANT USAGE ON submissionqueue_submid_seq TO rtevxmlrpc; + GRANT SELECT, UPDATE ON submissionqueue TO rtevparser; + +-- TABLE: systems +-- Overview table over all systems which have sent reports +-- The dmidata column will keep the complete DMIdata available +-- for further information about the system. +-- + CREATE TABLE systems ( + syskey SERIAL NOT NULL, + sysid VARCHAR(64) NOT NULL, + dmidata xml NOT NULL, + PRIMARY KEY(syskey) + ) WITH OIDS; + + GRANT SELECT,INSERT ON systems TO rtevparser; + GRANT USAGE ON systems_syskey_seq TO rtevparser; + +-- TABLE: systems_hostname +-- This table is used to track the hostnames and IP addresses +-- a registered system have used over time +-- + CREATE TABLE systems_hostname ( + syskey INTEGER REFERENCES systems(syskey) NOT NULL, + hostname VARCHAR(256) NOT NULL, + ipaddr cidr + ) WITH OIDS; + CREATE INDEX systems_hostname_syskey ON systems_hostname(syskey); + CREATE INDEX systems_hostname_hostname ON systems_hostname(hostname); + CREATE INDEX systems_hostname_ipaddr ON systems_hostname(ipaddr); + + GRANT SELECT, INSERT ON systems_hostname TO rtevparser; + + +-- TABLE: rtevalruns +-- Overview over all rteval runs, when they were run and how long they ran. +-- + CREATE TABLE rtevalruns ( + rterid SERIAL NOT NULL, -- RTEval Run Id + submid INTEGER REFERENCES submissionqueue(submid) NOT NULL, + syskey INTEGER REFERENCES systems(syskey) NOT NULL, + kernel_ver VARCHAR(32) NOT NULL, + kernel_rt BOOLEAN NOT NULL, + arch VARCHAR(12) NOT NULL, + distro VARCHAR(64), + run_start TIMESTAMP WITH TIME ZONE NOT NULL, + run_duration INTEGER NOT NULL, + load_avg REAL NOT NULL, + version VARCHAR(4), -- Version of rteval + report_filename TEXT, + PRIMARY KEY(rterid) + ) WITH OIDS; + + GRANT SELECT,INSERT ON rtevalruns TO rtevparser; + GRANT USAGE ON rtevalruns_rterid_seq TO rtevparser; + +-- TABLE rtevalruns_details +-- More specific information on the rteval run. The data is stored +-- in XML for flexibility +-- +-- Tags being saved here includes: /rteval/clocksource, /rteval/hardware, +-- /rteval/loads and /rteval/cyclictest/command_line +-- + CREATE TABLE rtevalruns_details ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + annotation TEXT, + num_cpu_cores INTEGER, + num_cpu_sockets INTEGER, + cpu_core_spread INTEGER[], + numa_nodes INTEGER, + xmldata xml NOT NULL, + PRIMARY KEY(rterid) + ); + GRANT INSERT ON rtevalruns_details TO rtevparser; + +-- TABLE: cyclic_statistics +-- This table keeps statistics overview over a particular rteval run +-- + CREATE TABLE cyclic_statistics ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + coreid INTEGER, -- NULL=system + priority INTEGER, -- NULL=system + num_samples BIGINT NOT NULL, + lat_min REAL NOT NULL, + lat_max REAL NOT NULL, + lat_mean REAL NOT NULL, + mode REAL NOT NULL, + range REAL NOT NULL, + median REAL NOT NULL, + stddev REAL NOT NULL, + mean_abs_dev REAL NOT NULL, + variance REAL NOT NULL, + cstid SERIAL NOT NULL, -- unique record ID + PRIMARY KEY(cstid) + ) WITH OIDS; + CREATE INDEX cyclic_statistics_rterid ON cyclic_statistics(rterid); + + GRANT INSERT ON cyclic_statistics TO rtevparser; + GRANT USAGE ON cyclic_statistics_cstid_seq TO rtevparser; + +-- TABLE: cyclic_histogram +-- This table keeps the raw histogram data for each rteval run being +-- reported. +-- + CREATE TABLE cyclic_histogram ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + core INTEGER, -- NULL=system + index INTEGER NOT NULL, + value BIGINT NOT NULL + ) WITHOUT OIDS; + CREATE INDEX cyclic_histogram_rterid ON cyclic_histogram(rterid); + + GRANT INSERT ON cyclic_histogram TO rtevparser; + +-- TABLE: cyclic_rawdata +-- This table keeps the raw data for each rteval run being reported. +-- Due to that it will be an enormous amount of data, we avoid using +-- OID on this table. +-- + CREATE TABLE cyclic_rawdata ( + rterid INTEGER REFERENCES rtevalruns(rterid) NOT NULL, + cpu_num INTEGER NOT NULL, + sampleseq INTEGER NOT NULL, + latency REAL NOT NULL + ) WITHOUT OIDS; + CREATE INDEX cyclic_rawdata_rterid ON cyclic_rawdata(rterid); + + GRANT INSERT ON cyclic_rawdata TO rtevparser; + +-- TABLE: notes +-- This table is purely to make notes, connected to different +-- records in the database +-- + CREATE TABLE notes ( + ntid SERIAL NOT NULL, + reftbl CHAR NOT NULL, -- S=systems, R=rtevalruns + refid INTEGER NOT NULL, -- reference id, to the corresponding table + notes TEXT NOT NULL, + createdby VARCHAR(48), + created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY(ntid) + ) WITH OIDS; + CREATE INDEX notes_refid ON notes(reftbl,refid); diff --git a/server/xmlrpc_API1.py b/server/xmlrpc_API1.py index 71e23d6..064f1b2 100644 --- a/server/xmlrpc_API1.py +++ b/server/xmlrpc_API1.py @@ -45,6 +45,8 @@ class XMLRPC_API1(): def __mkdatadir(self, dirpath): startdir = os.getcwd() + if dirpath[0] == '/': + os.chdir('/') for dir in dirpath.split("/"): if dir is '': continue |