/* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.

   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; version 2 of the License.

   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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA */

/*
  This plugin serves as an example for all those who which to use the new
  Hooks installed by Replication in order to capture:
  - Transaction progress
  - Server state
 */

#include <mysql/group_replication_priv.h>
#include <mysql/plugin.h>
#include <mysql/service_my_plugin_log.h>
#include <mysql/service_rpl_transaction_ctx.h>

static MYSQL_PLUGIN plugin_info_ptr;

int validate_plugin_server_requirements(Trans_param *param);
int test_channel_service_interface_initialization();
int test_channel_service_interface();
int test_channel_service_interface_io_thread();
bool test_channel_service_interface_is_io_stopping();
bool test_channel_service_interface_is_sql_stopping();
bool test_channel_service_interface_relay_log_renamed();

/*
  Will register the number of calls to each method of Server state
 */
static int before_handle_connection_call= 0;
static int before_recovery_call= 0;
static int after_engine_recovery_call= 0;
static int after_recovery_call= 0;
static int before_server_shutdown_call= 0;
static int after_server_shutdown_call= 0;
static bool thread_aborted= false;

static void dump_server_state_calls()
{
  if(before_handle_connection_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:before_handle_connection");
  }

  if(before_recovery_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:before_recovery");
  }

  if(after_engine_recovery_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:after_engine_recovery");
  }

  if(after_recovery_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:after_recovery");
  }

  if(before_server_shutdown_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:before_server_shutdown");
  }

  if(after_server_shutdown_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:after_server_shutdown");
  }
}

/*
  DBMS lifecycle events observers.
*/
int before_handle_connection(Server_state_param *param)
{
  before_handle_connection_call++;

  return 0;
}

int before_recovery(Server_state_param *param)
{
  before_recovery_call++;

  return 0;
}

int after_engine_recovery(Server_state_param *param)
{
  after_engine_recovery_call++;

  return 0;
}

int after_recovery(Server_state_param *param)
{
  after_recovery_call++;

  return 0;
}

int before_server_shutdown(Server_state_param *param)
{
  before_server_shutdown_call++;

  return 0;
}

int after_server_shutdown(Server_state_param *param)
{
  after_server_shutdown_call++;

  return 0;
}

Server_state_observer server_state_observer = {
  sizeof(Server_state_observer),

  before_handle_connection, //before the client connect the node
  before_recovery,           //before_recovery
  after_engine_recovery,     //after engine recovery
  after_recovery,            //after_recovery
  before_server_shutdown,    //before shutdown
  after_server_shutdown,     //after shutdown
};

static int trans_before_dml_call= 0;
static int trans_before_commit_call= 0;
static int trans_before_rollback_call= 0;
static int trans_after_commit_call= 0;
static int trans_after_rollback_call= 0;

static void dump_transaction_calls()
{

  if(trans_before_dml_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:trans_before_dml");
  }

  if(trans_before_commit_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:trans_before_commit");
  }

  if(trans_before_rollback_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:trans_before_rollback");
  }

  if(trans_after_commit_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:trans_after_commit");
  }

  if(trans_after_rollback_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:trans_after_rollback");
  }
}

/*
  Transaction lifecycle events observers.
*/
int trans_before_dml(Trans_param *param, int& out_val)
{
  trans_before_dml_call++;

  DBUG_EXECUTE_IF("cause_failure_in_before_dml_hook",
                  out_val= 1;);
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_channels",
                  test_channel_service_interface(););
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_channel_io_thread",
                  test_channel_service_interface_io_thread(););
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_channels_init",
                  test_channel_service_interface_initialization(););
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_is_io_stopping",
                  test_channel_service_interface_is_io_stopping(););
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_is_sql_stopping",
                  test_channel_service_interface_is_sql_stopping(););
  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_relay_log_renamed",
                  test_channel_service_interface_relay_log_renamed(););
  return 0;
}

typedef enum enum_before_commit_test_cases {
  NEGATIVE_CERTIFICATION,
  POSITIVE_CERTIFICATION_WITH_GTID,
  POSITIVE_CERTIFICATION_WITHOUT_GTID,
  INVALID_CERTIFICATION_OUTCOME
} before_commit_test_cases;

