Add rudimentary support for ido_mysql.

This commit is contained in:
Gunnar Beutner 2013-07-17 11:29:51 +02:00
parent ad83a51547
commit e66c36ec9e
14 changed files with 470 additions and 15 deletions

View File

@ -22,7 +22,6 @@
#include "base/dynamictype.h"
#include "ido/dbconnection.h"
#include <vector>
namespace icinga
{

View File

@ -17,11 +17,13 @@ libido_mysql_la_SOURCES = \
libido_mysql_la_CPPFLAGS = \
$(LTDLINCL) \
$(BOOST_CPPFLAGS) \
$(MYSQL_CFLAGS) \
-I${top_srcdir}/lib \
-I${top_srcdir}/components
libido_mysql_la_LDFLAGS = \
$(BOOST_LDFLAGS) \
$(MYSQL_LDFLAGS) \
-module \
-no-undefined \
@RELEASE_INFO@ \

View File

@ -25,4 +25,7 @@ type MysqlDbConnection {
%attribute string "password",
%attribute string "database",
%attribute string "instance_name",
%attribute string "instance_description"
}

View File

@ -17,7 +17,13 @@
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
******************************************************************************/
#include "mysqldbconnection.h"
#include "base/logger_fwd.h"
#include "base/objectlock.h"
#include "ido/dbtype.h"
#include "ido_mysql/mysqldbconnection.h"
#include <boost/tuple/tuple.hpp>
#include <boost/smart_ptr/make_shared.hpp>
#include <boost/foreach.hpp>
using namespace icinga;
@ -31,8 +37,219 @@ MysqlDbConnection::MysqlDbConnection(const Dictionary::Ptr& serializedUpdate)
RegisterAttribute("user", Attribute_Config, &m_User);
RegisterAttribute("password", Attribute_Config, &m_Password);
RegisterAttribute("database", Attribute_Config, &m_Database);
RegisterAttribute("instance_name", Attribute_Config, &m_InstanceName);
RegisterAttribute("instance_description", Attribute_Config, &m_InstanceDescription);
/* TODO: move this to a timer so we can periodically check if we're still connected - and reconnect if necessary */
String ihost, iuser, ipasswd, idb;
const char *host, *user , *passwd, *db;
long port;
ihost = m_Host;
iuser = m_User;
ipasswd = m_Password;
idb = m_Database;
host = (!ihost.IsEmpty()) ? ihost.CStr() : NULL;
port = m_Port;
user = (!iuser.IsEmpty()) ? iuser.CStr() : NULL;
passwd = (!ipasswd.IsEmpty()) ? ipasswd.CStr() : NULL;
db = (!idb.IsEmpty()) ? idb.CStr() : NULL;
{
boost::mutex::scoped_lock lock(m_ConnectionMutex);
if (!mysql_init(&m_Connection))
BOOST_THROW_EXCEPTION(std::bad_alloc());
if (!mysql_real_connect(&m_Connection, host, user, passwd, db, port, NULL, 0))
BOOST_THROW_EXCEPTION(std::runtime_error(mysql_error(&m_Connection)));
String instanceName = "default";
if (!m_InstanceName.IsEmpty())
instanceName = m_InstanceName;
Array::Ptr rows = Query("SELECT instance_id FROM icinga_instances WHERE instance_name = '" + Escape(instanceName) + "'");
if (rows->GetLength() == 0) {
Query("INSERT INTO icinga_instances (instance_name, instance_description) VALUES ('" + Escape(instanceName) + "', '" + m_InstanceDescription + "')");
m_InstanceID = GetInsertId();
} else {
Dictionary::Ptr row = rows->Get(0);
m_InstanceID = DbReference(row->Get("instance_id"));
}
std::ostringstream msgbuf;
msgbuf << "MySQL IDO instance id: " << static_cast<long>(m_InstanceID);
Log(LogInformation, "ido_mysql", msgbuf.str());
Query("UPDATE icinga_objects SET is_active = 0");
std::ostringstream qbuf;
qbuf << "SELECT objecttype_id, name1, name2 FROM icinga_objects WHERE instance_id = " << static_cast<long>(m_InstanceID);
rows = Query(qbuf.str());
ObjectLock olock(rows);
BOOST_FOREACH(const Dictionary::Ptr& row, rows) {
DbType::Ptr dbtype = DbType::GetById(row->Get("objecttype_id"));
if (!dbtype)
continue;
dbtype->GetOrCreateObjectByName(row->Get("name1"), row->Get("name2"));
}
}
UpdateAllObjects();
}
void MysqlDbConnection::Stop(void)
{
boost::mutex::scoped_lock lock(m_ConnectionMutex);
mysql_close(&m_Connection);
}
Array::Ptr MysqlDbConnection::Query(const String& query)
{
if (mysql_query(&m_Connection, query.CStr()) != 0)
BOOST_THROW_EXCEPTION(std::runtime_error(mysql_error(&m_Connection)));
MYSQL_RES *result = mysql_store_result(&m_Connection);
if (!result) {
if (mysql_field_count(&m_Connection) > 0)
BOOST_THROW_EXCEPTION(std::runtime_error(mysql_error(&m_Connection)));
return Array::Ptr();
}
Array::Ptr rows = boost::make_shared<Array>();
for (;;) {
Dictionary::Ptr row = FetchRow(result);
if (!row)
break;
rows->Add(row);
}
mysql_free_result(result);
return rows;
}
DbReference MysqlDbConnection::GetInsertId(void)
{
return DbReference(mysql_insert_id(&m_Connection));
}
String MysqlDbConnection::Escape(const String& s)
{
ssize_t length = s.GetLength();
char *to = new char[s.GetLength() * 2 + 1];
mysql_real_escape_string(&m_Connection, to, s.CStr(), length);
return String(to);
}
Dictionary::Ptr MysqlDbConnection::FetchRow(MYSQL_RES *result)
{
MYSQL_ROW row;
MYSQL_FIELD *field;
unsigned long *lengths, i;
row = mysql_fetch_row(result);
if (!row)
return Dictionary::Ptr();
lengths = mysql_fetch_lengths(result);
if (!lengths)
return Dictionary::Ptr();
Dictionary::Ptr dict = boost::make_shared<Dictionary>();
mysql_field_seek(result, 0);
for (field = mysql_fetch_field(result), i = 0; field; field = mysql_fetch_field(result), i++) {
Value value;
if (field) {
value = String(row[i], row[i] + lengths[i]);
}
dict->Set(field->name, value);
}
return dict;
}
void MysqlDbConnection::UpdateObject(const DbObject::Ptr& dbobj, DbUpdateType kind) {
DbReference dbref = GetReference(dbobj);
}
if (kind == DbObjectRemoved) {
if (!dbref.IsValid())
return;
std::ostringstream qbuf;
qbuf << "DELETE FROM icinga_" << dbobj->GetType()->GetTable() << "s WHERE id = " << static_cast<long>(dbref);
Log(LogWarning, "ido_mysql", "Query: " + qbuf.str());
}
std::ostringstream q1buf;
if (!dbref.IsValid()) {
q1buf << "INSERT INTO icinga_objects (instance_id, objecttype_id, name1, name2, is_active) VALUES (";
} else {
q1buf << "UPDATE icinga_objects SET is_active = 1 WHERE object_id = " << static_cast<long>(dbref);
}
Dictionary::Ptr cols = boost::make_shared<Dictionary>();
Dictionary::Ptr fields = dbobj->GetFields();
if (!fields)
return;
ObjectLock olock(fields);
String key;
Value value;
BOOST_FOREACH(boost::tie(key, value), fields) {
if (value.IsObjectType<DynamicObject>()) {
DbObject::Ptr dbobjcol = DbObject::GetOrCreateByObject(value);
if (!dbobjcol)
return;
DbReference dbrefcol = GetReference(dbobjcol);
if (!dbrefcol.IsValid()) {
UpdateObject(dbobjcol, DbObjectCreated);
dbrefcol = GetReference(dbobjcol);
if (!dbrefcol.IsValid())
return;
}
value = static_cast<long>(dbrefcol);
}
cols->Set(key, value);
}
std::ostringstream q2buf;
if (dbref.IsValid()) {
q2buf << "UPDATE icinga_" << dbobj->GetType()->GetTable() << "s SET xxx WHERE id = " << static_cast<long>(dbref);
} else {
q2buf << "INSERT INTO icinga_" << dbobj->GetType()->GetTable() << "s xxx";
}
Log(LogWarning, "ido_mysql", "Query: " + q2buf.str());
}

