Image

Imagecpplang 😦busy

Category:

Тестирование клиента MySQL

Один из наших сервисов использует самописный клиент MySQL,
который осуществляет периодический сброс статистики в базу.
Его задача очень простая:
1. проверить состояние соединения с СУБД.
2. если всё Ok, выполнить необходимые запросы.
3. ждать в течение заданного таймаута и повторить п.1,2.
Клиент и сервер MySQL находятся на разных хостах.
Функционал клиента выглядит примерно так:

static MYSQL *db;

static void db_disconnect() {
  if (db == NULL) return;
  mysql_close(db);
  db = NULL;
}

static int db_connect() {
  if (db)
    return 1;
  db = mysql_init(NULL);
  if (db == NULL) {
    log_w("Could not initialize db");
    return 0;
  }
  if (mysql_real_connect(db, ..., NULL, 0) == NULL){
    log_w("Connect MySQL failed: %s\n", mysql_error(db));
    db_disconnect();
    return 0;
  }
  log_w("DB (%s@%s) connect OK", ssrv_config.db_user, ssrv_config.db_name);
  return 1;
}

static int check_db() {
  if (db && (mysql_stat(db) == NULL)){
    log_w("DBconnection closed");
    db_disconnect();
  }
  if (!db)
    db_connect();
  if (!db)return 0;
    return 1;
}

void flush_stat() {
  if (!check_db())
    return;
  for (...){
    // SQL queries
    ...
  }
}

План тестирования.
1. Ограничения со стороны СУБД.
  1.1. Отобрать привилегии у пользователя СУБД и убедиться, что база становится недоступной клиенту.
  1.2. Назначить все необходимые привилегии пользователю посредством и убедиться, что соединение с базой восстановлено.

2. Ограничение со стороны FW.
В качестве FW использовался iptables.
Необходимо закрывать/открывать порт соединения с базой и исследовать результаты.
  2.1. Порт закрывается до работы клиента.
  2.2. Порт закрывается/открывается во время работы клиента.

Основные проблемы обнаружились в MySQL-API.
Смотрим ещё раз на check_db();

static int check_db() {
  if (db && (mysql_stat(db) == NULL)){
    log_w("DBconnection closed");
    db_disconnect();
  }
  if (!db)
    db_connect();
  if (!db)
    return 0;
  return 1;
}

Проблема первая: mysql_stat возвращает результат на момент соединения и далее не меняет своего значения.
Замена mysql_stat на mysql_ping привела к зависанию из-за большого таймаута соединения (mysql_ping, как известно, пытается восстанавливать соединение самостоятельно) на условии проверки.

Очевидным решением казалось применение опции таймаута соединения:

#define DBCONNECTION_TIMEOUT 5
...
static int db_connect() {
  unsigned int timeout = DBCONNECTION_TIMEOUT;
  ...
  if(mysql_options(db, MYSQL_OPT_CONNECT_TIMEOUT, &timeout)){
    log_w("Set options MySQL failed: %s\n", mysql_error(db));
  }
  ...
}


Но увы, это породило другую проблему:
В случае, когда порт закрывался до запуска клиента таймаут соединения действительно был равен заданному. Но при проверке п.2.2. клиент опять зависал на строке проверки mysql_ping.

В результате пришлось оторвать проверку напрочь, а функция flash_stat() стала выглядеть следующим образом:

void flush_stat() {
  if (!db_connect())
    return;
  for (...) {
    // SQL queries
    ...
  }
  db_disconnect();
}

Поскольку периодичность запуска клиента большая, подключение к базе на каждом срабатывании вполне допустимо.

Результаты тестирования.
1. Тестирование доступа к базе на стороне MySQL Server:
1.1.REVOKE ALL PRIVILEGES ON database FROM 'user'@'host';
  Записи в логе клиента:
  [28.05.2010 19:42:55] flush stat
  [28.05.2010 19:42:55] DBconnection closed
  [28.05.2010 19:43:00] Connect MySQL failed: Can't connect to MySQL server on 'host'

1.2.GRANT ALL PRIVILEGES ON reklama TO 'user'@'host'
  Записи в логе клиента:
  [28.05.2010 19:43:55] flush stat
  [28.05.2010 19:43:55] DB (user@database) connect OK
  [28.05.2010 19:43:55] flush stat OK

2. Тестирование через FW
2.1.
  Закрываем порт соединения с базой и запускаем клиент:
  /etc/init.d/iptables restart
  iptables -A OUTPUT -p TCP --dport 3306 -j DROP
  Проверяем, что получилось командой iptables -L -n -v
  Записи в логе клиента:
  [28.05.2010 19:42:55] flush stat
  [28.05.2010 19:42:55] DBconnection closed
  [28.05.2010 19:43:00] Connect MySQL failed: Can't connect to MySQL server on 'host'

  Открываем порт соединения с базой
  iptables -D OUTPUT -p TCP --dport 3306 -j DROP
  Записи в логе клиента:
  [28.05.2010 19:43:55] flush stat
  [28.05.2010 19:43:55] DB (user@database) connect OK
  [28.05.2010 19:43:55] flush stat OK

2.2 Повторяем п.2.1., но при работающем клиенте. Результат аналогичен предыдущему.

Мораль этой басни не нова: be careful, при использовании MySQL-API.