int before_commit_tests(Trans_param *param,
                        before_commit_test_cases test_case)
{
  rpl_sid fake_sid;
  rpl_sidno fake_sidno;
  rpl_gno fake_gno;

  Transaction_termination_ctx transaction_termination_ctx;
  memset(&transaction_termination_ctx, 0, sizeof(transaction_termination_ctx));
  transaction_termination_ctx.m_thread_id= param->thread_id;

  switch(test_case)
  {
  case NEGATIVE_CERTIFICATION:
    transaction_termination_ctx.m_rollback_transaction= TRUE;
    transaction_termination_ctx.m_generated_gtid= FALSE;
    transaction_termination_ctx.m_sidno= -1;
    transaction_termination_ctx.m_gno= -1;
    break;

  case POSITIVE_CERTIFICATION_WITH_GTID:
    fake_sid.parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
    fake_sidno= get_sidno_from_global_sid_map(fake_sid);
    fake_gno= get_last_executed_gno(fake_sidno);
    fake_gno++;

    transaction_termination_ctx.m_rollback_transaction= FALSE;
    transaction_termination_ctx.m_generated_gtid= TRUE;
    transaction_termination_ctx.m_sidno= fake_sidno;
    transaction_termination_ctx.m_gno= fake_gno;
    break;

  case POSITIVE_CERTIFICATION_WITHOUT_GTID:
    transaction_termination_ctx.m_rollback_transaction= FALSE;
    transaction_termination_ctx.m_generated_gtid= FALSE;
    transaction_termination_ctx.m_sidno= 0;
    transaction_termination_ctx.m_gno= 0;
    break;

  case INVALID_CERTIFICATION_OUTCOME:
    transaction_termination_ctx.m_rollback_transaction= TRUE;
    transaction_termination_ctx.m_generated_gtid= TRUE;
    transaction_termination_ctx.m_sidno= -1;
    transaction_termination_ctx.m_gno= -1;

  default:
    break;
  }

  if (set_transaction_ctx(transaction_termination_ctx))
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_ERROR_LEVEL,
                          "Unable to update transaction context service on server, thread_id: %lu",
                          param->thread_id);
    return 1;
  }

  return 0;
}

int trans_before_commit(Trans_param *param)
{
  trans_before_commit_call++;

  DBUG_EXECUTE_IF("force_error_on_before_commit_listener",
                  return 1;);

  DBUG_EXECUTE_IF("force_negative_certification_outcome",
                  return before_commit_tests(param, NEGATIVE_CERTIFICATION););

  DBUG_EXECUTE_IF("force_positive_certification_outcome_without_gtid",
                  return before_commit_tests(param, POSITIVE_CERTIFICATION_WITHOUT_GTID););

  DBUG_EXECUTE_IF("force_positive_certification_outcome_with_gtid",
                  return before_commit_tests(param, POSITIVE_CERTIFICATION_WITH_GTID););

  DBUG_EXECUTE_IF("force_invalid_certification_outcome",
                  return before_commit_tests(param, INVALID_CERTIFICATION_OUTCOME););

  return 0;
}

int trans_before_rollback(Trans_param *param)
{
  trans_before_rollback_call++;

  return 0;
}

int trans_after_commit(Trans_param *param)
{
  trans_after_commit_call++;

  return 0;
}

int trans_after_rollback(Trans_param *param)
{
  trans_after_rollback_call++;

  DBUG_EXECUTE_IF("validate_replication_observers_plugin_server_requirements",
                  return validate_plugin_server_requirements(param););

  return 0;
}

Trans_observer trans_observer = {
  sizeof(Trans_observer),

  trans_before_dml,
  trans_before_commit,
  trans_before_rollback,
  trans_after_commit,
  trans_after_rollback,
};

/*
  Binlog relay IO events observers.
*/
static int binlog_relay_thread_start_call= 0;
static int binlog_relay_thread_stop_call= 0;
static int binlog_relay_applier_start_call= 0;
static int binlog_relay_applier_stop_call= 0;
static int binlog_relay_before_request_transmit_call= 0;
static int binlog_relay_after_read_event_call= 0;
static int binlog_relay_after_queue_event_call= 0;
static int binlog_relay_after_reset_slave_call= 0;

