package com.zendesk.maxwell;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zendesk.maxwell.producer.MaxwellOutputConfig;
import com.zendesk.maxwell.replication.BinlogPosition;
import com.zendesk.maxwell.replication.Position;
import com.zendesk.maxwell.row.RowMap;
import com.zendesk.maxwell.schema.Schema;
import com.zendesk.maxwell.schema.SchemaCapturer;
import com.zendesk.maxwell.schema.SchemaStoreSchema;
import com.zendesk.maxwell.schema.ddl.ResolvedSchemaChange;
import com.zendesk.maxwell.schema.ddl.SchemaChange;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
public class MaxwellTestSupport {
static final Logger LOGGER = LoggerFactory.getLogger(MaxwellTestSupport.class);
public static MysqlIsolatedServer setupServer(String extraParams) throws Exception {
MysqlIsolatedServer server = new MysqlIsolatedServer();
server.boot(extraParams);
Connection conn = server.getConnection();
SchemaStoreSchema.ensureMaxwellSchema(conn, "maxwell");
SchemaStoreSchema.upgradeSchemaStoreSchema(conn);
return server;
}
public static MysqlIsolatedServer setupServer() throws Exception {
return setupServer(null);
}
public static void setupSchema(MysqlIsolatedServer server, boolean resetBinlogs) throws Exception {
List<String> queries = new ArrayList<String>(Arrays.asList(
"CREATE DATABASE if not exists shard_2",
"DROP DATABASE if exists shard_1",
"CREATE DATABASE shard_1",
"USE shard_1"
));
for ( File file: new File(getSQLDir() + "/schema").listFiles()) {
if ( !file.getName().endsWith(".sql"))
continue;
if ( file.getName().contains("sharded") )
continue;
byte[] sql = Files.readAllBytes(file.toPath());
String s = new String(sql);
if ( s != null ) {
queries.add(s);
}
}
String shardedFileName;
if ( server.getVersion().equals("5.6") )
shardedFileName = "sharded_56.sql";
else
shardedFileName = "sharded.sql";
File shardedFile = new File(getSQLDir() + "/schema/" + shardedFileName);
byte[] sql = Files.readAllBytes(shardedFile.toPath());
String s = new String(sql);
if ( s != null ) {
queries.add(s);
}
if ( resetBinlogs )
queries.add("RESET MASTER");
server.executeList(queries);
}
public static void setupSchema(MysqlIsolatedServer server) throws Exception {
setupSchema(server, true);
}
public static String getSQLDir() {
final String dir = System.getProperty("user.dir");
return dir + "/src/test/resources/sql/";
}
public static MaxwellContext buildContext(int port, Position p, MaxwellFilter filter) throws SQLException {
MaxwellConfig config = new MaxwellConfig();
config.replicationMysql.host = "127.0.0.1";
config.replicationMysql.port = port;
config.replicationMysql.user = "maxwell";
config.replicationMysql.password = "maxwell";
config.replicationMysql.jdbcOptions.add("useSSL=false");
config.maxwellMysql.host = "127.0.0.1";
config.maxwellMysql.port = port;
config.maxwellMysql.user = "maxwell";
config.maxwellMysql.password = "maxwell";
config.maxwellMysql.jdbcOptions.add("useSSL=false");
config.databaseName = "maxwell";
config.filter = filter;
config.initPosition = p;
return new MaxwellContext(config);
}
private static void clearSchemaStore(MysqlIsolatedServer mysql) throws Exception {
mysql.execute("drop database if exists maxwell");
}
public static List<RowMap> getRowsWithReplicator(final MysqlIsolatedServer mysql, MaxwellFilter filter, final String queries[], final String before[]) throws Exception {
MaxwellTestSupportCallback callback = new MaxwellTestSupportCallback() {
@Override
public void afterReplicatorStart(MysqlIsolatedServer mysql) throws SQLException {
mysql.executeList(Arrays.asList(queries));
}
@Override
public void beforeReplicatorStart(MysqlIsolatedServer mysql) throws SQLException {
if ( before != null)
mysql.executeList(Arrays.asList(before));
}
};
return getRowsWithReplicator(mysql, filter, callback, null);
}
public static boolean inGtidMode() {
return System.getenv(MaxwellConfig.GTID_MODE_ENV) != null;
}
public static Position capture(Connection c) throws SQLException {
return Position.capture(c, inGtidMode());
}
public static List<RowMap> getRowsWithReplicator(final MysqlIsolatedServer mysql, MaxwellFilter filter, MaxwellTestSupportCallback callback, MaxwellOutputConfig outputConfig) throws Exception {
final ArrayList<RowMap> list = new ArrayList<>();
clearSchemaStore(mysql);
MaxwellConfig config = new MaxwellConfig();
config.maxwellMysql.user = "maxwell";
config.maxwellMysql.password = "maxwell";
config.maxwellMysql.host = "localhost";
config.maxwellMysql.port = mysql.getPort();
config.maxwellMysql.jdbcOptions.add("useSSL=false");
config.replicationMysql = config.maxwellMysql;
if (outputConfig == null) {
outputConfig = new MaxwellOutputConfig();
}
if ( filter != null ) {
if ( filter.isDatabaseWhitelist() )
filter.includeDatabase("test");
if ( filter.isTableWhitelist() )
filter.includeTable("boundary");
}
config.filter = filter;
config.bootstrapperType = "sync";
callback.beforeReplicatorStart(mysql);
config.initPosition = capture(mysql.getConnection());
final String waitObject = new String("");
BufferedMaxwell maxwell = new BufferedMaxwell(config) {
@Override
protected void onReplicatorStart() {
synchronized(waitObject) {
waitObject.notify();
}
}
};
new Thread(maxwell).start();
synchronized(waitObject) { waitObject.wait(); }
callback.afterReplicatorStart(mysql);
maxwell.context.getPositionStore().heartbeat();
Position finalPosition = capture(mysql.getConnection());
LOGGER.debug("running replicator up to " + finalPosition);
Long pollTime = 2000L;
Position lastPositionRead = null;
for ( ;; ) {
RowMap row = maxwell.poll(pollTime);
pollTime = 500L; // after the first row is receive, we go into a tight loop.
if ( row == null ) {
LOGGER.debug("timed out waiting for final row. Last position we saw: " + lastPositionRead);
break;
}
lastPositionRead = row.getPosition();
if ( row.getPosition().newerThan(finalPosition) ) {
// consume whatever's left over in the buffer.
for ( ;; ) {
RowMap r = maxwell.poll(100);
if ( r == null )
break;
if ( r.toJSON(outputConfig) != null )
list.add(r);
}
break;
}
if ( row.toJSON(outputConfig) != null )
list.add(row);
}
callback.beforeTerminate(mysql);
maxwell.terminate();
Exception maxwellError = maxwell.context.getError();
if (maxwellError != null) {
throw maxwellError;
}
return list;
}
public static void testDDLFollowing(MysqlIsolatedServer server, String alters[]) throws Exception {
SchemaCapturer capturer = new SchemaCapturer(server.getConnection(), buildContext(server.getPort(), null, null).getCaseSensitivity());
Schema topSchema = capturer.capture();
server.executeList(Arrays.asList(alters));
ObjectMapper m = new ObjectMapper();
for ( String alterSQL : alters) {
List<SchemaChange> changes = SchemaChange.parse("shard_1", alterSQL);
if ( changes != null ) {
for ( SchemaChange change : changes ) {
ResolvedSchemaChange resolvedChange = change.resolve(topSchema);
if ( resolvedChange == null )
continue;
// go to and from json
String json = m.writeValueAsString(resolvedChange);
ResolvedSchemaChange fromJson = m.readValue(json, ResolvedSchemaChange.class);
fromJson.apply(topSchema);
}
}
}
Schema bottomSchema = capturer.capture();
List<String> diff = topSchema.diff(bottomSchema, "followed schema", "recaptured schema");
assertThat(StringUtils.join(diff.iterator(), "\n"), diff.size(), is(0));
}
}