View File

@ -20,9 +20,9 @@
#ifndef MYSQLDBCONNECTION_H
#define MYSQLDBCONNECTION_H
#include "base/array.h"
#include "base/dynamictype.h"
#include "ido/dbconnection.h"
#include <vector>
#include <mysql/mysql.h>
namespace icinga
@ -40,6 +40,7 @@ public:
typedef weak_ptr<MysqlDbConnection> WeakPtr;
MysqlDbConnection(const Dictionary::Ptr& serializedUpdate);
virtual void Stop(void);
virtual void UpdateObject(const DbObject::Ptr& dbobj, DbUpdateType kind);
@ -49,8 +50,18 @@ private:
Attribute<String> m_User;
Attribute<String> m_Password;
Attribute<String> m_Database;
Attribute<String> m_InstanceName;
Attribute<String> m_InstanceDescription;
MYSQL *m_Connection;
DbReference m_InstanceID;
boost::mutex m_ConnectionMutex;
MYSQL m_Connection;
Array::Ptr Query(const String& query);
DbReference GetInsertId(void);
String Escape(const String& s);
Dictionary::Ptr FetchRow(MYSQL_RES *result);
};
}

View File

@ -63,6 +63,7 @@ AX_BOOST_THREAD
AX_BOOST_SYSTEM
AX_BOOST_UNIT_TEST_FRAMEWORK
AX_BOOST_PROGRAM_OPTIONS
AX_LIB_MYSQL([5.0])
AX_CHECK_OPENSSL([], [AC_MSG_ERROR([You need the OpenSSL headers and libraries in order to build this application])])
AC_CHECK_LIB(ssl, SSL_new)
AC_CHECK_LIB(crypto, X509_NAME_oneline)

