This is an automated email from the ASF dual-hosted git repository.
tkhurana pushed a commit to branch PHOENIX-7562-feature-new
in repository https://gitbox.apache.org/repos/asf/phoenix.git
The following commit(s) were added to refs/heads/PHOENIX-7562-feature-new by
this push:
new 84ed758bed PHOENIX-7790 Add create command in PhoenixHAAdminTool for
creating a new HAGroup (#2397)
84ed758bed is described below
commit 84ed758bedcd502c07f655defd915b70b805db16
Author: ritegarg <[email protected]>
AuthorDate: Sat Apr 11 13:23:31 2026 -0700
PHOENIX-7790 Add create command in PhoenixHAAdminTool for creating a new
HAGroup (#2397)
---
.../apache/phoenix/jdbc/PhoenixHAAdminTool.java | 257 +++++++++++++++++++++
.../apache/phoenix/jdbc/PhoenixHAAdminToolIT.java | 145 +++++++++++-
2 files changed, 401 insertions(+), 1 deletion(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixHAAdminTool.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixHAAdminTool.java
index 816761c990..de3fb68377 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixHAAdminTool.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/jdbc/PhoenixHAAdminTool.java
@@ -23,6 +23,8 @@ import static
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CLUSTER_ROLE_2;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CLUSTER_URL_1;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.CLUSTER_URL_2;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.HA_GROUP_NAME;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.HDFS_URL_1;
+import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.HDFS_URL_2;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.POLICY;
import static
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.SYSTEM_HA_GROUP_NAME;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VERSION;
@@ -34,6 +36,7 @@ import static
org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_ZK;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
@@ -53,6 +56,7 @@ import
org.apache.phoenix.exception.StaleHAGroupStoreRecordVersionException;
import org.apache.phoenix.jdbc.ClusterRoleRecord.ClusterRole;
import org.apache.phoenix.jdbc.HAGroupStoreRecord.HAGroupState;
import org.apache.phoenix.jdbc.PhoenixHAAdmin.HighAvailibilityCuratorProvider;
+import org.apache.phoenix.mapreduce.util.ConnectionUtil;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -85,6 +89,7 @@ public class PhoenixHAAdminTool extends Configured implements
Tool {
private static final String CMD_INITIATE_FAILOVER = "initiate-failover";
private static final String CMD_ABORT_FAILOVER = "abort-failover";
private static final String CMD_GET_CLUSTER_ROLE_RECORD =
"get-cluster-role-record";
+ private static final String CMD_CREATE = "create";
// Common options
private static final Option HELP_OPT = new Option("h", "help", false, "Show
help");
@@ -137,6 +142,33 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
private static final Option TIMEOUT_OPT = new Option("t", "timeout", true,
"Timeout in seconds to wait for state transitions (default: 120)");
+ // Create command options (symmetric slot-based, no local/peer distinction)
+ // Cluster 1
+ private static final Option ZK_URL_1_OPT =
+ new Option("zk1", "zk-url-1", true, "ZK URL for cluster 1");
+
+ private static final Option CLUSTER_URL_1_OPT =
+ new Option("c1", "cluster-url-1", true, "HBase cluster URL for cluster 1");
+
+ private static final Option CLUSTER_ROLE_1_OPT =
+ new Option("cr1", "cluster-role-1", true, "Cluster role for cluster 1
(e.g., ACTIVE)");
+
+ private static final Option HDFS_URL_1_OPT =
+ new Option("hdfs1", "hdfs-url-1", true, "HDFS URL for cluster 1");
+
+ // Cluster 2
+ private static final Option ZK_URL_2_OPT =
+ new Option("zk2", "zk-url-2", true, "ZK URL for cluster 2");
+
+ private static final Option CLUSTER_URL_2_OPT =
+ new Option("c2", "cluster-url-2", true, "HBase cluster URL for cluster 2");
+
+ private static final Option CLUSTER_ROLE_2_OPT =
+ new Option("cr2", "cluster-role-2", true, "Cluster role for cluster 2
(e.g., STANDBY)");
+
+ private static final Option HDFS_URL_2_OPT =
+ new Option("hdfs2", "hdfs-url-2", true, "HDFS URL for cluster 2");
+
@Override
public int run(String[] args) throws Exception {
if (args.length == 0) {
@@ -177,6 +209,12 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
// Retrieves and displays cluster role record for HA group
// Required: --ha-group
return executeGetClusterRoleRecord(commandArgs);
+ case CMD_CREATE:
+ // Creates a new HA group entry in SYSTEM.HA_GROUP (idempotent)
+ // Required: --ha-group, --policy, --zk-url-1, --cluster-url-1,
--cluster-role-1,
+ // --zk-url-2, --cluster-url-2, --cluster-role-2
+ // Optional: --hdfs-url-1, --hdfs-url-2, --admin-version, --dry-run
+ return executeCreate(commandArgs);
default:
System.err.println("Unknown command: " + command);
printUsage();
@@ -780,6 +818,164 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
}
}
+ /**
+ * Creates a new HA group entry in SYSTEM.HA_GROUP. Idempotent: if the group
already exists,
+ * prints a skip message and returns success without modifying the existing
row. The ZK znode is
+ * initialized automatically on first access by HAGroupStoreClient. Run the
same command on both
+ * clusters.
+ */
+ private int executeCreate(String[] args) throws Exception {
+ try {
+ CommandLine cmdLine = new DefaultParser().parse(createCreateOptions(),
args);
+
+ if (cmdLine.hasOption(HELP_OPT.getOpt())) {
+ printCreateHelp();
+ return RET_SUCCESS;
+ }
+
+ String haGroupName = getRequiredOption(cmdLine, HA_GROUP_OPT, "HA group
name");
+ String policy = getRequiredOption(cmdLine, POLICY_OPT, "policy");
+ String zkUrl1 = getRequiredOption(cmdLine, ZK_URL_1_OPT, "ZK URL for
cluster 1");
+ String clusterUrl1 =
+ getRequiredOption(cmdLine, CLUSTER_URL_1_OPT, "cluster URL for cluster
1");
+ String clusterRole1Str =
+ getRequiredOption(cmdLine, CLUSTER_ROLE_1_OPT, "cluster role for
cluster 1");
+ String zkUrl2 = getRequiredOption(cmdLine, ZK_URL_2_OPT, "ZK URL for
cluster 2");
+ String clusterUrl2 =
+ getRequiredOption(cmdLine, CLUSTER_URL_2_OPT, "cluster URL for cluster
2");
+ String clusterRole2Str =
+ getRequiredOption(cmdLine, CLUSTER_ROLE_2_OPT, "cluster role for
cluster 2");
+
+ String hdfsUrl1 = getRequiredOption(cmdLine, HDFS_URL_1_OPT, "HDFS URL
for cluster 1");
+ String hdfsUrl2 = getRequiredOption(cmdLine, HDFS_URL_2_OPT, "HDFS URL
for cluster 2");
+ final boolean dryRun = cmdLine.hasOption(DRY_RUN_OPT.getOpt());
+
+ long adminVersion = 1L;
+ String adminVersionStr =
cmdLine.getOptionValue(ADMIN_VERSION_OPT.getOpt());
+ if (adminVersionStr != null) {
+ adminVersion = Long.parseLong(adminVersionStr);
+ }
+
+ ClusterRole clusterRole1 = parseClusterRole(clusterRole1Str);
+ ClusterRole clusterRole2 = parseClusterRole(clusterRole2Str);
+
+ String localZkUrl = getLocalZkUrl(getConf());
+
+ if (haGroupExistsInSystemTable(haGroupName, localZkUrl)) {
+ System.out.println("HA group '" + haGroupName
+ + "' already exists in SYSTEM.HA_GROUP. Skipping creation. Use
update command to modify it.");
+ return RET_SUCCESS;
+ }
+
+ System.out.println("\n=== HA Group to Create ===\n");
+ System.out.println(String.format(" %-25s: %s", "HA Group Name",
haGroupName));
+ System.out.println(String.format(" %-25s: %s", "Policy", policy));
+ System.out.println(String.format(" %-25s: %s", "ZK URL 1", zkUrl1));
+ System.out.println(String.format(" %-25s: %s", "Cluster URL 1",
clusterUrl1));
+ System.out.println(String.format(" %-25s: %s", "Cluster Role 1",
clusterRole1));
+ System.out.println(String.format(" %-25s: %s", "ZK URL 2", zkUrl2));
+ System.out.println(String.format(" %-25s: %s", "Cluster URL 2",
clusterUrl2));
+ System.out.println(String.format(" %-25s: %s", "Cluster Role 2",
clusterRole2));
+ System.out.println(String.format(" %-25s: %s", "HDFS URL 1", hdfsUrl1));
+ System.out.println(String.format(" %-25s: %s", "HDFS URL 2", hdfsUrl2));
+ System.out.println(String.format(" %-25s: %d", "Admin Version",
adminVersion));
+ System.out.println();
+
+ if (dryRun) {
+ System.out.println("\n\u2713 Dry-run completed. No changes were
applied.");
+ return RET_SUCCESS;
+ }
+
+ insertIntoSystemTable(haGroupName, policy, zkUrl1, clusterUrl1,
clusterRole1, hdfsUrl1,
+ zkUrl2, clusterUrl2, clusterRole2, hdfsUrl2, adminVersion, localZkUrl);
+
+ System.out.println(" \u2713 SYSTEM.HA_GROUP entry created.");
+
+ HAGroupStoreManager manager = HAGroupStoreManager.getInstance(getConf());
+ Optional<HAGroupStoreRecord> record =
manager.getHAGroupStoreRecord(haGroupName);
+ if (record.isPresent()) {
+ System.out
+ .println(" \u2713 Znode initialized (state: " +
record.get().getHAGroupState() + ").");
+ } else {
+ System.err.println(
+ " \u26a0 Znode initialization returned empty record for '" +
haGroupName + "'.");
+ }
+
+ System.out.println("\n\u2713 HA group '" + haGroupName + "' created
successfully.");
+ return RET_SUCCESS;
+
+ } catch (IllegalArgumentException e) {
+ System.err.println("\n\u2717 Invalid argument: " + e.getMessage());
+ return RET_ARGUMENT_ERROR;
+ } catch (Exception e) {
+ System.err.println("\n\u2717 Create failed: " + e.getMessage());
+ LOG.error("Create command failed", e);
+ return RET_UPDATE_ERROR;
+ }
+ }
+
+ /**
+ * Check if an HA group entry already exists in SYSTEM.HA_GROUP.
+ */
+ private boolean haGroupExistsInSystemTable(String haGroupName, String
localZkUrl)
+ throws SQLException {
+ String query =
+ "SELECT COUNT(*) FROM " + SYSTEM_HA_GROUP_NAME + " WHERE " +
HA_GROUP_NAME + " = ?";
+ try (PhoenixConnection conn = (PhoenixConnection)
ConnectionUtil.getInputConnection(getConf());
+ PreparedStatement pstmt = conn.prepareStatement(query)) {
+ pstmt.setString(1, haGroupName);
+ try (ResultSet rs = pstmt.executeQuery()) {
+ if (rs.next()) {
+ return rs.getLong(1) > 0;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Insert a new HA group row into SYSTEM.HA_GROUP using symmetric slot-based
columns.
+ */
+ private void insertIntoSystemTable(String haGroupName, String policy, String
zkUrl1,
+ String clusterUrl1, ClusterRole clusterRole1, String hdfsUrl1, String
zkUrl2,
+ String clusterUrl2, ClusterRole clusterRole2, String hdfsUrl2, long
adminVersion,
+ String localZkUrl) throws SQLException {
+
+ String insertQuery = "UPSERT INTO " + SYSTEM_HA_GROUP_NAME + " (" +
HA_GROUP_NAME + ", "
+ + POLICY + ", " + ZK_URL_1 + ", " + CLUSTER_URL_1 + ", " +
CLUSTER_ROLE_1 + ", " + HDFS_URL_1
+ + ", " + ZK_URL_2 + ", " + CLUSTER_URL_2 + ", " + CLUSTER_ROLE_2 + ", "
+ HDFS_URL_2 + ", "
+ + VERSION + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ try (PhoenixConnection conn = (PhoenixConnection)
ConnectionUtil.getInputConnection(getConf());
+ PreparedStatement pstmt = conn.prepareStatement(insertQuery)) {
+ pstmt.setString(1, haGroupName);
+ pstmt.setString(2, policy);
+ pstmt.setString(3, zkUrl1);
+ pstmt.setString(4, clusterUrl1);
+ pstmt.setString(5, clusterRole1.name());
+ pstmt.setString(6, hdfsUrl1);
+ pstmt.setString(7, zkUrl2);
+ pstmt.setString(8, clusterUrl2);
+ pstmt.setString(9, clusterRole2.name());
+ pstmt.setString(10, hdfsUrl2);
+ pstmt.setLong(11, adminVersion);
+ pstmt.executeUpdate();
+ conn.commit();
+ }
+ }
+
+ /**
+ * Parse a ClusterRole from a string value.
+ */
+ private ClusterRole parseClusterRole(String role) {
+ try {
+ return ClusterRole.valueOf(role.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid cluster role: " + role +
"\nValid roles: "
+ +
Arrays.stream(ClusterRole.values()).map(Enum::name).collect(Collectors.joining(",
")));
+ }
+ }
+
/**
* Read current admin version from ZK and increment by 1
*/
@@ -1226,6 +1422,17 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
}
}
+ /**
+ * Create options for create command
+ */
+ private static Options createCreateOptions() {
+ return new
Options().addOption(HELP_OPT).addOption(HA_GROUP_OPT).addOption(POLICY_OPT)
+
.addOption(ZK_URL_1_OPT).addOption(CLUSTER_URL_1_OPT).addOption(CLUSTER_ROLE_1_OPT)
+
.addOption(ZK_URL_2_OPT).addOption(CLUSTER_URL_2_OPT).addOption(CLUSTER_ROLE_2_OPT)
+
.addOption(HDFS_URL_1_OPT).addOption(HDFS_URL_2_OPT).addOption(ADMIN_VERSION_OPT)
+ .addOption(DRY_RUN_OPT);
+ }
+
/**
* Create options for update command
*/
@@ -1324,6 +1531,8 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
System.out.println("Usage: phoenix-consistentha-admin-tool <command>
[options]");
System.out.println();
System.out.println("Commands:");
+ System.out
+ .println(" create Create a new HA group in
SYSTEM.HA_GROUP (idempotent)");
System.out.println(" update Update HA group
configuration");
System.out.println(" get Show HA group
configuration");
System.out.println(" list List all HA groups");
@@ -1335,6 +1544,10 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
.println("Run 'phoenix-consistentha-admin-tool <command> --help' for
command-specific help");
System.out.println();
System.out.println("Examples:");
+ System.out.println(" phoenix-consistentha-admin-tool create -g myHAGroup
-p FAILOVER"
+ + " -zk1 my-zk:2181:/hbase -c1 my-hmaster:16000 -cr1 ACTIVE"
+ + " -zk2 my-zk:2181:/hbase -c2 my-hmaster:16000 -cr2 STANDBY"
+ + " -hdfs1 hdfs://host1:8020 -hdfs2 hdfs://host2:8020");
System.out.println(
" phoenix-consistentha-admin-tool update -g myHAGroup " + "-pz
newhost:2181:/hbase -v 5");
System.out.println(" phoenix-consistentha-admin-tool get -g myHAGroup");
@@ -1345,6 +1558,50 @@ public class PhoenixHAAdminTool extends Configured
implements Tool {
System.out.println();
}
+ /**
+ * Print create command help
+ */
+ private void printCreateHelp() {
+ System.out.println();
+ System.out.println("Usage: phoenix-consistentha-admin-tool create
[options]");
+ System.out.println();
+ System.out.println("Description:");
+ System.out.println(" Creates a new HA group entry in SYSTEM.HA_GROUP. The
ZK znode is");
+ System.out.println(" initialized automatically on first access. This
command is idempotent:");
+ System.out.println(" running it again on an existing HA group prints a
skip message and");
+ System.out.println(" returns success. Run the same command on both
clusters.");
+ System.out.println();
+ System.out.println("REQUIRED:");
+ System.out.println(" -g, --ha-group <name> HA group name");
+ System.out.println(" -p, --policy <policy> HA policy (e.g.,
FAILOVER)");
+ System.out.println(" -zk1, --zk-url-1 <url> ZK URL for cluster
1");
+ System.out.println(" -c1, --cluster-url-1 <url> HBase cluster URL
for cluster 1");
+ System.out.println(" -cr1, --cluster-role-1 <role> Cluster role for
cluster 1");
+ System.out.println(" -zk2, --zk-url-2 <url> ZK URL for cluster
2");
+ System.out.println(" -c2, --cluster-url-2 <url> HBase cluster URL
for cluster 2");
+ System.out.println(" -cr2, --cluster-role-2 <role> Cluster role for
cluster 2");
+ System.out.println();
+ System.out.println(" -hdfs1, --hdfs-url-1 <url> HDFS URL for cluster
1");
+ System.out.println(" -hdfs2, --hdfs-url-2 <url> HDFS URL for cluster
2");
+ System.out.println();
+ System.out.println("OPTIONAL:");
+ System.out.println(" -v, --admin-version <ver> Initial admin
version (default: 1)");
+ System.out.println(" -d, --dry-run Show what would be
created");
+ System.out.println(" -h, --help Show this help");
+ System.out.println();
+ System.out.println("Valid Cluster Roles:");
+ System.out.println(
+ " " +
Arrays.stream(ClusterRole.values()).map(Enum::name).collect(Collectors.joining(",
")));
+ System.out.println();
+ System.out.println("Example:");
+ System.out.println(" phoenix-consistentha-admin-tool create -g myHAGroup
-p FAILOVER \\");
+ System.out.println(" -zk1 my-zk:2181:/hbase -c1 my-hmaster:16000 -cr1
ACTIVE \\");
+ System.out.println(" -zk2 my-zk:2181:/hbase -c2 my-hmaster:16000 -cr2
STANDBY");
+ System.out.println();
+ System.out.println("Note: Run this same command on both clusters.");
+ System.out.println();
+ }
+
/**
* Print update command help
*/
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/PhoenixHAAdminToolIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/PhoenixHAAdminToolIT.java
index b365878087..7ff79f2c7f 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/jdbc/PhoenixHAAdminToolIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/jdbc/PhoenixHAAdminToolIT.java
@@ -30,6 +30,7 @@ import static
org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.VERSION;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ZK_URL_1;
import static org.apache.phoenix.jdbc.PhoenixDatabaseMetaData.ZK_URL_2;
import static org.apache.phoenix.jdbc.PhoenixHAAdmin.toPath;
+import static org.apache.phoenix.jdbc.PhoenixHAAdminTool.RET_ARGUMENT_ERROR;
import static org.apache.phoenix.jdbc.PhoenixHAAdminTool.RET_SUCCESS;
import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR;
import static org.apache.phoenix.util.PhoenixRuntime.JDBC_PROTOCOL_ZK;
@@ -73,7 +74,6 @@ public class PhoenixHAAdminToolIT extends HABaseIT {
private static final Logger LOG =
LoggerFactory.getLogger(PhoenixHAAdminToolIT.class);
private static final PrintStream STDOUT = System.out;
private static final ByteArrayOutputStream STDOUT_CAPTURE = new
ByteArrayOutputStream();
- private static final Long BUFFER_TIME_IN_MS = 100L;
private String haGroupName;
private PhoenixHAAdminTool adminTool;
@@ -897,4 +897,147 @@ public class PhoenixHAAdminToolIT extends HABaseIT {
"✓ System table verification passed: all fields updated correctly,
version incremented");
LOG.info("✓ Update test completed successfully: ZK and System Table both
updated correctly");
}
+
+ /**
+ * Test that the create command successfully creates a new HA group entry in
SYSTEM.HA_GROUP and
+ * znode for the HA group is also created.
+ */
+ @Test(timeout = 180000)
+ public void testCreateCommandNewHAGroup() throws Exception {
+ String createHaGroupName = "testCreate_new_" + System.currentTimeMillis();
+
+ System.setOut(new PrintStream(STDOUT_CAPTURE));
+
+ int ret = ToolRunner.run(adminTool,
+ new String[] { "create", "-g", createHaGroupName, "-p", "FAILOVER",
"-zk1",
+ CLUSTERS.getZkUrl1(), "-c1", CLUSTERS.getMasterAddress1(), "-cr1",
"ACTIVE", "-zk2",
+ CLUSTERS.getZkUrl2(), "-c2", CLUSTERS.getMasterAddress2(), "-cr2",
"STANDBY", "-hdfs1",
+ CLUSTERS.getHdfsUrl1(), "-hdfs2", CLUSTERS.getHdfsUrl2() });
+
+ assertEquals("create command should succeed", RET_SUCCESS, ret);
+
+ String output = STDOUT_CAPTURE.toString();
+ LOG.info("Got stdout from create command: \n++++++++\n{}++++++++\n",
output);
+
+ assertTrue("Output should indicate creation succeeded",
+ output.contains("created successfully"));
+ assertTrue("Output should indicate Znode was initialized",
+ output.contains("Znode initialized"));
+
+ // Verify row was written to SYSTEM.HA_GROUP
+ SystemTableRecord record = querySystemTable(createHaGroupName,
CLUSTERS.getZkUrl1());
+ assertNotNull("System table should contain the new HA group", record);
+ assertEquals("Policy should be FAILOVER", "FAILOVER", record.policy);
+ assertEquals("Version should default to 1", 1L, record.version);
+
+ // Verify ZK znode was created by the create command
+ try (PhoenixHAAdmin haAdmin = new
PhoenixHAAdmin(CLUSTERS.getHBaseCluster1().getConfiguration(),
+ ZK_CONSISTENT_HA_GROUP_RECORD_NAMESPACE)) {
+ HAGroupStoreRecord zkRecord =
+ haAdmin.getHAGroupStoreRecordInZooKeeper(createHaGroupName).getLeft();
+ assertNotNull("ZK znode should be created by create command", zkRecord);
+ assertEquals("ZK record HA group name should match", createHaGroupName,
+ zkRecord.getHaGroupName());
+ assertEquals("ZK record policy should match", "FAILOVER",
zkRecord.getPolicy());
+ assertEquals("ZK record local cluster URL should match",
CLUSTERS.getMasterAddress1(),
+ zkRecord.getClusterUrl());
+ assertEquals("ZK record peer cluster URL should match",
CLUSTERS.getMasterAddress2(),
+ zkRecord.getPeerClusterUrl());
+ assertEquals("ZK record peer ZK URL should match", CLUSTERS.getZkUrl2(),
+ zkRecord.getPeerZKUrl());
+ assertEquals("ZK record local HDFS URL should match",
CLUSTERS.getHdfsUrl1(),
+ zkRecord.getHdfsUrl());
+ assertEquals("ZK record peer HDFS URL should match",
CLUSTERS.getHdfsUrl2(),
+ zkRecord.getPeerHdfsUrl());
+ assertEquals("ZK record admin version should match", 1L,
zkRecord.getAdminCRRVersion());
+ }
+ }
+
+ /**
+ * Test that the create command is idempotent: if the HA group already
exists in SYSTEM.HA_GROUP,
+ * it returns success without modifying the existing row.
+ */
+ @Test(timeout = 180000)
+ public void testCreateCommandAlreadyExists() throws Exception {
+ // haGroupName is pre-inserted by @Before
+ SystemTableRecord originalRecord = querySystemTable(haGroupName,
CLUSTERS.getZkUrl1());
+ assertNotNull("Record should exist before create command", originalRecord);
+
+ System.setOut(new PrintStream(STDOUT_CAPTURE));
+
+ // Run create with different values - these should NOT overwrite the
existing row
+ int ret = ToolRunner.run(adminTool,
+ new String[] { "create", "-g", haGroupName, "-p", "FAILOVER", "-zk1",
CLUSTERS.getZkUrl1(),
+ "-c1", "different-url:16000", "-cr1", "STANDBY", "-zk2",
CLUSTERS.getZkUrl2(), "-c2",
+ CLUSTERS.getMasterAddress2(), "-cr2", "ACTIVE", "-hdfs1",
CLUSTERS.getHdfsUrl1(), "-hdfs2",
+ CLUSTERS.getHdfsUrl2() });
+
+ assertEquals("create command should return success even when group already
exists", RET_SUCCESS,
+ ret);
+
+ String output = STDOUT_CAPTURE.toString();
+ LOG.info("Got stdout from create (existing group):
\n++++++++\n{}++++++++\n", output);
+
+ assertTrue("Output should indicate group already exists and was skipped",
+ output.contains("already exists") && output.contains("Skipping"));
+
+ // Verify the system table row was NOT modified
+ SystemTableRecord afterRecord = querySystemTable(haGroupName,
CLUSTERS.getZkUrl1());
+ assertNotNull("System table record should still exist", afterRecord);
+ assertEquals("Policy should remain unchanged", originalRecord.policy,
afterRecord.policy);
+ assertEquals("Version should remain unchanged", originalRecord.version,
afterRecord.version);
+ assertEquals("ZK URL 1 should remain unchanged", originalRecord.zkUrl1,
afterRecord.zkUrl1);
+ assertEquals("ZK URL 2 should remain unchanged", originalRecord.zkUrl2,
afterRecord.zkUrl2);
+ assertEquals("Cluster URL 1 should remain unchanged",
originalRecord.clusterUrl1,
+ afterRecord.clusterUrl1);
+ assertEquals("Cluster URL 2 should remain unchanged",
originalRecord.clusterUrl2,
+ afterRecord.clusterUrl2);
+ }
+
+ /**
+ * Test that the create command dry-run mode shows what would be created
without writing to
+ * SYSTEM.HA_GROUP.
+ */
+ @Test(timeout = 180000)
+ public void testCreateCommandDryRun() throws Exception {
+ String dryRunHaGroupName = "testCreate_dryRun_" +
System.currentTimeMillis();
+
+ System.setOut(new PrintStream(STDOUT_CAPTURE));
+
+ int ret = ToolRunner.run(adminTool,
+ new String[] { "create", "-g", dryRunHaGroupName, "-p", "FAILOVER",
"-zk1",
+ CLUSTERS.getZkUrl1(), "-c1", CLUSTERS.getMasterAddress1(), "-cr1",
"ACTIVE", "-zk2",
+ CLUSTERS.getZkUrl2(), "-c2", CLUSTERS.getMasterAddress2(), "-cr2",
"STANDBY", "-hdfs1",
+ CLUSTERS.getHdfsUrl1(), "-hdfs2", CLUSTERS.getHdfsUrl2(), "-d" });
+
+ assertEquals("create dry-run should succeed", RET_SUCCESS, ret);
+
+ String output = STDOUT_CAPTURE.toString();
+ LOG.info("Got stdout from create dry-run: \n++++++++\n{}++++++++\n",
output);
+
+ assertTrue("Output should indicate dry-run completed",
+ output.contains("Dry-run") || output.contains("dry-run"));
+
+ // Verify nothing was written to system table
+ SystemTableRecord record = querySystemTable(dryRunHaGroupName,
CLUSTERS.getZkUrl1());
+ assertTrue("System table should NOT contain the HA group after dry-run",
record == null);
+ }
+
+ /**
+ * Test that the create command returns RET_ARGUMENT_ERROR when a required
argument is missing.
+ */
+ @Test(timeout = 180000)
+ public void testCreateCommandMissingRequiredArg() throws Exception {
+ System.setOut(new PrintStream(STDOUT_CAPTURE));
+
+ // Run create without the required --policy argument
+ int ret = ToolRunner.run(adminTool,
+ new String[] { "create", "-g", "anyGroupName", "-zk1",
CLUSTERS.getZkUrl1(), "-c1",
+ CLUSTERS.getMasterAddress1(), "-cr1", "ACTIVE", "-zk2",
CLUSTERS.getZkUrl2(), "-c2",
+ CLUSTERS.getMasterAddress2(), "-cr2", "STANDBY", "-hdfs1",
CLUSTERS.getHdfsUrl1(), "-hdfs2",
+ CLUSTERS.getHdfsUrl2() });
+
+ assertEquals("create command should return RET_ARGUMENT_ERROR when
--policy is missing",
+ RET_ARGUMENT_ERROR, ret);
+ }
}