static void dump_binlog_relay_calls()
{
  if (binlog_relay_thread_start_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_thread_start");
  }

  if (binlog_relay_thread_stop_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_thread_stop");
  }

  if (binlog_relay_applier_start_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_applier_start");
  }

  if (binlog_relay_applier_stop_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_applier_stop");
  }

  if (binlog_relay_before_request_transmit_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_before_request_transmit");
  }

  if (binlog_relay_after_read_event_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_after_read_event");
  }

  if (binlog_relay_after_queue_event_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_after_queue_event");
  }

  if (binlog_relay_after_reset_slave_call)
  {
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "\nreplication_observers_example_plugin:binlog_relay_after_reset_slave");
  }
}

int binlog_relay_thread_start(Binlog_relay_IO_param *param)
{
  binlog_relay_thread_start_call++;

  return 0;
}

int binlog_relay_thread_stop(Binlog_relay_IO_param *param)
{
  binlog_relay_thread_stop_call++;

  return 0;
}

int binlog_relay_applier_start(Binlog_relay_IO_param *param)
{
  binlog_relay_applier_start_call++;
  return 0;
}

int binlog_relay_applier_stop(Binlog_relay_IO_param *param,
                              bool aborted)
{
  binlog_relay_applier_stop_call++;
  thread_aborted = aborted;
  return 0;
}

int binlog_relay_before_request_transmit(Binlog_relay_IO_param *param,
                                         uint32 flags)
{
  binlog_relay_before_request_transmit_call++;

  return 0;
}

int binlog_relay_after_read_event(Binlog_relay_IO_param *param,
                                  const char *packet, unsigned long len,
                                  const char **event_buf, unsigned long *event_len)
{
  binlog_relay_after_read_event_call++;

  return 0;
}

int binlog_relay_after_queue_event(Binlog_relay_IO_param *param,
                                   const char *event_buf,
                                   unsigned long event_len,
                                   uint32 flags)
{
  binlog_relay_after_queue_event_call++;

  return 0;
}

int binlog_relay_after_reset_slave(Binlog_relay_IO_param *param)
{
  binlog_relay_after_reset_slave_call++;

  return 0;
}

Binlog_relay_IO_observer relay_io_observer = {
  sizeof(Binlog_relay_IO_observer),

  binlog_relay_thread_start,
  binlog_relay_thread_stop,
  binlog_relay_applier_start,
  binlog_relay_applier_stop,
  binlog_relay_before_request_transmit,
  binlog_relay_after_read_event,
  binlog_relay_after_queue_event,
  binlog_relay_after_reset_slave,
};