View File

@ -29,7 +29,7 @@ DbConnection::DbConnection(const Dictionary::Ptr& serializedUpdate)
void DbConnection::Initialize(void)
{
DbObject::OnObjectUpdated.connect(boost::bind(&DbConnection::UpdateObject, this, _1, _2));
DbObject::OnObjectUpdated.connect(boost::bind(&DbConnection::InternalUpdateObject, this, _1, _2));
}
void DbConnection::SetReference(const DbObject::Ptr& dbobj, const DbReference& dbref)
@ -57,6 +57,14 @@ void DbConnection::UpdateObject(const DbObject::Ptr&, DbUpdateType)
/* Default handler does nothing. */
}
void DbConnection::InternalUpdateObject(const DbObject::Ptr& dbobj, DbUpdateType kind)
{
UpdateObject(dbobj, kind);
if (kind == DbObjectRemoved)
SetReference(dbobj, DbReference());
}
void DbConnection::UpdateAllObjects(void)
{
DynamicType::Ptr type;

View File

@ -48,6 +48,7 @@ protected:
private:
void Initialize(void);
void InternalUpdateObject(const DbObject::Ptr& dbobj, DbUpdateType kind);
std::map<DbObject::Ptr, DbReference> m_References;
};

View File

@ -19,6 +19,7 @@
#include "ido/dbobject.h"
#include "ido/dbtype.h"
#include "icinga/service.h"
#include "base/dynamictype.h"
#include "base/objectlock.h"
#include <boost/foreach.hpp>
@ -27,8 +28,8 @@ using namespace icinga;
boost::signals2::signal<void (const DbObject::Ptr&, DbUpdateType)> DbObject::OnObjectUpdated;
DbObject::DbObject(const String& name1, const String& name2)
: m_Name1(name1), m_Name2(name2), m_Object()
DbObject::DbObject(const shared_ptr<DbType>& type, const String& name1, const String& name2)
: m_Name1(name1), m_Name2(name2), m_Type(type)
{ }
void DbObject::StaticInitialize(void)
@ -59,6 +60,16 @@ String DbObject::GetName2(void) const
return m_Name2;
}
DbType::Ptr DbObject::GetType(void) const
{
return m_Type;
}
void DbObject::SendUpdate(DbUpdateType kind)
{
OnObjectUpdated(GetSelf(), kind);
}
DbObject::Ptr DbObject::GetOrCreateByObject(const DynamicObject::Ptr& object)
{
DbObject::Ptr dbobj = static_pointer_cast<DbObject>(object->GetExtension("DbObject"));
@ -71,7 +82,23 @@ DbObject::Ptr DbObject::GetOrCreateByObject(const DynamicObject::Ptr& object)
if (!dbtype)
return DbObject::Ptr();
/* TODO: Deal with service names. */
Service::Ptr service;
String name1, name2;
service = dynamic_pointer_cast<Service>(object);
if (service) {
Host::Ptr host = service->GetHost();
if (!host)
return DbObject::Ptr();
name1 = service->GetHost()->GetName();
name2 = service->GetShortName();
} else {
name1 = object->GetName();
}
dbobj = dbtype->GetOrCreateObjectByName(object->GetName(), String());
{
@ -86,13 +113,21 @@ DbObject::Ptr DbObject::GetOrCreateByObject(const DynamicObject::Ptr& object)
void DbObject::ObjectRegisteredHandler(const DynamicObject::Ptr& object)
{
DbObject::Ptr dbobj = GetOrCreateByObject(object);
OnObjectUpdated(dbobj, DbObjectCreated);
if (!dbobj)
return;
dbobj->SendUpdate(DbObjectCreated);
}
void DbObject::ObjectUnregisteredHandler(const DynamicObject::Ptr& object)
{
DbObject::Ptr dbobj = GetOrCreateByObject(object);
OnObjectUpdated(dbobj, DbObjectRemoved);
if (!dbobj)
return;
dbobj->SendUpdate(DbObjectRemoved);
{
ObjectLock olock(object);
@ -115,5 +150,9 @@ void DbObject::TransactionClosingHandler(double tx, const std::set<DynamicObject
void DbObject::FlushObjectHandler(double tx, const DynamicObject::Ptr& object)
{
DbObject::Ptr dbobj = GetOrCreateByObject(object);
OnObjectUpdated(dbobj, DbObjectUpdated);
if (!dbobj)
return;
dbobj->SendUpdate();
}

View File

@ -34,6 +34,8 @@ enum DbUpdateType
DbObjectRemoved
};
class DbType;
/**
* A database object.
*
@ -49,6 +51,7 @@ public:
String GetName1(void) const;
String GetName2(void) const;
boost::shared_ptr<DbType> GetType(void) const;
virtual Dictionary::Ptr GetFields(void) const = 0;
@ -56,12 +59,15 @@ public:
static boost::signals2::signal<void (const DbObject::Ptr&, DbUpdateType)> OnObjectUpdated;
void SendUpdate(DbUpdateType kind = DbObjectUpdated);
protected:
DbObject(const String& name1, const String& name2);
DbObject(const boost::shared_ptr<DbType>& type, const String& name1, const String& name2);
private:
String m_Name1;
String m_Name2;
boost::shared_ptr<DbType> m_Type;
DynamicObject::Ptr m_Object;
friend boost::shared_ptr<DbObject> boost::make_shared<>(const icinga::String&, const icinga::String&);

View File

@ -20,6 +20,8 @@
#include "ido/dbtype.h"
#include "base/objectlock.h"
#include <boost/thread/once.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/foreach.hpp>
using namespace icinga;
@ -64,6 +66,18 @@ DbType::Ptr DbType::GetByName(const String& name)
return it->second;
}
DbType::Ptr DbType::GetById(long tid)
{
String name;
DbType::Ptr type;
BOOST_FOREACH(boost::tie(name, type), GetTypes()) {
if (type->GetTypeId() == tid)
return type;
}
return DbType::Ptr();
}
DbObject::Ptr DbType::GetOrCreateObjectByName(const String& name1, const String& name2)
{
ASSERT(!OwnsLock());

View File

@ -50,6 +50,7 @@ public:
static void RegisterType(const DbType::Ptr& type);
static DbType::Ptr GetByName(const String& name);
static DbType::Ptr GetById(long tid);
DbObject::Ptr GetOrCreateObjectByName(const String& name1, const String& name2);

View File

@ -19,16 +19,22 @@
#include "ido/hostdbobject.h"
#include "ido/dbtype.h"
#include "icinga/host.h"
using namespace icinga;
REGISTER_DBTYPE("Host", "hosts", 1, HostDbObject);
REGISTER_DBTYPE("Host", "host", 1, HostDbObject);
HostDbObject::HostDbObject(const String& name1, const String& name2)
: DbObject(name1, name2)
: DbObject(DbType::GetByName("Host"), name1, name2)
{ }
Dictionary::Ptr HostDbObject::GetFields(void) const
{
Dictionary::Ptr fields = boost::make_shared<Dictionary>();
Host::Ptr host = static_pointer_cast<Host>(GetObject());
fields->Set("display_name", host->GetDisplayName());
return fields;
}

147
m4/ax_lib_mysql.m4 Normal file
View File

@ -0,0 +1,147 @@
# ===========================================================================
# http://www.gnu.org/software/autoconf-archive/ax_lib_mysql.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_LIB_MYSQL([MINIMUM-VERSION])
#
# DESCRIPTION
#
# This macro provides tests of availability of MySQL client library of
# particular version or newer.
#
# AX_LIB_MYSQL macro takes only one argument which is optional. If there
# is no required version passed, then macro does not run version test.
#
# The --with-mysql option takes one of three possible values:
#
# no - do not check for MySQL client library
#
# yes - do check for MySQL library in standard locations (mysql_config
# should be in the PATH)
#
# path - complete path to mysql_config utility, use this option if
# mysql_config can't be found in the PATH
#
# This macro calls:
#
# AC_SUBST(MYSQL_CFLAGS)
# AC_SUBST(MYSQL_LDFLAGS)
# AC_SUBST(MYSQL_VERSION)
#
# And sets:
#
# HAVE_MYSQL
#
# LICENSE
#
# Copyright (c) 2008 Mateusz Loskot <mateusz@loskot.net>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 12
AC_DEFUN([AX_LIB_MYSQL],
[
AC_ARG_WITH([mysql],
AS_HELP_STRING([--with-mysql=@<:@ARG@:>@],
[use MySQL client library @<:@default=yes@:>@, optionally specify path to mysql_config]
),
[
if test "$withval" = "no"; then
want_mysql="no"
elif test "$withval" = "yes"; then
want_mysql="yes"
else
want_mysql="yes"
MYSQL_CONFIG="$withval"
fi
],
[want_mysql="yes"]
)
AC_ARG_VAR([MYSQL_CONFIG], [Full path to mysql_config program])
MYSQL_CFLAGS=""
MYSQL_LDFLAGS=""
MYSQL_VERSION=""
dnl
dnl Check MySQL libraries
dnl
if test "$want_mysql" = "yes"; then
if test -z "$MYSQL_CONFIG" ; then
AC_PATH_PROGS([MYSQL_CONFIG], [mysql_config mysql_config5], [no])
fi
if test "$MYSQL_CONFIG" != "no"; then
MYSQL_CFLAGS="`$MYSQL_CONFIG --cflags`"
MYSQL_LDFLAGS="`$MYSQL_CONFIG --libs`"
MYSQL_VERSION=`$MYSQL_CONFIG --version`
found_mysql="yes"
else
found_mysql="no"
fi
fi
dnl
dnl Check if required version of MySQL is available
dnl
mysql_version_req=ifelse([$1], [], [], [$1])
if test "$found_mysql" = "yes" -a -n "$mysql_version_req"; then
AC_MSG_CHECKING([if MySQL version is >= $mysql_version_req])
dnl Decompose required version string of MySQL
dnl and calculate its number representation
mysql_version_req_major=`expr $mysql_version_req : '\([[0-9]]*\)'`
mysql_version_req_minor=`expr $mysql_version_req : '[[0-9]]*\.\([[0-9]]*\)'`
mysql_version_req_micro=`expr $mysql_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
if test "x$mysql_version_req_micro" = "x"; then
mysql_version_req_micro="0"
fi
mysql_version_req_number=`expr $mysql_version_req_major \* 1000000 \
\+ $mysql_version_req_minor \* 1000 \
\+ $mysql_version_req_micro`
dnl Decompose version string of installed MySQL
dnl and calculate its number representation
mysql_version_major=`expr $MYSQL_VERSION : '\([[0-9]]*\)'`
mysql_version_minor=`expr $MYSQL_VERSION : '[[0-9]]*\.\([[0-9]]*\)'`
mysql_version_micro=`expr $MYSQL_VERSION : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
if test "x$mysql_version_micro" = "x"; then
mysql_version_micro="0"
fi
mysql_version_number=`expr $mysql_version_major \* 1000000 \
\+ $mysql_version_minor \* 1000 \
\+ $mysql_version_micro`
mysql_version_check=`expr $mysql_version_number \>\= $mysql_version_req_number`
if test "$mysql_version_check" = "1"; then
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
fi
fi
if test "$found_mysql" = "yes" ; then
AC_DEFINE([HAVE_MYSQL], [1],
[Define to 1 if MySQL libraries are available])
fi
AC_SUBST([MYSQL_VERSION])
AC_SUBST([MYSQL_CFLAGS])
AC_SUBST([MYSQL_LDFLAGS])
])