Drizzled Public API Documentation

queue_producer.cc
00001 /* - mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*-
00002  *  vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
00003  *
00004  *  Copyright (C) 2011 David Shrewsbury
00005  *
00006  *  This program is free software; you can redistribute it and/or modify
00007  *  it under the terms of the GNU General Public License as published by
00008  *  the Free Software Foundation; either version 2 of the License, or
00009  *  (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00019  */
00020 
00021 #include <config.h>
00022 
00023 #include <plugin/slave/queue_producer.h>
00024 #include <drizzled/errmsg_print.h>
00025 #include <drizzled/sql/result_set.h>
00026 #include <drizzled/execute.h>
00027 #include <drizzled/gettext.h>
00028 #include <drizzled/message/transaction.pb.h>
00029 #include <boost/lexical_cast.hpp>
00030 #include <google/protobuf/text_format.h>
00031 #include <string>
00032 #include <vector>
00033 
00034 using namespace std;
00035 using namespace drizzled;
00036 
00037 namespace slave
00038 {
00039 
00040 QueueProducer::~QueueProducer()
00041 {
00042   if (_is_connected)
00043     closeConnection();
00044 }
00045 
00046 bool QueueProducer::init()
00047 {
00048   setIOState("", true);
00049   return reconnect(true);
00050 }
00051 
00052 bool QueueProducer::process()
00053 {
00054   if (_saved_max_commit_id == 0)
00055   {
00056     if (not queryForMaxCommitId(&_saved_max_commit_id))
00057     {
00058       if (_last_return == DRIZZLE_RETURN_LOST_CONNECTION)
00059       {
00060         if (reconnect(false))
00061         {
00062           return true;    /* reconnect successful, try again */
00063         }
00064         else
00065         {
00066           _last_error_message= "Master offline";
00067           return false;   /* reconnect failed, shutdown the thread */
00068         }
00069       }
00070       else
00071       {
00072         return false;     /* unrecoverable error, shutdown the thread */
00073       }
00074     }
00075   }
00076 
00077   /* Keep getting events until caught up */
00078   enum drizzled::error_t err;
00079   while ((err= (queryForReplicationEvents(_saved_max_commit_id))) == EE_OK)
00080   {}
00081 
00082   if (err == ER_YES)  /* We encountered an error */
00083   {
00084     if (_last_return == DRIZZLE_RETURN_LOST_CONNECTION)
00085     {
00086       if (reconnect(false))
00087       {
00088         return true;    /* reconnect successful, try again */
00089       }
00090       else
00091       {
00092         _last_error_message= "Master offline";
00093         return false;   /* reconnect failed, shutdown the thread */
00094       }
00095     }
00096     else
00097     {
00098       return false;     /* unrecoverable error, shutdown the thread */
00099     }
00100   }
00101 
00102   return true;
00103 }
00104 
00105 void QueueProducer::shutdown()
00106 {
00107   setIOState(_last_error_message, false);
00108   if (_is_connected)
00109     closeConnection();
00110 }
00111 
00112 bool QueueProducer::reconnect(bool initial_connection)
00113 {
00114   if (not initial_connection)
00115   {
00116     errmsg_printf(error::ERROR, _("Lost connection to master. Reconnecting."));
00117   }
00118 
00119   _is_connected= false;
00120   _last_return= DRIZZLE_RETURN_OK;
00121   _last_error_message.clear();
00122   boost::posix_time::seconds duration(_seconds_between_reconnects);
00123 
00124   uint32_t attempts= 1;
00125 
00126   while (not openConnection())
00127   {
00128     if (attempts++ == _max_reconnects)
00129       break;
00130     boost::this_thread::sleep(duration);
00131   }
00132 
00133   return _is_connected;
00134 }
00135 
00136 bool QueueProducer::openConnection()
00137 {
00138   if ((_drizzle= drizzle_create()) == NULL)
00139   {
00140     _last_return= DRIZZLE_RETURN_INTERNAL_ERROR;
00141     _last_error_message= "Replication slave: ";
00142     _last_error_message.append(drizzle_error(_drizzle));
00143     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00144     return false;
00145   }
00146   
00147   if ((_connection= drizzle_con_create(_drizzle)) == NULL)
00148   {
00149     _last_return= DRIZZLE_RETURN_INTERNAL_ERROR;
00150     _last_error_message= "Replication slave: ";
00151     _last_error_message.append(drizzle_error(_drizzle));
00152     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00153     return false;
00154   }
00155   
00156   drizzle_con_set_tcp(_connection, _master_host.c_str(), _master_port);
00157   drizzle_con_set_auth(_connection, _master_user.c_str(), _master_pass.c_str());
00158 
00159   drizzle_return_t ret= drizzle_con_connect(_connection);
00160 
00161   if (ret != DRIZZLE_RETURN_OK)
00162   {
00163     _last_return= ret;
00164     _last_error_message= "Replication slave: ";
00165     _last_error_message.append(drizzle_error(_drizzle));
00166     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00167     return false;
00168   }
00169   
00170   _is_connected= true;
00171 
00172   return true;
00173 }
00174 
00175 bool QueueProducer::closeConnection()
00176 {
00177   drizzle_return_t ret;
00178   drizzle_result_st result;
00179 
00180   _is_connected= false;
00181 
00182   if (drizzle_quit(_connection, &result, &ret) == NULL)
00183   {
00184     _last_return= ret;
00185     drizzle_result_free(&result);
00186     return false;
00187   }
00188 
00189   drizzle_result_free(&result);
00190 
00191   return true;
00192 }
00193 
00194 bool QueueProducer::queryForMaxCommitId(uint64_t *max_commit_id)
00195 {
00196   /*
00197    * This SQL will get the maximum commit_id value we have pulled over from
00198    * the master. We query two tables because either the queue will be empty,
00199    * in which case the last_applied_commit_id will be the value we want, or
00200    * we have yet to drain the queue,  we get the maximum value still in
00201    * the queue.
00202    */
00203   string sql("SELECT MAX(x.cid) FROM"
00204              " (SELECT MAX(`commit_order`) AS cid FROM `sys_replication`.`queue`"
00205              "  WHERE `master_id` = "
00206              + boost::lexical_cast<string>(masterId())
00207              + "  UNION ALL SELECT `last_applied_commit_id` AS cid"
00208              + "  FROM `sys_replication`.`applier_state` WHERE `master_id` = "
00209              + boost::lexical_cast<string>(masterId())
00210              + ") AS x");
00211 
00212   sql::ResultSet result_set(1);
00213   Execute execute(*(_session.get()), true);
00214   execute.run(sql, result_set);
00215   assert(result_set.getMetaData().getColumnCount() == 1);
00216 
00217   /* Really should only be 1 returned row */
00218   uint32_t found_rows= 0;
00219   while (result_set.next())
00220   {
00221     string value= result_set.getString(0);
00222 
00223     if ((value == "") || (found_rows == 1))
00224       break;
00225 
00226     assert(result_set.isNull(0) == false);
00227     *max_commit_id= boost::lexical_cast<uint64_t>(value);
00228     found_rows++;
00229   }
00230 
00231   if (found_rows == 0)
00232   {
00233     _last_error_message= "Could not determine last committed transaction.";
00234     return false;
00235   }
00236 
00237   return true;
00238 }
00239 
00240 bool QueueProducer::queryForTrxIdList(uint64_t max_commit_id,
00241                                       vector<uint64_t> &list)
00242 {
00243   (void)list;
00244   string sql("SELECT `id` FROM `data_dictionary`.`sys_replication_log`"
00245              " WHERE `commit_id` > ");
00246   sql.append(boost::lexical_cast<string>(max_commit_id));
00247   sql.append(" ORDER BY `commit_id` LIMIT 25");
00248 
00249   drizzle_return_t ret;
00250   drizzle_result_st result;
00251   drizzle_query_str(_connection, &result, sql.c_str(), &ret);
00252   
00253   if (ret != DRIZZLE_RETURN_OK)
00254   {
00255     _last_return= ret;
00256     _last_error_message= "Replication slave: ";
00257     _last_error_message.append(drizzle_error(_drizzle));
00258     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00259     drizzle_result_free(&result);
00260     return false;
00261   }
00262 
00263   ret= drizzle_result_buffer(&result);
00264 
00265   if (ret != DRIZZLE_RETURN_OK)
00266   {
00267     _last_return= ret;
00268     _last_error_message= "Replication slave: ";
00269     _last_error_message.append(drizzle_error(_drizzle));
00270     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00271     drizzle_result_free(&result);
00272     return false;
00273   }
00274 
00275   drizzle_row_t row;
00276 
00277   while ((row= drizzle_row_next(&result)) != NULL)
00278   {
00279     if (row[0])
00280     {
00281       list.push_back(boost::lexical_cast<uint32_t>(row[0]));
00282     }
00283     else
00284     {
00285       _last_return= ret;
00286       _last_error_message= "Replication slave: Unexpected NULL for trx id";
00287       errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00288       drizzle_result_free(&result);
00289       return false;
00290     }
00291   }
00292 
00293   drizzle_result_free(&result);
00294   return true;
00295 }
00296 
00297 
00298 bool QueueProducer::queueInsert(const char *trx_id,
00299                                 const char *seg_id,
00300                                 const char *commit_id,
00301                                 const char *originating_server_uuid,
00302                                 const char *originating_commit_id,
00303                                 const char *msg,
00304                                 const char *msg_length)
00305 {
00306   message::Transaction message;
00307 
00308   message.ParseFromArray(msg, boost::lexical_cast<int>(msg_length));
00309 
00310   /*
00311    * The SQL to insert our results into the local queue.
00312    */
00313   string sql= "INSERT INTO `sys_replication`.`queue`"
00314               " (`master_id`, `trx_id`, `seg_id`, `commit_order`,"
00315               "  `originating_server_uuid`, `originating_commit_id`, `msg`) VALUES (";
00316   sql.append(boost::lexical_cast<string>(masterId()));
00317   sql.append(", ", 2);
00318   sql.append(trx_id);
00319   sql.append(", ", 2);
00320   sql.append(seg_id);
00321   sql.append(", ", 2);
00322   sql.append(commit_id);
00323   sql.append(", '", 3);
00324   sql.append(originating_server_uuid);
00325   sql.append("' , ", 4);
00326   sql.append(originating_commit_id);
00327   sql.append(", '", 3);
00328 
00329   /*
00330    * Ideally we would store the Transaction message in binary form, as it
00331    * it stored on the master and tranferred to the slave. However, we are
00332    * inserting using drizzle::Execute which doesn't really handle binary
00333    * data. Until that is changed, we store as plain text.
00334    */
00335   string message_text;
00336   google::protobuf::TextFormat::PrintToString(message, &message_text);  
00337 
00338   /*
00339    * Execution using drizzled::Execute requires some special escaping.
00340    */
00341   string::iterator it= message_text.begin();
00342   for (; it != message_text.end(); ++it)
00343   {
00344     if (*it == '\"')
00345     {
00346       it= message_text.insert(it, '\\');
00347       ++it;
00348     }
00349     else if (*it == '\'')
00350     {
00351       it= message_text.insert(it, '\\');
00352       ++it;
00353       it= message_text.insert(it, '\\');
00354       ++it;
00355     }
00356     else if (*it == '\\')
00357     {
00358       it= message_text.insert(it, '\\');
00359       ++it;
00360       it= message_text.insert(it, '\\');
00361       ++it;
00362       it= message_text.insert(it, '\\');
00363       ++it;
00364     }
00365     else if (*it == ';')
00366     {
00367       it= message_text.insert(it, '\\');
00368       ++it;  /* advance back to the semicolon */
00369     }
00370   }
00371 
00372   sql.append(message_text);
00373   sql.append("')", 2);
00374 
00375   vector<string> statements;
00376   statements.push_back(sql);
00377 
00378   if (not executeSQL(statements))
00379   {
00380     markInErrorState();
00381     return false;
00382   }
00383 
00384   uint64_t tmp_commit_id= boost::lexical_cast<uint64_t>(commit_id);
00385   if (tmp_commit_id > _saved_max_commit_id)
00386     _saved_max_commit_id= tmp_commit_id;
00387 
00388   return true;
00389 }
00390 
00391 
00392 enum drizzled::error_t QueueProducer::queryForReplicationEvents(uint64_t max_commit_id)
00393 {
00394   vector<uint64_t> trx_id_list;
00395 
00396   if (not queryForTrxIdList(max_commit_id, trx_id_list))
00397     return ER_YES;
00398 
00399   if (trx_id_list.size() == 0)    /* nothing to get from the master */
00400   {
00401     return ER_NO;
00402   }
00403 
00404   /*
00405    * The SQL to pull everything we need from the master.
00406    */
00407   string sql= "SELECT `id`, `segid`, `commit_id`, `originating_server_uuid`,"
00408               " `originating_commit_id`, `message`, `message_len` "
00409               " FROM `data_dictionary`.`sys_replication_log` WHERE `id` IN (";
00410 
00411   for (size_t x= 0; x < trx_id_list.size(); x++)
00412   {
00413     if (x > 0)
00414       sql.append(", ", 2);
00415     sql.append(boost::lexical_cast<string>(trx_id_list[x]));
00416   }
00417 
00418   sql.append(")", 1);
00419   sql.append(" ORDER BY `commit_id` ASC");
00420 
00421   drizzle_return_t ret;
00422   drizzle_result_st result;
00423   drizzle_query_str(_connection, &result, sql.c_str(), &ret);
00424   
00425   if (ret != DRIZZLE_RETURN_OK)
00426   {
00427     _last_return= ret;
00428     _last_error_message= "Replication slave: ";
00429     _last_error_message.append(drizzle_error(_drizzle));
00430     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00431     drizzle_result_free(&result);
00432     return ER_YES;
00433   }
00434 
00435   /* TODO: Investigate 1-row-at-a-time buffering */
00436 
00437   ret= drizzle_result_buffer(&result);
00438 
00439   if (ret != DRIZZLE_RETURN_OK)
00440   {
00441     _last_return= ret;
00442     _last_error_message= "Replication slave: ";
00443     _last_error_message.append(drizzle_error(_drizzle));
00444     errmsg_printf(error::ERROR, _("%s"), _last_error_message.c_str());
00445     drizzle_result_free(&result);
00446     return ER_YES;
00447   }
00448 
00449   drizzle_row_t row;
00450 
00451   while ((row= drizzle_row_next(&result)) != NULL)
00452   {
00453     if (not queueInsert(row[0], row[1], row[2], row[3], row[4], row[5], row[6]))
00454     {
00455       errmsg_printf(error::ERROR,
00456                     _("Replication slave: Unable to insert into queue."));
00457       drizzle_result_free(&result);
00458       return ER_YES;
00459     }
00460   }
00461 
00462   drizzle_result_free(&result);
00463 
00464   return EE_OK;
00465 }
00466 
00467 
00468 void QueueProducer::setIOState(const string &err_msg, bool status)
00469 {
00470   vector<string> statements;
00471   string sql;
00472   string msg(err_msg);
00473 
00474   if (not status)
00475   {
00476     sql= "UPDATE `sys_replication`.`io_state` SET `status` = 'STOPPED'";
00477   }
00478   else
00479   {
00480     sql= "UPDATE `sys_replication`.`io_state` SET `status` = 'RUNNING'";
00481   }
00482   
00483   sql.append(", `error_msg` = '", 17);
00484 
00485   /* Escape embedded quotes and statement terminators */
00486   string::iterator it;
00487   for (it= msg.begin(); it != msg.end(); ++it)
00488   {
00489     if (*it == '\'')
00490     {
00491       it= msg.insert(it, '\'');
00492       ++it;  /* advance back to the quote */
00493     }
00494     else if (*it == ';')
00495     {
00496       it= msg.insert(it, '\\');
00497       ++it;  /* advance back to the semicolon */
00498     }
00499   }
00500   
00501   sql.append(msg);
00502   sql.append("' WHERE `master_id` = ");
00503   sql.append(boost::lexical_cast<string>(masterId()));
00504 
00505   statements.push_back(sql);
00506   executeSQL(statements);
00507 }
00508 
00509 } /* namespace slave */