/*
  Validate plugin requirements on server code.
  This function is mainly to ensure that any change on server code
  will not break Group Replication requirements.
*/
int validate_plugin_server_requirements(Trans_param *param)
{
  int success= 0;

  /*
    Instantiate a Gtid_log_event without a THD parameter.
  */
  rpl_sid fake_sid;
  fake_sid.parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
  rpl_sidno fake_sidno= get_sidno_from_global_sid_map(fake_sid);
  rpl_gno fake_gno= get_last_executed_gno(fake_sidno)+1;

  Gtid gtid= { fake_sidno, fake_gno };
  Gtid_specification gtid_spec= { GTID_GROUP, gtid };
  Gtid_log_event *gle=
    new Gtid_log_event(param->server_id, true, 0, 1, true, gtid_spec);

  if (gle->is_valid())
    success++;
  else
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "replication_observers_example_plugin:validate_plugin_server_requirements:"
                          " failed to instantiate a Gtid_log_event");
  delete gle;


  /*
    Instantiate a anonymous Gtid_log_event without a THD parameter.
  */
  Gtid_specification anonymous_gtid_spec= { ANONYMOUS_GROUP, gtid };
  gle=
    new Gtid_log_event(param->server_id, true, 0, 1, true, anonymous_gtid_spec);

  if (gle->is_valid())
    success++;
  else
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "replication_observers_example_plugin:validate_plugin_server_requirements:"
                          " failed to instantiate a anonymous Gtid_log_event");
  delete gle;


  /*
    Instantiate a Transaction_context_log_event.
  */
  Transaction_context_log_event *tcle= new Transaction_context_log_event(param->server_uuid,
                                                                         true,
                                                                         param->thread_id,
                                                                         false);

  if (tcle->is_valid())
  {
    Gtid_set *snapshot_version= tcle->get_snapshot_version();
    size_t snapshot_version_len= snapshot_version->get_encoded_length();
    uchar* snapshot_version_buf= (uchar *)my_malloc(PSI_NOT_INSTRUMENTED,
                                                    snapshot_version_len, MYF(0));
    snapshot_version->encode(snapshot_version_buf);
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "snapshot version is '%s'",
                          snapshot_version_buf);
    my_free(snapshot_version_buf);
    success++;
  }
  else
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "replication_observers_example_plugin:validate_plugin_server_requirements:"
                          " failed to instantiate a Transaction_context_log_event");
  delete tcle;


  /*
    Instantiate a View_Change_log_event.
  */
  View_change_log_event *vcle= new View_change_log_event(const_cast<char*>("1421867646:1"));

  if (vcle->is_valid())
  {
    success++;
  }
  else
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "replication_observers_example_plugin:validate_plugin_server_requirements:"
                          " failed to instantiate a View_change_log_event");
  delete vcle;


  /*
    include/mysql/group_replication_priv.h exported functions.
  */
  my_thread_attr_t *thread_attr= get_connection_attrib();

  char *hostname, *uuid;
  uint port;
  unsigned int server_version;
  st_server_ssl_variables server_ssl_variables=
      {false,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};

  get_server_parameters(&hostname, &port, &uuid, &server_version,
                        &server_ssl_variables);

  Trans_context_info startup_pre_reqs;
  get_server_startup_prerequirements(startup_pre_reqs, false);

  //check the server is initialized by checking if the default channel exists
  bool server_engine_ready= channel_is_active("", CHANNEL_NO_THD);

  uchar *encoded_gtid_executed= NULL;
  size_t length;
  get_server_encoded_gtid_executed(&encoded_gtid_executed, &length);

#if !defined(DBUG_OFF)
  char *encoded_gtid_executed_string=
      encoded_gtid_set_to_string(encoded_gtid_executed, length);
#endif

  if (thread_attr != NULL &&
      hostname != NULL &&
      uuid != NULL &&
      port > 0 &&
      startup_pre_reqs.gtid_mode == 3 &&
      server_engine_ready &&
      encoded_gtid_executed != NULL
#if !defined(DBUG_OFF)
      && encoded_gtid_executed_string != NULL
#endif
     )
    success++;
  else
    my_plugin_log_message(&plugin_info_ptr,
                          MY_INFORMATION_LEVEL,
                          "replication_observers_example_plugin:validate_plugin_server_requirements:"
                          " failed to invoke group_replication_priv.h exported functions");

#if !defined(DBUG_OFF)
  my_free(encoded_gtid_executed_string);
#endif
  my_free(encoded_gtid_executed);


  /*
    Log number of successful validations.
  */
  my_plugin_log_message(&plugin_info_ptr,
                        MY_INFORMATION_LEVEL,
                        "\nreplication_observers_example_plugin:validate_plugin_server_requirements=%d",
                        success);

  return 0;
}

int test_channel_service_interface_initialization()
{
    int error= initialize_channel_service_interface();
    DBUG_ASSERT(error);
    return error;
}

