From 59658fe43a8134a6d89f29b5e88f0d72f0a157f9 Mon Sep 17 00:00:00 2001 From: Tim Simpson Date: Fri, 25 May 2012 12:27:11 -0500 Subject: [PATCH] Made Sneaky Pete work with RDL as far as prepare call and most user ops. * Ripped out nova::db::api stuff, which mimicked Nova API in Diablo. * Added nova::guest::HeartBeat class which works with RDLs slimmed down db. * Added "guest_id" property to FlagValues class. * Removed guest_ethernet_device and preset_instance_id props from FlagValues. * Fixed MySqlAppStatus to work with RDL. Mostly changed db names, and made it handle the "NEW" status. * Added == operator to IsoDateTime (needed it for a HeartBeat unit test...). * Changed nova::rpc::Receiver to send ack in the new nicer way. * Log out Sneaky Pete intro flag to program start up to make it easier to see in the logs. --- Jamroot.jam | 15 +- copy-to-guest.sh | 4 +- src/nova/db/api.cc | 128 ----------------- src/nova/db/api.h | 50 ------- src/nova/flags.cc | 10 +- src/nova/flags.h | 4 +- src/nova/guest/HeartBeat.cc | 50 +++++++ src/nova/guest/HeartBeat.h | 32 +++++ src/nova/guest/mysql/MySqlAppStatus.cc | 65 +++------ src/nova/guest/mysql/MySqlAppStatus.h | 19 +-- src/nova/guest/mysql/MySqlMessageHandler.cc | 12 +- src/nova/guest/utils.cc | 10 +- src/nova/guest/utils.h | 6 + src/nova/rpc/Receiver.cc | 7 +- src/receiver_daemon.cc | 69 +++++---- tests/nova/db/api_tests.cc | 130 ----------------- tests/nova/guest/HeartBeat_tests.cc | 135 ++++++++++++++++++ .../nova/guest/mysql/MySqlAppStatus_tests.cc | 11 +- 18 files changed, 321 insertions(+), 436 deletions(-) delete mode 100644 src/nova/db/api.cc delete mode 100644 src/nova/db/api.h create mode 100644 src/nova/guest/HeartBeat.cc create mode 100644 src/nova/guest/HeartBeat.h delete mode 100644 tests/nova/db/api_tests.cc create mode 100644 tests/nova/guest/HeartBeat_tests.cc diff --git a/Jamroot.jam b/Jamroot.jam index 1dad7cd..b74ffc9 100644 --- a/Jamroot.jam +++ b/Jamroot.jam @@ -258,8 +258,8 @@ unit u_nova_guest_utils : u_nova_guest_GuestException ; -unit u_nova_db_api - : src/nova/db/api.cc +unit u_nova_guest_HeartBeat + : src/nova/guest/HeartBeat.cc : u_nova_db_mysql u_nova_guest_utils ; @@ -408,7 +408,7 @@ unit u_nova_guest_diagnostics_InterrogatorMessageHandler ; alias guest_lib - : u_nova_db_api + : u_nova_guest_HeartBeat u_nova_configfile u_nova_flags u_nova_guest_apt_apt @@ -491,18 +491,17 @@ unit-test mysql_nova_updater_tests : BOOST_TEST_DYN_LINK "BOOST_TEST_CATCH_SYSTEM_ERRORS=no valgrind --leak-check=full" ; -explicit api_tests ; -unit-test api_tests +unit-test HeartBeat_tests : u_nova_flags - u_nova_db_api + u_nova_guest_HeartBeat test_dependencies - tests/nova/db/api_tests.cc + tests/nova/guest/HeartBeat_tests.cc : BOOST_TEST_DYN_LINK "BOOST_TEST_CATCH_SYSTEM_ERRORS=no valgrind --leak-check=full" ; -explicit api_tests ; +explicit HeartBeat_tests ; # Requires that mysql be utterly destroyed and reinstalled on the machine. diff --git a/copy-to-guest.sh b/copy-to-guest.sh index dbcb24d..73ea640 100755 --- a/copy-to-guest.sh +++ b/copy-to-guest.sh @@ -1,2 +1,2 @@ -mkdir /vz/private/$1/agent -sudo cp -rf * /vz/private/$1/agent/ +mkdir /var/lib/vz/private/$1/agent +sudo cp -rf * /var/lib/vz/private/$1/agent/ diff --git a/src/nova/db/api.cc b/src/nova/db/api.cc deleted file mode 100644 index 0ac6a58..0000000 --- a/src/nova/db/api.cc +++ /dev/null @@ -1,128 +0,0 @@ -#include "nova/db/api.h" -#include "nova/flags.h" -#include "nova/Log.h" -#include "nova/db/mysql.h" -#include -#include -#include "nova/guest/utils.h" - -using nova::flags::FlagValues; -using namespace nova::db::mysql; -using boost::optional; -using nova::guest::utils::IsoDateTime; -using std::string; -using std::stringstream; - -namespace nova { namespace db { - - -class ApiMySql : public Api { - -private: - MySqlConnectionWithDefaultDbPtr con; - -public: - ApiMySql(MySqlConnectionWithDefaultDbPtr con) - : con(con) - { - } - - virtual ~ApiMySql() {} - - void ensure() { - con->ensure(); - } - - virtual ServicePtr service_create(const NewService & new_service) { - ServicePtr service = service_get_by_args(new_service); - if (!!service) { - return service; - } - ensure(); - MySqlPreparedStatementPtr stmt = con->prepare_statement( - "INSERT INTO services " - "(created_at, updated_at, deleted, report_count, disabled, " - " availability_zone, services.binary, host, topic) " - "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?);"); - IsoDateTime now; - stmt->set_string(0, now.c_str()); - stmt->set_string(1, now.c_str()); - stmt->set_int(2, 0); - stmt->set_int(3, 0); - stmt->set_int(4, 0); - stmt->set_string(5, new_service.availability_zone.c_str()); - stmt->set_string(6, new_service.binary.c_str()); - stmt->set_string(7, new_service.host.c_str()); - stmt->set_string(8, new_service.topic.c_str()); - stmt->execute(); - return service_get_by_args(new_service); - } - - virtual ServicePtr service_get_by_args(const NewService & search) { - ensure(); - //TODO(tim.simpson): Get the preparedstatement result set to work with - // int return values. - stringstream query; - query << "SELECT disabled, id, report_count " - "FROM services WHERE services.binary= \"" - << con->escape_string(search.binary.c_str()) - << "\" AND host= \"" << con->escape_string(search.host.c_str()) - << "\" AND services.topic = \"" - << con->escape_string(search.topic.c_str()) - << "\" AND availability_zone = \"" - << con->escape_string(search.availability_zone.c_str()) << "\""; - MySqlResultSetPtr results = con->query(query.str().c_str()); - if (!results->next()) { - return ServicePtr(); - } else { - ServicePtr service(new Service()); - service->availability_zone = search.availability_zone; - service->binary = search.binary; - optional d_int = results->get_int(0); - if (d_int) { - service->disabled = (d_int.get() != 0); - } else { - service->disabled = boost::none; - } - service->host = search.host; - service->id = results->get_int_non_null(1); - service->report_count = results->get_int_non_null(2); - service->topic = search.topic; - return service; - } - } - - virtual void service_update(Service & service) { - ensure(); - stringstream query; - query << "UPDATE services " - "SET updated_at = ? , report_count = ? "; - if (service.disabled) { - query << ", disabled = ? "; - } - query << "WHERE services.binary= ? AND host= ? " - "AND services.topic = ? AND availability_zone = ?"; - MySqlPreparedStatementPtr stmt = con->prepare_statement( - query.str().c_str()); - IsoDateTime now; - int index = 0; - stmt->set_string(index ++, now.c_str()); - stmt->set_int(index ++, service.report_count); - if (service.disabled) { - stmt->set_int(index ++, service.disabled.get() ? 1 : 0); - } - stmt->set_string(index ++, service.binary.c_str()); - stmt->set_string(index ++, service.host.c_str()); - stmt->set_string(index ++, service.topic.c_str()); - stmt->set_string(index ++, service.availability_zone.c_str()); - stmt->execute(); - } - -}; - -ApiPtr create_api(MySqlConnectionWithDefaultDbPtr con) { - ApiPtr ptr(new ApiMySql(con)); - return ptr; -} - -} } // end nova::db diff --git a/src/nova/db/api.h b/src/nova/db/api.h deleted file mode 100644 index 0867083..0000000 --- a/src/nova/db/api.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __NOVA_DB_API -#define __NOVA_DB_API - -#include "nova/flags.h" -#include "nova/db/mysql.h" -#include -#include -#include - - -namespace nova { namespace db { - -struct NewService { - std::string availability_zone; - std::string binary; - std::string host; - std::string topic; -}; - -struct Service : public NewService { - int id; - boost::optional disabled; - int report_count; -}; - -typedef boost::shared_ptr ServicePtr; - - -class Api { - -public: - virtual ~Api() {} - - virtual ServicePtr service_create(const NewService & new_service) = 0; - - virtual ServicePtr service_get_by_args(const NewService & new_service) = 0; - - virtual void service_update(Service & service) = 0; - -}; - -typedef boost::shared_ptr ApiPtr; - - -ApiPtr create_api(nova::db::mysql::MySqlConnectionWithDefaultDbPtr con); - - -}} // nova::db - -#endif diff --git a/src/nova/flags.cc b/src/nova/flags.cc index 7bafb65..3ec7f0c 100644 --- a/src/nova/flags.cc +++ b/src/nova/flags.cc @@ -121,7 +121,7 @@ void FlagMap::add_from_file(const char * file_path) { } FlagMapPtr FlagMap::create_from_args(size_t count, char** argv, - bool ignore_mismatch) { + bool ignore_mismatch) { FlagMapPtr flags(new FlagMap()); for (size_t i = 0; i < count; i ++) { flags->add_from_arg(argv[i], ignore_mismatch); @@ -269,8 +269,8 @@ const char * FlagValues::db_backend() const { return map->get("db_backend", "sqlalchemy"); } -const char * FlagValues::guest_ethernet_device() const { - return map->get("guest_ethernet_device", "eth0"); +const char * FlagValues::guest_id() const { + return map->get("guest_id"); } optional FlagValues::host() const { @@ -336,10 +336,6 @@ unsigned long FlagValues::periodic_interval() const { return get_flag_value(*map, "periodic_interval", (unsigned long) 60); } -optional FlagValues::preset_instance_id() const { - return get_flag_value(*map, "preset_instance_id"); -} - size_t FlagValues::rabbit_client_memory() const { return get_flag_value(*map, "rabbit_client_memory", (size_t) 4096); } diff --git a/src/nova/flags.h b/src/nova/flags.h index 364ca1a..6e4072c 100644 --- a/src/nova/flags.h +++ b/src/nova/flags.h @@ -101,7 +101,7 @@ class FlagValues { const char * db_backend() const; - const char * guest_ethernet_device() const; + const char * guest_id() const; boost::optional host() const; @@ -135,8 +135,6 @@ class FlagValues { unsigned long periodic_interval() const; - boost::optional preset_instance_id() const; - size_t rabbit_client_memory() const; const char * rabbit_host() const; diff --git a/src/nova/guest/HeartBeat.cc b/src/nova/guest/HeartBeat.cc new file mode 100644 index 0000000..5d3cf83 --- /dev/null +++ b/src/nova/guest/HeartBeat.cc @@ -0,0 +1,50 @@ +#include "nova/guest/HeartBeat.h" +#include +#include "nova/Log.h" +#include "nova/db/mysql.h" +#include +#include "nova/guest/utils.h" + +using boost::format; +using namespace nova::db::mysql; +using boost::optional; +using nova::guest::utils::IsoDateTime; +using std::string; + +namespace nova { namespace guest { + + +HeartBeat::HeartBeat(MySqlConnectionWithDefaultDbPtr con, const char * guest_id) +: con(con), guest_id(guest_id) { +} + +HeartBeat::~HeartBeat() { +} + + +bool HeartBeat::exists() { + con->ensure(); + string query = str(format("SELECT updated_at FROM agent_heartbeats " + "WHERE instance_id='%s'") + % con->escape_string(guest_id.c_str())); + MySqlResultSetPtr results = con->query(query.c_str()); + return results->next(); +} + +void HeartBeat::update() { + IsoDateTime now; + const char * query = exists() + ? "UPDATE agent_heartbeats SET updated_at = ? " + "WHERE agent_heartbeats.instance_id = ?" + : "INSERT INTO agent_heartbeats (id, updated_at, instance_id) " + "VALUES(UUID(), ?, ?);" + ; + con->ensure(); + MySqlPreparedStatementPtr stmt = con->prepare_statement(query); + stmt->set_string(0, now.c_str()); + stmt->set_string(1, guest_id.c_str()); + stmt->execute(); +} + + +} } // end nova::guest diff --git a/src/nova/guest/HeartBeat.h b/src/nova/guest/HeartBeat.h new file mode 100644 index 0000000..2162851 --- /dev/null +++ b/src/nova/guest/HeartBeat.h @@ -0,0 +1,32 @@ +#ifndef __NOVA_GUEST_HEARTBEAT +#define __NOVA_GUEST_HEARTBEAT + +#include "nova/db/mysql.h" +#include + + +namespace nova { namespace guest { + +/** Updates the agent_heartbeats table with the current time. Used to determine + * if Sneaky Pete is still alive. */ +class HeartBeat { +public: + HeartBeat(nova::db::mysql::MySqlConnectionWithDefaultDbPtr con, + const char * guest_id); + + ~HeartBeat(); + + void update(); + +private: + + nova::db::mysql::MySqlConnectionWithDefaultDbPtr con; + + bool exists(); + + const std::string guest_id; +}; + +}} // nova::guest + +#endif diff --git a/src/nova/guest/mysql/MySqlAppStatus.cc b/src/nova/guest/mysql/MySqlAppStatus.cc index 3310cab..3fbbe9a 100644 --- a/src/nova/guest/mysql/MySqlAppStatus.cc +++ b/src/nova/guest/mysql/MySqlAppStatus.cc @@ -51,16 +51,14 @@ bool MySqlAppStatusContext::is_file(const char * file_path) const { MySqlAppStatus::MySqlAppStatus(MySqlConnectionWithDefaultDbPtr nova_db_connection, - const char * guest_ethernet_device, unsigned long nova_db_reconnect_wait_time, - boost::optional preset_instance_id, + const char * guest_id, MySqlAppStatusContext * context) : context(context), - guest_ethernet_device(guest_ethernet_device), + guest_id(guest_id), nova_db(nova_db_connection), nova_db_mutex(), nova_db_reconnect_wait_time(nova_db_reconnect_wait_time), - preset_instance_id(preset_instance_id), restart_mode(false), status(boost::none) { @@ -149,25 +147,6 @@ MySqlAppStatus::Status MySqlAppStatus::get_actual_db_status() const { } } -int MySqlAppStatus::get_guest_instance_id() { - if (preset_instance_id) { - return preset_instance_id.get(); - } - string address = utils::get_ipv4_address(guest_ethernet_device.c_str()); - - string text = str(format("SELECT instance_id FROM fixed_ips WHERE " - "address=\"%s\"") % nova_db->escape_string(address.c_str())); - MySqlResultSetPtr result = nova_db->query(text.c_str()); - if (!result->next()) { - NOVA_LOG_ERROR2("Could not find guest instance for host given address " - "%s.", address.c_str()); - throw MySqlGuestException(MySqlGuestException::GUEST_INSTANCE_ID_NOT_FOUND); - } - optional id = result->get_string(0); - NOVA_LOG_DEBUG2("instance from db=%s", id.get().c_str()); - return boost::lexical_cast(id.get().c_str()); -} - const char * MySqlAppStatus::get_current_status_string() const { if (status) { return status_name(status.get()); @@ -176,10 +155,10 @@ const char * MySqlAppStatus::get_current_status_string() const { } } -optional MySqlAppStatus::get_status_from_nova_db() { - int instance_id = get_guest_instance_id(); - string text = str(format("SELECT state FROM guest_status WHERE " - "instance_id= %d ") % instance_id); +optional MySqlAppStatus::get_status_from_nova_db() +const { + string text = str(format("SELECT status_id FROM service_statuses WHERE " + "instance_id= '%s' ") % guest_id); MySqlResultSetPtr result = nova_db->query(text.c_str()); if (result->next()) { optional status_as_int = result->get_int(0); @@ -192,7 +171,8 @@ optional MySqlAppStatus::get_status_from_nova_db() { } bool MySqlAppStatus::is_mysql_installed() const { - return (status && status.get() != BUILDING && status.get() != FAILED); + return (status && status.get() != NEW && + status.get() != BUILDING && status.get() != FAILED); } bool MySqlAppStatus::is_mysql_restarting() const { @@ -242,8 +222,6 @@ void MySqlAppStatus::repeatedly_attempt_mysql_method( } void MySqlAppStatus::set_status(MySqlAppStatus::Status status) { - int instance_id = get_guest_instance_id(); - const char * description = status_name(status); Status state = status; NOVA_LOG_INFO2("Updating MySQL app status to %d (%s).", ((int)status), @@ -253,23 +231,20 @@ void MySqlAppStatus::set_status(MySqlAppStatus::Status status) { if (get_status_from_nova_db() == boost::none) { NOVA_LOG_INFO("Inserting new Guest status row. Why wasn't this there?"); stmt = nova_db->prepare_statement( - "INSERT INTO guest_status " - "(created_at, updated_at, instance_id, state, state_description) " - "VALUES(?, ?, ?, ?, ?) "); - stmt->set_string(0, now.c_str()); - stmt->set_string(1, now.c_str()); - stmt->set_int(2, instance_id); - stmt->set_int(3, (int)state); - stmt->set_string(4, description); + "INSERT INTO service_statuses " + "(instance_id, status_id, status_description) " + "VALUES(?, ?, ?) "); + stmt->set_string(0, guest_id); + stmt->set_int(1, (int)state); + stmt->set_string(2, description); } else { stmt = nova_db->prepare_statement( - "UPDATE guest_status " - "SET state_description=?, state=?, updated_at=? " + "UPDATE service_statuses " + "SET status_description=?, status_id=? " "WHERE instance_id=?"); stmt->set_string(0, description); stmt->set_int(1, (int) state); - stmt->set_string(2, now.c_str()); - stmt->set_int(3, instance_id); + stmt->set_string(2, guest_id); } stmt->execute(0); this->status = optional(status); @@ -292,8 +267,12 @@ const char * MySqlAppStatus::status_name(MySqlAppStatus::Status status) { return "running"; case SHUTDOWN: return "shutdown"; - default: + case NEW: + return "new"; + case UNKNOWN: return "unknown_case"; + default: + return "!invalid status code!"; } } diff --git a/src/nova/guest/mysql/MySqlAppStatus.h b/src/nova/guest/mysql/MySqlAppStatus.h index 6601413..c8acf9e 100644 --- a/src/nova/guest/mysql/MySqlAppStatus.h +++ b/src/nova/guest/mysql/MySqlAppStatus.h @@ -45,7 +45,7 @@ namespace nova { namespace guest { namespace mysql { public: /* NOTE: These *MUST* match the values found in - * nova.compute.power_state! */ + * reddwarf.instance.models.ServiceStatuses! */ enum Status { RUNNING = 0x01, // MySQL is active. BLOCKED = 0x02, @@ -54,15 +54,14 @@ namespace nova { namespace guest { namespace mysql { CRASHED = 0x06, // When MySQL died after starting. FAILED = 0x08, BUILDING = 0x09, // MySQL is being installed / prepared. - UNKNOWN=0x16 // Set by Nova when the guest becomes unresponsive + UNKNOWN=0x16, // Set when the guest becomes unresponsive + NEW = 0x17 // Set when the instance is shiny and new. }; MySqlAppStatus(nova::db::mysql::MySqlConnectionWithDefaultDbPtr nova_db, - const char * guest_ethernet_device, unsigned long nova_db_reconnect_wait_time, - boost::optional preset_instance_id - = boost::none, + const char * guest_id, MySqlAppStatusContext * context = new MySqlAppStatusContext()); @@ -106,9 +105,6 @@ namespace nova { namespace guest { namespace mysql { * installed or the installation procedure failed. */ Status get_actual_db_status() const; - /** Determines the ID of this instance in the Nova DB. */ - int get_guest_instance_id(); - /** If true, updates are restricted until the mode is switched * off. */ bool is_mysql_restarting() const; @@ -133,10 +129,9 @@ namespace nova { namespace guest { namespace mysql { boost::optional find_mysql_pid_file() const; - /** Talks to Nova DB to get the status of the MySQL app. */ - boost::optional get_status_from_nova_db(); + boost::optional get_status_from_nova_db() const; - const std::string guest_ethernet_device; + const char * guest_id; nova::db::mysql::MySqlConnectionWithDefaultDbPtr nova_db; @@ -144,8 +139,6 @@ namespace nova { namespace guest { namespace mysql { const unsigned long nova_db_reconnect_wait_time; - const boost::optional preset_instance_id; - bool restart_mode; boost::optional status; diff --git a/src/nova/guest/mysql/MySqlMessageHandler.cc b/src/nova/guest/mysql/MySqlMessageHandler.cc index 3119185..35e3247 100644 --- a/src/nova/guest/mysql/MySqlMessageHandler.cc +++ b/src/nova/guest/mysql/MySqlMessageHandler.cc @@ -134,19 +134,19 @@ namespace { JSON_METHOD(list_users) { MySqlAdminPtr sql = guest->sql_admin(); - std::stringstream user_xml; + std::stringstream user_json; MySqlUserListPtr users = sql->list_users(); - user_xml << "["; + user_json << "["; bool once = false; BOOST_FOREACH(MySqlUserPtr & user, *users) { if (once) { - user_xml << ", "; + user_json << ", "; } - user_to_stream(user_xml, user); + user_to_stream(user_json, user); once = true; } - user_xml << "]"; - JsonDataPtr rtn(new JsonArray(user_xml.str().c_str())); + user_json << "]"; + JsonDataPtr rtn(new JsonArray(user_json.str().c_str())); return rtn; } diff --git a/src/nova/guest/utils.cc b/src/nova/guest/utils.cc index e950e4b..983184b 100644 --- a/src/nova/guest/utils.cc +++ b/src/nova/guest/utils.cc @@ -11,6 +11,7 @@ #include #include #include +#include using nova::guest::GuestException; @@ -85,12 +86,19 @@ const char * IsoTime::c_str() const { } IsoDateTime::IsoDateTime() { - set_time(str, sizeof(str), "%Y-%m-%d %H:%M:%S"); + set_to_now(); +} + +bool IsoDateTime::operator==(const IsoDateTime & rhs) const { + return 0 == strncmp(this->str, rhs.str, sizeof(str)); } const char * IsoDateTime::c_str() const { return str; } +void IsoDateTime::set_to_now() { + set_time(str, sizeof(str), "%Y-%m-%d %H:%M:%S"); +} }}} // end nova::guest::utils diff --git a/src/nova/guest/utils.h b/src/nova/guest/utils.h index 6c1d148..d2321cd 100644 --- a/src/nova/guest/utils.h +++ b/src/nova/guest/utils.h @@ -23,10 +23,16 @@ class IsoTime { class IsoDateTime { public: + /** Creates an IsoDateTime set to the current time. */ IsoDateTime(); + bool operator==(const IsoDateTime & rhs) const; + const char * c_str() const; + /** Update to the current time. */ + void set_to_now(); + private: char str[20]; }; diff --git a/src/nova/rpc/Receiver.cc b/src/nova/rpc/Receiver.cc index 903a344..d89759b 100644 --- a/src/nova/rpc/Receiver.cc +++ b/src/nova/rpc/Receiver.cc @@ -20,7 +20,8 @@ using std::string; namespace nova { namespace rpc { namespace { - const char * EMPTY_MESSAGE = "{ \"failure\": null, \"result\":null }"; + const char * END_MESSAGE = "{ \"failure\": null, \"result\":null, " + " \"ending\":true }"; } @@ -111,8 +112,8 @@ void Receiver::finish_message(const GuestOutput & output) { rtn_ex_channel->publish(exchange_name, routing_key, msg.c_str()); // This is like telling Nova "roger." - NOVA_LOG_INFO2("Replying with empty message: %s", EMPTY_MESSAGE); - rtn_ex_channel->publish(exchange_name, routing_key, EMPTY_MESSAGE); + NOVA_LOG_INFO2("Replying with 'end' message: %s", END_MESSAGE); + rtn_ex_channel->publish(exchange_name, routing_key, END_MESSAGE); } JsonObjectPtr Receiver::_next_message() { diff --git a/src/receiver_daemon.cc b/src/receiver_daemon.cc index dd753ab..d407421 100644 --- a/src/receiver_daemon.cc +++ b/src/receiver_daemon.cc @@ -1,5 +1,4 @@ #include "nova/rpc/amqp.h" -#include "nova/db/api.h" #include "nova/guest/apt.h" #include "nova/ConfigFile.h" #include "nova/flags.h" @@ -7,7 +6,9 @@ #include "nova/guest/guest.h" #include "nova/guest/diagnostics.h" #include "nova/guest/GuestException.h" +#include "nova/guest/HeartBeat.h" #include +#include #include #include "nova/db/mysql.h" #include "nova/guest/mysql/MySqlMessageHandler.h" @@ -36,7 +37,6 @@ // #define CATCH_RPC_METHOD_ERRORS // #endif -using nova::db::ApiPtr; using nova::guest::apt::AptGuest; using nova::guest::apt::AptMessageHandler; using std::auto_ptr; @@ -48,9 +48,7 @@ using namespace nova::guest; using namespace nova::guest::diagnostics; using namespace nova::db::mysql; using namespace nova::guest::mysql; -using nova::db::NewService; using namespace nova::rpc; -using nova::db::ServicePtr; using std::string; @@ -69,25 +67,19 @@ static bool quit; class PeriodicTasker { private: + HeartBeat & heart_beat; JsonObject message; MySqlConnectionWithDefaultDbPtr nova_db; - NewService service_key; MySqlAppStatusPtr status_updater; public: - PeriodicTasker(MySqlConnectionWithDefaultDbPtr nova_db, - MySqlAppStatusPtr status_updater, NewService service_key) - : message(PERIODIC_MESSAGE), - nova_db(nova_db), - service_key(service_key), + PeriodicTasker(HeartBeat & heart_beat, MySqlAppStatusPtr status_updater) + : heart_beat(heart_beat), + message(PERIODIC_MESSAGE), status_updater(status_updater) { } - void ensure_db() { - nova_db->ensure(); - } - void loop(unsigned long periodic_interval, unsigned long report_interval) { unsigned long next_periodic_task = periodic_interval; unsigned long next_reporting = report_interval; @@ -121,11 +113,7 @@ class PeriodicTasker { void report_state() { START_THREAD_TASK(); - ensure_db(); - ApiPtr api = nova::db::create_api(nova_db); - ServicePtr service = api->service_create(service_key); - service->report_count ++; - api->service_update(*service); + this->heart_beat.update(); END_THREAD_TASK("report_state()"); } }; @@ -141,17 +129,13 @@ AmqpConnectionPtr make_amqp_connection(FlagValues & flags) { int main(int argc, char* argv[]) { quit = false; - + bool logging_initialized = false; #ifndef _DEBUG try { #endif - // Initialize MySQL libraries. This should be done before spawning - // threads. - MySqlApiScope mysql_api_scope; - /* Grab flag values. */ FlagValues flags(FlagMap::create_from_args(argc, argv, true)); @@ -171,6 +155,20 @@ int main(int argc, char* argv[]) { LogApiScope log_api_scope(log_options); + logging_initialized = true; + + NOVA_LOG_INFO(" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + NOVA_LOG_INFO(" ^ ^ ^ ^"); + NOVA_LOG_INFO(" ^ ' ' -----REDDWARF-GUEST-AGENT----- ^"); + NOVA_LOG_INFO(" ^ \\`-'/ -------Sneaky--Pete------- ^"); + NOVA_LOG_INFO(" ^ |__ ^"); + NOVA_LOG_INFO(" ^ / starting now...^"); + NOVA_LOG_INFO(" ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + + // Initialize MySQL libraries. This should be done before spawning + // threads. + MySqlApiScope mysql_api_scope; + /* Create connection to Nova database. */ MySqlConnectionWithDefaultDbPtr nova_db( new MySqlConnectionWithDefaultDb( @@ -192,9 +190,8 @@ int main(int argc, char* argv[]) { /* Create MySQL updater. */ MySqlAppStatusPtr mysql_status_updater(new MySqlAppStatus( nova_db, - flags.guest_ethernet_device(), flags.nova_db_reconnect_wait_time(), - flags.preset_instance_id())); + flags.guest_id())); /* Create MySQL Guest. */ handlers[1].reset(new MySqlMessageHandler()); @@ -211,18 +208,14 @@ int main(int argc, char* argv[]) { string actual_host = nova::guest::utils::get_host_name(); string host = flags.host().get_value_or(actual_host.c_str()); + /* Create HeartBeat. */ + HeartBeat heart_beat(nova_db, flags.guest_id()); + /* Create tasker. */ - NewService service_key; - service_key.availability_zone = flags.node_availability_zone(); - service_key.binary = "nova-guest"; - service_key.host = host; - service_key.topic = "guest"; // Real nova takes binary after "nova-". - PeriodicTasker tasker(nova_db, - mysql_status_updater, service_key); + PeriodicTasker tasker(heart_beat, mysql_status_updater); /* Create AMQP connection. */ - string topic = "guest."; - topic += nova::guest::utils::get_host_name(); + string topic = str(format("guestagent.%s") % flags.guest_id()); quit = false; @@ -276,7 +269,11 @@ int main(int argc, char* argv[]) { #ifndef _DEBUG } catch (const std::exception & e) { - NOVA_LOG_ERROR2("Error: %s", e.what()); + if (logging_initialized) { + NOVA_LOG_ERROR2("Error: %s", e.what()); + } else { + std::cerr << "Error: " << e.what() << std::endl; + } } #endif return 0; diff --git a/tests/nova/db/api_tests.cc b/tests/nova/db/api_tests.cc deleted file mode 100644 index 2b0b1e0..0000000 --- a/tests/nova/db/api_tests.cc +++ /dev/null @@ -1,130 +0,0 @@ -#define BOOST_TEST_MODULE nova_db_api_tests -#include - -#include "nova/db/api.h" -#include "nova/flags.h" -#include -#include "nova/Log.h" -#include "nova/db/mysql.h" -#include "nova/guest/utils.h" - -#define CHECK_POINT() BOOST_CHECK_EQUAL(2,2); - -using nova::db::Api; -using nova::db::ApiPtr; -using namespace nova::flags; -using nova::guest::utils::IsoDateTime; -using nova::Log; -using namespace nova::db::mysql; -using boost::optional; -using nova::db::NewService; -using nova::db::Service; -using nova::db::ServicePtr; -using std::string; - - -FlagMapPtr get_flags() { - FlagMapPtr ptr(new FlagMap()); - char * test_args = getenv("TEST_ARGS"); - BOOST_REQUIRE_MESSAGE(test_args != 0, - "TEST_ARGS environment var not defined."); - if (test_args != 0) { - ptr->add_from_arg(test_args); - } - return ptr; -} - -BOOST_AUTO_TEST_CASE(integration_tests) -{ - MySqlApiScope mysql_api_scope; - - FlagValues flags(get_flags()); - - CHECK_POINT(); - - NewService args; - args.availability_zone = "Enders"; - args.binary = "nova-guest"; - args.host = "Letterman"; - args.topic = "cereal"; - - MySqlConnectionWithDefaultDbPtr connection( - new MySqlConnectionWithDefaultDb(flags.nova_sql_host(), - flags.nova_sql_user(), flags.nova_sql_password(), - flags.nova_sql_database())); - // Destroy all the matching services. - { - CHECK_POINT(); - MySqlPreparedStatementPtr stmt = connection->prepare_statement( - "DELETE FROM services WHERE services.binary= ? AND host= ? " - "AND services.topic = ? AND availability_zone = ?"); - int index = 0; - stmt->set_string(index ++, args.binary.c_str()); - stmt->set_string(index ++, args.host.c_str()); - stmt->set_string(index ++, args.topic.c_str()); - stmt->set_string(index ++, args.availability_zone.c_str()); - stmt->execute(); - } - CHECK_POINT(); - - ApiPtr api = nova::db::create_api(connection); - - ServicePtr service; - - // Retrieve - should be missing. - { - ServicePtr service0 = api->service_get_by_args(args); - BOOST_CHECK(!service0); - } - - // Create - { - service = api->service_create(args); - BOOST_CHECK_EQUAL(service->availability_zone, "Enders"); - BOOST_CHECK_EQUAL(service->binary, "nova-guest"); - BOOST_CHECK_EQUAL(service->host, "Letterman"); - BOOST_CHECK_EQUAL(service->topic, "cereal"); - BOOST_CHECK(!!service->disabled); - if (service->disabled != boost::none) { - BOOST_CHECK_EQUAL(service->disabled.get(), false); - } - BOOST_CHECK_EQUAL(!!service->report_count, 0); - } - - - // Retrieve - { - ServicePtr service2 = api->service_get_by_args(args); - BOOST_CHECK_EQUAL(service2->availability_zone, - service->availability_zone); - BOOST_CHECK_EQUAL(service2->binary, service->binary); - BOOST_CHECK_EQUAL(service2->host, service->host); - BOOST_CHECK_EQUAL(service2->topic, service->topic); - BOOST_CHECK_EQUAL(service2->disabled, service->disabled); - BOOST_CHECK_EQUAL(service2->report_count, service->report_count); - BOOST_CHECK_EQUAL(service2->id, service->id); - } - - // Update - { - service->report_count ++; - service->disabled = optional(true); - IsoDateTime new_time; - - api->service_update(*service); - - ServicePtr service2 = api->service_get_by_args(args); - BOOST_CHECK_EQUAL(service2->availability_zone, - service->availability_zone); - BOOST_CHECK_EQUAL(service2->binary, service->binary); - BOOST_CHECK_EQUAL(service2->host, service->host); - BOOST_CHECK_EQUAL(service2->topic, service->topic); - BOOST_CHECK_EQUAL(!service2->disabled, !service->disabled); - if (service->disabled) { - BOOST_CHECK_EQUAL(service2->disabled.get(), - service->disabled.get()); - } - BOOST_CHECK_EQUAL(service2->report_count, service->report_count); - BOOST_CHECK_EQUAL(service2->id, service->id); - } -} diff --git a/tests/nova/guest/HeartBeat_tests.cc b/tests/nova/guest/HeartBeat_tests.cc new file mode 100644 index 0000000..55dcc16 --- /dev/null +++ b/tests/nova/guest/HeartBeat_tests.cc @@ -0,0 +1,135 @@ +#define BOOST_TEST_MODULE nova_guest_HeartBeat_tests +#include + +#include "nova/guest/HeartBeat.h" +#include "nova/flags.h" +#include +#include +#include "nova/Log.h" +#include "nova/db/mysql.h" +#include "nova/utils/regex.h" +#include +#include "nova/guest/utils.h" + +#define CHECK_POINT() BOOST_CHECK_EQUAL(2,2); + +using namespace nova::flags; +using boost::format; +using nova::guest::HeartBeat; +using nova::guest::utils::IsoDateTime; +using nova::Log; +using namespace nova::db::mysql; +using namespace nova; +using boost::optional; +using nova::utils::Regex; +using nova::utils::RegexMatchesPtr; +using std::string; + + +FlagMapPtr get_flags() { + FlagMapPtr ptr(new FlagMap()); + char * test_args = getenv("TEST_ARGS"); + BOOST_REQUIRE_MESSAGE(test_args != 0, + "TEST_ARGS environment var not defined."); + if (test_args != 0) { + ptr->add_from_arg(test_args); + } + return ptr; +} + +optional get_updated_at(MySqlConnectionWithDefaultDbPtr con, + const char * instance_id) { + string query = str(format("SELECT updated_at FROM agent_heartbeats " + "WHERE instance_id='%s'") + % con->escape_string(instance_id)); + NOVA_LOG_INFO(query.c_str()); + CHECK_POINT(); + MySqlResultSetPtr results = con->query(query.c_str()); + CHECK_POINT(); + if (results->next()) { + CHECK_POINT(); + return results->get_string(0); + } else { + CHECK_POINT(); + return boost::none; + } +} + +BOOST_AUTO_TEST_CASE(integration_tests) +{ + LogApiScope log(LogOptions::simple()); + MySqlApiScope mysql_api_scope; + FlagValues flags(get_flags()); + const char * GUEST_ID = "testid"; + + CHECK_POINT(); + + MySqlConnectionWithDefaultDbPtr connection( + new MySqlConnectionWithDefaultDb(flags.nova_sql_host(), + flags.nova_sql_user(), flags.nova_sql_password(), + flags.nova_sql_database())); + + // Destroy all the matching services. + { + CHECK_POINT(); + MySqlPreparedStatementPtr stmt = connection->prepare_statement( + "DELETE FROM agent_heartbeats WHERE instance_id= ?"); + stmt->set_string(0, GUEST_ID); + stmt->execute(); + } + CHECK_POINT(); + + HeartBeat heart_beat(connection, GUEST_ID); + CHECK_POINT(); + { + // Shouldn't find a thing. + const optional result = get_updated_at(connection, GUEST_ID); + BOOST_CHECK_EQUAL(boost::none, result); + } + // We'll need to make sure the time is in the correct format, i.e.: + // 2012-05-22 20:19:49 + Regex iso_time_fmt("^[0-9]{4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}" + "\\:[0-9]{2}\\:[0-9]{2}"); + + heart_beat.update(); + IsoDateTime before; + + // Now it had better find something. + const optional result = get_updated_at(connection, GUEST_ID); + { + BOOST_REQUIRE(!!result); + RegexMatchesPtr matches = iso_time_fmt.match(result.get().c_str()); + BOOST_CHECK(!!matches); + BOOST_CHECK(matches->exists_at(0)); + } + + // Wait a second before proceeding... + + const int sleep_time = 50; + int attempts = (1000 / sleep_time) + 2; + IsoDateTime now; + do + { + attempts --; + BOOST_REQUIRE_MESSAGE(attempts >= 0, "IsoDateTime is not updating."); + boost::this_thread::sleep(boost::posix_time::milliseconds(sleep_time)); + now.set_to_now(); + NOVA_LOG_INFO2("%s != %s", now.c_str(), before.c_str()); + } while(now == before); + + + // Tests that a second call, which executes the update query, doesn't break. + heart_beat.update(); + const optional result2 = get_updated_at(connection, GUEST_ID); + { + // Make sure the update function put a time in there. + BOOST_REQUIRE(!!result2); + RegexMatchesPtr matches = iso_time_fmt.match(result2.get().c_str()); + BOOST_CHECK(!!matches); + BOOST_CHECK(matches->exists_at(0)); + + // Make sure it wasn't the same time as before. + NOVA_LOG_INFO2("%s != %s", result.get().c_str(), result2.get().c_str()); + BOOST_CHECK(result.get() != result2.get()); + } +} diff --git a/tests/nova/guest/mysql/MySqlAppStatus_tests.cc b/tests/nova/guest/mysql/MySqlAppStatus_tests.cc index d01eb9a..72348d5 100644 --- a/tests/nova/guest/mysql/MySqlAppStatus_tests.cc +++ b/tests/nova/guest/mysql/MySqlAppStatus_tests.cc @@ -85,7 +85,7 @@ struct MySqlAppStatusDefaultTestContext struct MySqlAppStatusTestsFixture { FlagValues flags; - int id; + const char * id; MySqlConnectionWithDefaultDbPtr nova_db; MySqlAppStatus updater; @@ -94,12 +94,11 @@ struct MySqlAppStatusTestsFixture { nova_db(new MySqlConnectionWithDefaultDb(flags.nova_sql_host(), flags.nova_sql_user(), flags.nova_sql_password(), flags.nova_sql_database())), updater(nova_db, - flags.guest_ethernet_device(), flags.nova_sql_reconnect_wait_time(), - optional(-255), + flags.guest_id(), new MySqlAppStatusDefaultTestContext()) { - id = updater.get_guest_instance_id(); + id = updater.guest_id; CHECK_POINT(); delete_row(); @@ -112,8 +111,8 @@ struct MySqlAppStatusTestsFixture { void delete_row() { MySqlPreparedStatementPtr stmt = nova_db->prepare_statement( - "DELETE from guest_status WHERE instance_id = ?"); - stmt->set_int(0, id); + "DELETE from service_statuses WHERE instance_id = ?"); + stmt->set_string(0, id); stmt->execute(0); } };