/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.entity.database.mysql; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; import java.util.Map; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.core.effector.EffectorTasks; import org.apache.brooklyn.core.effector.ssh.SshEffectorTasks; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; import org.apache.brooklyn.entity.database.mysql.MySqlCluster.MySqlMaster; import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.core.task.ssh.SshTasks; import org.apache.brooklyn.util.os.Os; import org.apache.brooklyn.util.ssh.BashCommands; import org.testng.annotations.Test; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; public class MySqlClusterIntegrationTest extends BrooklynAppLiveTestSupport { private static final String TEST_LOCATION = "localhost"; @Test(groups="Integration") public void testAllNodesInit() throws Exception { try { MySqlClusterTestHelper.test(app, getLocation()); } finally { cleanData(); } } @Test(groups = {"Integration"}) public void testMasterInit() throws Exception { try { MySqlClusterTestHelper.testMasterInit(app, getLocation()); } finally { cleanData(); } } @Test(groups="Integration") public void testDumpReplication() throws Exception { try { Location loc = getLocation(); EntitySpec<MySqlCluster> clusterSpec = EntitySpec.create(MySqlCluster.class) .configure(MySqlMaster.MASTER_CREATION_SCRIPT_CONTENTS, MySqlClusterTestHelper.CREATION_SCRIPT) .configure(MySqlNode.MYSQL_SERVER_CONF, MySqlClusterTestHelper.minimalMemoryConfig()); MySqlCluster cluster = MySqlClusterTestHelper.initCluster(app, loc, clusterSpec); MySqlNode master = (MySqlNode) cluster.getAttribute(MySqlCluster.FIRST); purgeLogs(cluster, master); // test dump replication from master MySqlNode slave = (MySqlNode) Iterables.getOnlyElement(cluster.invoke(MySqlCluster.RESIZE_BY_DELTA, ImmutableMap.of("delta", 1)).getUnchecked()); assertEquals(cluster.getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT).getEntityId(), master.getId()); MySqlClusterTestHelper.assertReplication(master, slave); // test dump replication from slave, missing dump on node deleteSnapshot(cluster); cluster.config().set(MySqlCluster.REPLICATION_PREFERRED_SOURCE, slave.getId()); MySqlNode secondSlave = (MySqlNode) Iterables.getOnlyElement(cluster.invoke(MySqlCluster.RESIZE_BY_DELTA, ImmutableMap.of("delta", 1)).getUnchecked()); assertEquals(cluster.getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT).getEntityId(), slave.getId()); MySqlClusterTestHelper.assertReplication(master, secondSlave); // test dump replication from slave, missing snapshot entity Entities.destroy(slave); cluster.config().set(MySqlCluster.REPLICATION_PREFERRED_SOURCE, secondSlave.getId()); MySqlNode thirdSlave = (MySqlNode) Iterables.getOnlyElement(cluster.invoke(MySqlCluster.RESIZE_BY_DELTA, ImmutableMap.of("delta", 1)).getUnchecked()); assertEquals(cluster.getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT).getEntityId(), secondSlave.getId()); MySqlClusterTestHelper.assertReplication(master, thirdSlave); } finally { cleanData(); } } @Test(groups="Integration") public void testReplicationDatabaseFiltering() throws Exception { try { Location loc = getLocation(); EntitySpec<MySqlCluster> clusterSpec = EntitySpec.create(MySqlCluster.class) .configure(MySqlMaster.MASTER_CREATION_SCRIPT_CONTENTS, MySqlClusterTestHelper.CREATION_SCRIPT) .configure(MySqlNode.MYSQL_SERVER_CONF, MySqlClusterTestHelper.minimalMemoryConfig()) .configure(MySqlCluster.SLAVE_REPLICATE_DO_DB, ImmutableList.of("feedback", "items", "mysql")) .configure(MySqlCluster.SLAVE_REPLICATE_DUMP_DB, ImmutableList.of("feedback", "items", "mysql")); MySqlCluster cluster = MySqlClusterTestHelper.initCluster(app, loc, clusterSpec); MySqlNode master = (MySqlNode) cluster.getAttribute(MySqlCluster.FIRST); purgeLogs(cluster, master); // test dump replication from master MySqlNode slave = (MySqlNode) Iterables.getOnlyElement(cluster.invoke(MySqlCluster.RESIZE_BY_DELTA, ImmutableMap.of("delta", 1)).getUnchecked()); assertEquals(cluster.getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT).getEntityId(), master.getId()); MySqlClusterTestHelper.assertReplication(master, slave, "db_filter_test"); } finally { cleanData(); } } private void deleteSnapshot(MySqlCluster cluster) { ReplicationSnapshot replicationSnapshot = cluster.getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT); Entity snapshotEntity = mgmt.getEntityManager().getEntity(replicationSnapshot.getEntityId()); SshMachineLocation machine = EffectorTasks.getSshMachine(snapshotEntity); Entities.submit(snapshotEntity, SshEffectorTasks.ssh( "cd $RUN_DIR", "rm " + replicationSnapshot.getSnapshotPath()) .summary("clear snapshot") .machine(machine) .environmentVariable("RUN_DIR", snapshotEntity.getAttribute(MySqlNode.RUN_DIR)) .requiringExitCodeZero()) .asTask() .getUnchecked(); } private void purgeLogs(MySqlCluster cluster, MySqlNode master) { String preFlushBinaryLogFile = getBinaryLogFile(master); ReplicationSnapshot replicationSnapshot = master.getParent().getAttribute(MySqlCluster.REPLICATION_LAST_SLAVE_SNAPSHOT); assertEquals(preFlushBinaryLogFile, replicationSnapshot.getBinLogName()); MySqlClusterTestHelper.execSql(master, "FLUSH LOGS"); String postFlushBinaryLogFile = getBinaryLogFile(master); waitSlavesCatchUp(cluster, postFlushBinaryLogFile); assertNotEquals(postFlushBinaryLogFile, preFlushBinaryLogFile); MySqlClusterTestHelper.execSql(master, "PURGE BINARY LOGS TO '" + postFlushBinaryLogFile + "';"); assertFalse(fileExists(master, preFlushBinaryLogFile)); } private void waitSlavesCatchUp(final MySqlCluster cluster, final String binLog) { Asserts.succeedsEventually(new Runnable() { @Override public void run() { MySqlNode master = (MySqlNode) cluster.getAttribute(MySqlCluster.FIRST); for (Entity node : cluster.getMembers()) { if (node == master) continue; String status = MySqlClusterTestHelper.execSql((MySqlNode) node, "SHOW SLAVE STATUS \\G"); Map<String, String> map = MySqlRowParser.parseSingle(status); assertEquals(map.get("Relay_Master_Log_File"), binLog); } } }); } private String getBinaryLogFile(MySqlNode master) { String status = MySqlClusterTestHelper.execSql(master, "SHOW MASTER STATUS \\G"); Map<String, String> map = MySqlRowParser.parseSingle(status); return map.get("File"); } private boolean fileExists(MySqlNode node, String binLogName) { String dataDir = Strings.nullToEmpty(node.getConfig(MySqlNode.DATA_DIR)); String path = Os.mergePathsUnix(dataDir, binLogName); String cmd = BashCommands.chain( "cd $RUN_DIR", BashCommands.requireTest(String.format("-f \"%s\"", path), "File " + path + " doesn't exist.")); String summary = "Check if file " + path + " exists"; SshMachineLocation machine = EffectorTasks.getSshMachine(node); return Entities.submit(node, SshTasks.newSshExecTaskFactory(machine, cmd) .allowingNonZeroExitCode() .environmentVariable("RUN_DIR", node.getAttribute(SoftwareProcess.RUN_DIR)) .summary(summary) .allowingNonZeroExitCode()).asTask().getUnchecked() == 0; } private void cleanData() { if (app.getChildren().isEmpty()) return; for (Entity member : Iterables.getOnlyElement(app.getChildren()).getChildren()) { String runDir = member.getAttribute(MySqlNode.RUN_DIR); if (runDir != null) { Os.deleteRecursively(runDir); } } } private Location getLocation() { return mgmt.getLocationRegistry().resolve(TEST_LOCATION); } }