Mysql GTID replication fails when data inserted

If you have a master and a slave configured and you insert
new data into the master, it will cause subsequent replica
create to fail.

The problem is that we weren't setting the gtid_purged variable
using the metadata in the xtrabackup_binlog_info file. The
Mysql GTID replication strategy was adjusted to account for this.

A release note has been added.

The replication scenario tests were enhanced to validate this
issue.

Note: this issue doesn't occur with MariaDB GTID replication
because it mechanism is different.

Scenario tests were run succesfully on Mysql, Percona and
MariaDB with this change in place.

Change-Id: I66c8b6278afa50ba14e4bb7888e3a25dc657a9e4
Closes-bug: 1563574
This commit is contained in:
Doug Shelley 2016-03-31 14:42:54 +00:00 committed by amrith
parent a00ad54aea
commit 09a312ae3a
4 changed files with 56 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
fixes:
- Fixes an issue with a failure to establish a new replica for MySQL
in some cases where a replica already exists and some data has
been inserted into the master. Bug 1563574

View File

@ -13,11 +13,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
import csv
from oslo_log import log as logging from oslo_log import log as logging
from trove.common import cfg from trove.common import cfg
from trove.common import exception
from trove.common.i18n import _
from trove.guestagent.backup.backupagent import BackupAgent from trove.guestagent.backup.backupagent import BackupAgent
from trove.guestagent.common import operating_system
from trove.guestagent.common.operating_system import FileMode
from trove.guestagent.datastore.mysql.service import MySqlApp
from trove.guestagent.strategies.replication import mysql_base from trove.guestagent.strategies.replication import mysql_base
AGENT = BackupAgent() AGENT = BackupAgent()
@ -29,7 +35,21 @@ LOG = logging.getLogger(__name__)
class MysqlGTIDReplication(mysql_base.MysqlReplicationBase): class MysqlGTIDReplication(mysql_base.MysqlReplicationBase):
"""MySql Replication coordinated by GTIDs.""" """MySql Replication coordinated by GTIDs."""
class UnableToDetermineLastMasterGTID(exception.TroveError):
message = _("Unable to determine last GTID executed on master "
"(from file %(binlog_file)s).")
def connect_to_master(self, service, snapshot): def connect_to_master(self, service, snapshot):
if 'dataset' in snapshot:
# pull the last executed GTID from the master via
# the xtrabackup metadata file. If that value is
# provided we need to set the gtid_purged variable
# before executing the CHANGE MASTER TO command
last_gtid = self._read_last_master_gtid()
if last_gtid:
set_gtid_cmd = "SET GLOBAL gtid_purged='%s'" % last_gtid
service.execute_on_client(set_gtid_cmd)
logging_config = snapshot['log_position'] logging_config = snapshot['log_position']
LOG.debug("connect_to_master %s" % logging_config['replication_user']) LOG.debug("connect_to_master %s" % logging_config['replication_user'])
change_master_cmd = ( change_master_cmd = (
@ -47,3 +67,18 @@ class MysqlGTIDReplication(mysql_base.MysqlReplicationBase):
}) })
service.execute_on_client(change_master_cmd) service.execute_on_client(change_master_cmd)
service.start_slave() service.start_slave()
def _read_last_master_gtid(self):
INFO_FILE = ('%s/xtrabackup_binlog_info' % MySqlApp.get_data_dir())
LOG.info(_("Setting read permissions on %s") % INFO_FILE)
operating_system.chmod(INFO_FILE, FileMode.ADD_READ_ALL, as_root=True)
LOG.info(_("Reading last master GTID from %s") % INFO_FILE)
try:
with open(INFO_FILE, 'rb') as f:
row = csv.reader(f, delimiter='\t',
skipinitialspace=True).next()
return row[2]
except (IOError, IndexError) as ex:
LOG.exception(ex)
raise self.UnableToDetermineLastMasterGTID(
{'binlog_file': INFO_FILE})

View File

@ -46,6 +46,16 @@ class ReplicationGroup(TestGroup):
self.test_runner.run_create_single_replica() self.test_runner.run_create_single_replica()
@test(runs_after=[create_single_replica]) @test(runs_after=[create_single_replica])
def add_data_after_replica(self):
"""Add data to master after initial replica is setup"""
self.test_runner.run_add_data_after_replica()
@test(runs_after=[add_data_after_replica])
def verify_replica_data_after_single(self):
"""Verify data exists on single replica"""
self.test_runner.run_verify_replica_data_after_single()
@test(runs_after=[verify_replica_data_after_single])
def create_multiple_replicas(self): def create_multiple_replicas(self):
"""Test creating multiple replicas.""" """Test creating multiple replicas."""
self.test_runner.run_create_multiple_replicas() self.test_runner.run_create_multiple_replicas()

View File

@ -43,6 +43,9 @@ class ReplicationRunner(TestRunner):
self.test_helper.add_data(data_type, host) self.test_helper.add_data(data_type, host)
self.used_data_sets.add(data_type) self.used_data_sets.add(data_type)
def run_add_data_after_replica(self, data_type=DataType.micro):
self.assert_add_replication_data(data_type, self.master_host)
def run_verify_data_for_replication(self, data_type=DataType.small): def run_verify_data_for_replication(self, data_type=DataType.small):
self.assert_verify_replication_data(data_type, self.master_host) self.assert_verify_replication_data(data_type, self.master_host)
@ -124,6 +127,9 @@ class ReplicationRunner(TestRunner):
self.report.log("Checking data on host %s" % host) self.report.log("Checking data on host %s" % host)
self.assert_verify_replication_data(data_type, host) self.assert_verify_replication_data(data_type, host)
def run_verify_replica_data_after_single(self):
self.assert_verify_replica_data(self.instance_info.id, DataType.micro)
def run_verify_replica_data_new(self): def run_verify_replica_data_new(self):
self.assert_verify_replica_data(self.instance_info.id, DataType.tiny) self.assert_verify_replica_data(self.instance_info.id, DataType.tiny)