int test_channel_service_interface()
{
    //The initialization method should return OK
    int error= initialize_channel_service_interface();
    DBUG_ASSERT(!error);

    //Test channel creation
    char interface_channel[]= "example_channel";
    Channel_creation_info info;
    initialize_channel_creation_info(&info);
    error= channel_create(interface_channel, &info);
    DBUG_ASSERT(!error);

    //Assert the channel exists
    bool exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
    DBUG_ASSERT(exists);

    //Check that a non existing channel is declared as such
    char dummy_channel[]= "dummy_channel";
    exists= channel_is_active(dummy_channel, CHANNEL_NO_THD);
    DBUG_ASSERT(!exists);

    //Test that we cannot create a empty named channel (the default channel)
    char empty_interface_channel[]= "";
    initialize_channel_creation_info(&info);
    error= channel_create(empty_interface_channel, &info);
    DBUG_ASSERT(error == RPL_CHANNEL_SERVICE_DEFAULT_CHANNEL_CREATION_ERROR);

    //Start the applier thread (since it does not need an external server)
    Channel_connection_info connection_info;
    initialize_channel_connection_info(&connection_info);
    error= channel_start(interface_channel,
                         &connection_info,
                         CHANNEL_APPLIER_THREAD,
                         true);
    DBUG_ASSERT(!error);

    //Assert that the applier thread is running
    bool running= channel_is_active(interface_channel, CHANNEL_APPLIER_THREAD);
    DBUG_ASSERT(running);

    //Wait for execution of events (none in this case so it should return OK)
    error= channel_wait_until_apply_queue_applied(interface_channel, 100000);
    DBUG_ASSERT(!error);

    //Get the last delivered gno (should be 0)
    rpl_sid fake_sid;
    fake_sid.parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
    rpl_sidno fake_sidno= get_sidno_from_global_sid_map(fake_sid);
    rpl_gno gno= channel_get_last_delivered_gno(interface_channel, fake_sidno);
    DBUG_ASSERT(gno == 0);

    //Check that for non existing channels it returns the corresponding error
    gno= channel_get_last_delivered_gno(dummy_channel, fake_sidno);
    DBUG_ASSERT(gno == RPL_CHANNEL_SERVICE_CHANNEL_DOES_NOT_EXISTS_ERROR);

    //Extract the applier id
    long unsigned int * applier_id= NULL;
    channel_get_thread_id(interface_channel,
                          CHANNEL_APPLIER_THREAD,
                          &applier_id);
    DBUG_ASSERT(*applier_id > 0);
    my_free(applier_id);

    DBUG_ASSERT(binlog_relay_applier_stop_call==0);

    //Stop the channel applier
    error= channel_stop(interface_channel,
                        3,
                        10000);
    DBUG_ASSERT(!error);
    //Repeat the stop to check it goes ok
    error= channel_stop(interface_channel,
                        3,
                        10000);
    DBUG_ASSERT(!error);

    DBUG_ASSERT(binlog_relay_applier_stop_call>0);
    DBUG_ASSERT(!thread_aborted);

    //Assert that the applier thread is not running
    running= channel_is_active(interface_channel, CHANNEL_APPLIER_THREAD);
    DBUG_ASSERT(!running);

    //Purge the channel and assert all is OK
    error= channel_purge_queue(interface_channel, true);
    DBUG_ASSERT(!error);

    //Assert the channel is not there.
    exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
    DBUG_ASSERT(!exists);

    //Check that a queue in an empty channel will fail.
    char empty_event[]= "";
    error= channel_queue_packet(dummy_channel, empty_event, 0);
    DBUG_ASSERT(error);

    //Test a multi thread channel
    info.channel_mts_parallel_type= CHANNEL_MTS_PARALLEL_TYPE_LOGICAL_CLOCK;
    info.channel_mts_parallel_workers= 3;

    error= channel_create(interface_channel, &info);
    DBUG_ASSERT(!error);

    //Assert the channel exists
    exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
    DBUG_ASSERT(exists);

    error= channel_start(interface_channel,
                         &connection_info,
                         CHANNEL_APPLIER_THREAD,
                         true);
    DBUG_ASSERT(!error);

    //Extract the applier ids
    applier_id= NULL;
    int num_appliers= channel_get_thread_id(interface_channel,
                                            CHANNEL_APPLIER_THREAD,
                                            &applier_id);
    DBUG_ASSERT(num_appliers == 4);

    unsigned long thread_id= 0;
    for (int i = 0; i < num_appliers; i++)
    {
      thread_id= applier_id[i];
      DBUG_ASSERT(thread_id > 0);
    }
    my_free(applier_id);

    //Stop the channel applier
    error= channel_stop(interface_channel,
                        3,
                        10000);
    DBUG_ASSERT(!error);

    //Purge the channel and assert all is OK
    error= channel_purge_queue(interface_channel, true);
    DBUG_ASSERT(!error);

    //Assert the channel is not there.
    exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
    DBUG_ASSERT(!exists);

    return (error && exists && running && gno && num_appliers && thread_id);
}

int test_channel_service_interface_io_thread()
{
  //The initialization method should return OK
  int error= initialize_channel_service_interface();
  DBUG_ASSERT(!error);

  char interface_channel[]= "example_channel";

  //Assert the channel exists
  bool exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
  DBUG_ASSERT(exists);

  //Assert that the receiver is running
  bool running= channel_is_active(interface_channel, CHANNEL_RECEIVER_THREAD);
  DBUG_ASSERT(running);

  //Extract the receiver id
  long unsigned int * thread_id= NULL;
  int num_threads= channel_get_thread_id(interface_channel,
                                         CHANNEL_RECEIVER_THREAD,
                                         &thread_id);
  DBUG_ASSERT(num_threads == 1);
  DBUG_ASSERT(*thread_id > 0);
  my_free(thread_id);

  //Get the I/O thread retrieved GTID set
  char *retrieved_gtid_set;
  error= channel_get_retrieved_gtid_set(interface_channel,
                                        &retrieved_gtid_set);
  DBUG_ASSERT(!error);
  DBUG_ASSERT(strlen(retrieved_gtid_set) > 0);
  my_free(retrieved_gtid_set);

  //Check that the applier thread is waiting for events to be queued.
  int is_waiting= channel_is_applier_waiting(interface_channel);
  DBUG_ASSERT(is_waiting == 1);

  //Stop the channel
  error= channel_stop(interface_channel,
                      3,
                      10000);
  DBUG_ASSERT(!error);

  //Assert that the receiver thread is not running
  running= channel_is_active(interface_channel, CHANNEL_RECEIVER_THREAD);
  DBUG_ASSERT(!running);

  return (error && exists && running && num_threads && is_waiting);
}

bool test_channel_service_interface_is_io_stopping()
{
  //The initialization method should return OK
  int error= initialize_channel_service_interface();
  DBUG_ASSERT(!error);

  //Initialize the channel to be used with the channel service interface
  char interface_channel[]= "example_channel";
  Channel_creation_info info;
  initialize_channel_creation_info(&info);
  error= channel_create(interface_channel, &info);
  DBUG_ASSERT(!error);

  //Reset the I/O stop counter
  binlog_relay_thread_stop_call= 0;

  //Unregister the thread stop hook
  error= unregister_binlog_relay_io_observer(&relay_io_observer,
                                             (void *)plugin_info_ptr);
  DBUG_ASSERT(!error);

  //Start the I/O thread
  Channel_connection_info connection_info;
  initialize_channel_connection_info(&connection_info);
  error= channel_start(interface_channel,
                       &connection_info,
                       CHANNEL_RECEIVER_THREAD,
                       true);
  DBUG_ASSERT(!error);

  //Assert the channel exists
  bool exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
  DBUG_ASSERT(exists);

  //Wait until I/O thread reached the error and is going to stop
  DBUG_EXECUTE_IF("pause_after_io_thread_stop_hook",
                  {
                    const char act[]= "now "
                                      "WAIT_FOR reached_stopping_io_thread";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  //Register the thread stop hook again
  error= register_binlog_relay_io_observer(&relay_io_observer,
                                           (void *)plugin_info_ptr);
  DBUG_ASSERT(!error);

  //Assert that the receiver is stopping
  bool io_stopping= channel_is_stopping(interface_channel, CHANNEL_RECEIVER_THREAD);
  DBUG_ASSERT(io_stopping);

  //Assert that the receiver is running
  bool io_running= channel_is_active(interface_channel, CHANNEL_RECEIVER_THREAD);
  DBUG_ASSERT(io_running);

  //Signal to make the MTR test case to start monitoring the I/O thread
  DBUG_EXECUTE_IF("pause_after_io_thread_stop_hook",
                  {
                    const char act[]= "now "
                                      "SIGNAL reached_io_thread_started";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  DBUG_EXECUTE_IF("pause_after_io_thread_stop_hook",
                  {
                    const char act[]= "now SIGNAL continue_to_stop_io_thread";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  // The plug-in has missed the stop
  DBUG_ASSERT(binlog_relay_thread_stop_call==0);

  return (error | exists | io_stopping | io_running);
}

bool test_channel_service_interface_is_sql_stopping()
{
  //The initialization method should return OK
  int error= initialize_channel_service_interface();
  DBUG_ASSERT(!error);

  //Initialize the channel to be used with the channel service interface
  char interface_channel[]= "example_channel";
  Channel_creation_info info;
  initialize_channel_creation_info(&info);
  error= channel_create(interface_channel, &info);
  DBUG_ASSERT(!error);

  //Assert the channel exists
  bool exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
  DBUG_ASSERT(exists);

  //Unregister the thread stop hook
  error= unregister_binlog_relay_io_observer(&relay_io_observer,
                                             (void *)plugin_info_ptr);
  DBUG_ASSERT(!error);

  //Start the I/O thread
  Channel_connection_info connection_info;
  initialize_channel_connection_info(&connection_info);
  error= channel_start(interface_channel,
                       &connection_info,
                       CHANNEL_RECEIVER_THREAD,
                       true);
  DBUG_ASSERT(!error);

  //Start the SQL thread
  error= channel_start(interface_channel,
                       &connection_info,
                       CHANNEL_APPLIER_THREAD,
                       true);
  DBUG_ASSERT(!error);

  //Wait until SQL thread reached the error and is going to stop
  DBUG_EXECUTE_IF("pause_after_sql_thread_stop_hook",
                  {
                    const char act[]= "now "
                                      "WAIT_FOR reached_stopping_sql_thread";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  //Register the thread stop hook again
  error= register_binlog_relay_io_observer(&relay_io_observer,
                                           (void *)plugin_info_ptr);
  DBUG_ASSERT(!error);

  //Assert that the applier is stopping
  bool sql_stopping= channel_is_stopping(interface_channel, CHANNEL_APPLIER_THREAD);
  DBUG_ASSERT(sql_stopping);

  //Assert that the applier is running
  bool sql_running= channel_is_active(interface_channel, CHANNEL_APPLIER_THREAD);
  DBUG_ASSERT(sql_running);

  //Signal to make the MTR test case to start monitoring the SQL thread
  DBUG_EXECUTE_IF("pause_after_sql_thread_stop_hook",
                  {
                    const char act[]= "now "
                                      "SIGNAL reached_sql_thread_started";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  DBUG_EXECUTE_IF("pause_after_sql_thread_stop_hook",
                  {
                    const char act[]= "now SIGNAL continue_to_stop_sql_thread";
                    DBUG_ASSERT(!debug_sync_set_action(current_thd,
                                                       STRING_WITH_LEN(act)));
                  };);

  //The plug-in has missed the stop
  DBUG_ASSERT(binlog_relay_applier_stop_call==0);

  return (error | exists | sql_stopping | sql_running);
}

bool test_channel_service_interface_relay_log_renamed()
{
  //The initialization method should return OK
  int error= initialize_channel_service_interface();
  DBUG_ASSERT(!error);

  //Initialize the channel to be used with the channel service interface
  char interface_channel[]= "example_channel";
  char channel_hostname[]= "127.0.0.1";
  char channel_user[]= "root";
  Channel_creation_info info;
  initialize_channel_creation_info(&info);
  info.preserve_relay_logs= true;
  info.hostname= channel_hostname;
  info.user= channel_user;
  error= channel_create(interface_channel, &info);
  DBUG_ASSERT(!error);

  //Assert the channel exists
  bool exists= channel_is_active(interface_channel, CHANNEL_NO_THD);
  DBUG_ASSERT(exists);

  //Start the SQL thread
  Channel_connection_info connection_info;
  initialize_channel_connection_info(&connection_info);
  error= channel_start(interface_channel,
                       &connection_info,
                       CHANNEL_APPLIER_THREAD,
                       true);

  if (error)
  {
    THD *thd= current_thd;
    thd->clear_error();
#if !defined(DBUG_OFF)
    const char act[]= "now SIGNAL reached_sql_thread_startup_failed";
    DBUG_ASSERT(!debug_sync_set_action(thd, STRING_WITH_LEN(act)));
#endif
  }
  else
  {
#if !defined(DBUG_OFF)
    const char act[]= "now SIGNAL reached_sql_thread_started";
    DBUG_ASSERT(!debug_sync_set_action(current_thd, STRING_WITH_LEN(act)));
#endif
  }

  return (error | exists);
}

/*
  Initialize the Replication Observer example at server start or plugin
  installation.

  SYNOPSIS
    replication_observers_example_plugin_init()

  DESCRIPTION
    Registers Server state observer and Transaction Observer

  RETURN VALUE
    0                    success
    1                    failure (cannot happen)
*/

static int replication_observers_example_plugin_init(MYSQL_PLUGIN plugin_info)
{
  plugin_info_ptr= plugin_info;

  DBUG_ENTER("replication_observers_example_plugin_init");

  if(register_server_state_observer(&server_state_observer, (void *)plugin_info_ptr))
  {
    my_plugin_log_message(&plugin_info_ptr, MY_ERROR_LEVEL, "Failure in registering the server state observers");
    DBUG_RETURN(1);
  }

  if (register_trans_observer(&trans_observer, (void *)plugin_info_ptr))
  {
    my_plugin_log_message(&plugin_info_ptr, MY_ERROR_LEVEL,"Failure in registering the transactions state observers");
    DBUG_RETURN(1);
  }

  if (register_binlog_relay_io_observer(&relay_io_observer, (void *)plugin_info_ptr))
  {
    my_plugin_log_message(&plugin_info_ptr, MY_ERROR_LEVEL,"Failure in registering the relay io observer");
    DBUG_RETURN(1);
  }

  my_plugin_log_message(&plugin_info_ptr, MY_INFORMATION_LEVEL,"replication_observers_example_plugin: init finished");

  DBUG_RETURN(0);
}


/*
  Terminate the Replication Observer example at server shutdown or
  plugin deinstallation.

  SYNOPSIS
    replication_observers_example_plugin_deinit()

  DESCRIPTION
    Unregisters Server state observer and Transaction Observer

  RETURN VALUE
    0                    success
    1                    failure (cannot happen)

*/

static int replication_observers_example_plugin_deinit(void *p)
{
  DBUG_ENTER("replication_observers_example_plugin_deinit");

  dump_server_state_calls();
  dump_transaction_calls();
  dump_binlog_relay_calls();

  if (unregister_server_state_observer(&server_state_observer, p))
  {
    my_plugin_log_message(&p, MY_ERROR_LEVEL,"Failure in unregistering the server state observers");
    DBUG_RETURN(1);
  }

  if (unregister_trans_observer(&trans_observer, p))
  {
    my_plugin_log_message(&p, MY_ERROR_LEVEL,"Failure in unregistering the transactions state observers");
    DBUG_RETURN(1);
  }

  if (unregister_binlog_relay_io_observer(&relay_io_observer, p))
  {
    my_plugin_log_message(&p, MY_ERROR_LEVEL,"Failure in unregistering the relay io observer");
    DBUG_RETURN(1);
  }

  my_plugin_log_message(&p, MY_INFORMATION_LEVEL,"replication_observers_example_plugin: deinit finished");

  DBUG_RETURN(0);
}

/*
  Plugin library descriptor
*/
struct Mysql_replication replication_observers_example_plugin=
{ MYSQL_REPLICATION_INTERFACE_VERSION };

mysql_declare_plugin(replication_observers_example)
{
  MYSQL_REPLICATION_PLUGIN,
  &replication_observers_example_plugin,
  "replication_observers_example",
  "ORACLE",
  "Replication observer infrastructure example.",
  PLUGIN_LICENSE_GPL,
  replication_observers_example_plugin_init, /* Plugin Init */
  replication_observers_example_plugin_deinit, /* Plugin Deinit */
  0x0100 /* 1.0 */,
  NULL,                       /* status variables                */
  NULL,                       /* system variables                */
  NULL,                       /* config options                  */
  0,                          /* flags                           */
}
mysql_declare_plugin_end;
