//@{!__dbug_off && VER(>=8.0.27)}

// Tests recovery method selection for createReplicaCluster() in ClusterSet

// Variables to combine and test:
// - (target instance has) empty GTIDs
// - (target instance has) GTIDs subset
// - (target instance has) errant GTIDs
// - missing GTIDs from 1 peer, all peers
//
// - recoveryMethod: incremental/clone/auto
// - interactive: true/false
//
// - gtidSetIsComplete: true/false
// - clone supported/not
// - clone available/not
//
// Recovery selection algorithm:
//
// InnoDB ClusterSet .createReplicaCluster()
// -----------------------------------------
// IF recoveryMethod = clone AND clone_available THEN
//     CONTINUE WITH CLONE
// ELSE IF recoveryMethod = incremental THEN
//     IF incremental_recovery_possible THEN
//         CONTINUE WITHOUT CLONE
//     ELSE
//         ERROR "Incremental recovery is not possible (GTID state not
//               compatible)"
// ELSE
//     IF incremental_recovery_possible THEN
//         IF incremental_recovery_safe OR gtidSetComplete THEN
//             CONTINUE WITHOUT CLONE
//         ELSE IF clone_supported AND clone_available THEN
//             PROMPT Clone, Recovery, Abort
//         ELSE
//             PROMPT Recovery, Abort
//    ELSE
//         IF clone_supported AND clone_available THEN
//             IF gtid-set empty THEN
//                 PROMPT Clone, Abort
//             ELSE
//                 CONTINUE WITH CLONE
//         ELSE
//             ABORT
//

function install_clone(session) {
    ensure_plugin_enabled("clone", session, "mysql_clone");
}

function uninstall_clone(session) {
    ensure_plugin_disabled("clone", session);
}

function clone_installed(session) {
    var row = session.runSql("select plugin_status from information_schema.plugins where plugin_name='clone'").fetchOne();
    if (row)
        return row[0] == "ACTIVE";
    return false;
}

function mark_gtid_set_complete(flag) {
    if (flag)
        session.runSql("UPDATE mysql_innodb_cluster_metadata.clusters SET attributes = JSON_SET(attributes, '$.opt_gtidSetIsComplete', true)");
    else
        session.runSql("UPDATE mysql_innodb_cluster_metadata.clusters SET attributes = JSON_REMOVE(attributes, '$.opt_gtidSetIsComplete')");
}


//@<> Setup + Create primary cluster
var scene = new ClusterScenario([__mysql_sandbox_port1, __mysql_sandbox_port2]);
var session = scene.session
var cluster = scene.cluster
testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port4, "root", {report_host: hostname});
var session4 = mysql.getSession(__sandbox_uri4);

//@<> createClusterSet()
var cluster_set = cluster.createClusterSet("myClusterSet");
var gtid_executed = session.runSql("SELECT @@global.gtid_executed").fetchOne()[0];
testutil.dbugSet("+d,dba_abort_create_replica_cluster");

//@<> invalid recoveryMethod (should fail)
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "foobar"})}, "Invalid value for option recoveryMethod: foobar", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: 1})}, "Option 'recoveryMethod' is expected to be of type String, but is Integer", "TypeError");

//@<> invalid cloneDonor (should fail)
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {cloneDonor: "127.0.0.1:3306"})}, "Option cloneDonor only allowed if option recoveryMethod is used and set to 'clone'.", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental", cloneDonor: "127.0.0.1:3306"})}, "Option cloneDonor only allowed if option recoveryMethod is set to 'clone'.", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone", cloneDonor: ":"})}, "Invalid value for cloneDonor: Invalid address format in ':'. Must be <host>:<port> or [<ip>]:<port> for IPv6 addresses", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone", cloneDonor: ""})}, "Invalid value for cloneDonor, string value cannot be empty.", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone", cloneDonor: "localhost:"})}, "Invalid value for cloneDonor: Invalid address format in 'localhost:'. Must be <host>:<port> or [<ip>]:<port> for IPv6 addresses", "ArgumentError");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone", cloneDonor: ":3306"})}, "Invalid value for cloneDonor: Invalid address format in ':3306'. Must be <host>:<port> or [<ip>]:<port> for IPv6 addresses", "ArgumentError");

// Tests for recoveryMethod = clone
//
//   IF recoveryMethod = clone AND clone_available THEN
//      CONTINUE WITH CLONE

//@<> createReplicaCluster: recoveryMethod:clone, interactive, make sure no prompts
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone"})}, "debug", "LogicError");
shell.options.useWizards = false;

//@<> createReplicaCluster: recoveryMethod:clone, empty GTID -> clone
mark_gtid_set_complete(false);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone"})}, "debug", "LogicError");
EXPECT_OUTPUT_CONTAINS("Clone based recovery selected through the recoveryMethod option");

//@<> createReplicaCluster: recoveryMethod:clone, empty GTIDs + gtidSetIsComplete -> clone
mark_gtid_set_complete(true);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone"})}, "debug", "LogicError");
EXPECT_OUTPUT_CONTAINS("Clone based recovery selected through the recoveryMethod option");
mark_gtid_set_complete(false);

//@<> createReplicaCluster: recoveryMethod:clone, subset GTIDs, clone unavailable -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);
testutil.dbugSet("+d,dba_clone_version_check_fail");
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone"})}, "Cannot use recoveryMethod=clone because the selected donor is incompatible or no compatible donors are available due to version/platform incompatibilities.", "MYSQLSH");
EXPECT_OUTPUT_CONTAINS(`WARNING: Clone-based recovery not available: Instance '${__endpoint1}' cannot be a donor because its version (8.0.17) isn't compatible with the recipient's (${__version})`);
testutil.dbugSet("");

//@<> createReplicaCluster: recoveryMethod:clone, subset GTIDs -> clone
testutil.dbugSet("+d,dba_abort_create_replica_cluster");
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "clone"})}, "debug", "LogicError");
EXPECT_OUTPUT_CONTAINS("Clone based recovery selected through the recoveryMethod option");

// Tests for recoveryMethod = incremental
//
// createReplicaCluster:
//
// ELSE IF recoveryMethod = incremental THEN
//     IF incremental_recovery_possible THEN
//         CONTINUE WITHOUT CLONE
//     ELSE
//         ERROR "Incremental recovery is not possible (GTID state not compatible)"
//

//@<> createReplicaCluster: recoveryMethod:incremental, interactive, make sure no prompts
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty).`);
EXPECT_OUTPUT_CONTAINS("Incremental state recovery selected through the recoveryMethod option");

//@<> createReplicaCluster: recoveryMethod:incremental, empty GTIDs + gtidSetIsComplete -> incr
mark_gtid_set_complete(true);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS("Incremental state recovery selected through the recoveryMethod option");

//@<> createReplicaCluster: recoveryMethod:incremental, subset GTIDs -> incr
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS("Incremental state recovery selected through the recoveryMethod option");

//@<> createReplicaCluster: recoveryMethod:incremental, errant GTIDs -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed+",00025721-1111-1111-1111-111111111111:1"]);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "Cannot use recoveryMethod=incremental option because the GTID state is not compatible or cannot be recovered.", "MYSQLSH");

EXPECT_OUTPUT_CONTAINS(`WARNING: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it contains transactions that do not originate from the clusterset, which must be discarded before it can join the clusterset.`);

// Tests for recoveryMethod = auto
//
// ELSE
//      IF incremental_recovery_possible THEN
//          IF incremental_recovery_safe OR gtidSetComplete THEN
//              CONTINUE WITHOUT CLONE
//          ELSE IF clone_supported THEN
//              PROMPT Clone, Recovery, Abort
//          ELSE
//              PROMPT Recovery, Abort
//      ELSE
//          IF clone_supported THEN
//              PROMPT Clone, Abort
//          ELSE
//              ABORT

//@<> createReplicaCluster: recoveryMethod:auto, interactive, empty GTID -> prompt c/i/a
testutil.expectPrompt("Please select a recovery method [C]lone/[I]ncremental recovery/[A]bort (default Clone): ", "i");
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty). The Shell is unable to decide whether replication can completely recover its state.`);

//@<> createReplicaCluster: recoveryMethod:auto, interactive, empty GTID, clone unavailable -> prompt i/a
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);
shell.options.useWizards = true;
testutil.dbugSet("+d,dba_clone_version_check_fail");

testutil.expectPrompt("Please select a recovery method [I]ncremental recovery/[A]bort (default Incremental recovery): ", "i");

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty). The Shell is unable to decide whether replication can completely recover its state.`);
testutil.dbugSet("");
testutil.dbugSet("+d,dba_abort_create_replica_cluster");

//@<> createReplicaCluster: recoveryMethod:auto, interactive, empty GTIDs + gtidSetIsComplete -> incr
mark_gtid_set_complete(true);

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS("Incremental state recovery was selected because it seems to be safely usable.");

mark_gtid_set_complete(false);

//@<> createReplicaCluster: recoveryMethod:auto, interactive, errant GTIDs -> prompt c/a
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed+",00025721-1111-1111-1111-111111111111:1"]);
testutil.expectPrompt("Please select a recovery method [C]lone/[A]bort (default Abort): ", "a");

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "Cancelled", "RuntimeError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS(`WARNING: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it contains transactions that do not originate from the clusterset, which must be discarded before it can join the clusterset.`);
EXPECT_OUTPUT_CONTAINS(`Instance '${__endpoint4}' has the following errant GTIDs that do not exist in the clusterset:`);
EXPECT_OUTPUT_CONTAINS("00025721-1111-1111-1111-111111111111:1");
EXPECT_OUTPUT_CONTAINS(`WARNING: Discarding these extra GTID events can either be done manually or by completely overwriting the state of '${__endpoint4}' with a physical snapshot from an existing clusterset member. To use this method by default, set the 'recoveryMethod' option to 'clone'.`);
EXPECT_OUTPUT_CONTAINS("Having extra GTID events is not expected, and it is recommended to investigate this further and ensure that the data can be removed prior to choosing the clone recovery method.");

// Non-interactive tests

//@<> createReplicaCluster: recoveryMethod:auto, non-interactive, empty GTID -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "'recoveryMethod' option must be set to 'clone' or 'incremental'", "MYSQLSH");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS("WARNING: It should be safe to rely on replication to incrementally recover the state of the new Replica Cluster if you are sure all updates ever executed in the ClusterSet were done with GTIDs enabled, there are no purged transactions and the instance used to create the new Replica Cluster contains the same GTID set as the ClusterSet or a subset of it. To use this method by default, set the 'recoveryMethod' option to 'incremental'.");

//@<> createReplicaCluster: recoveryMethod:auto, non-interactive, empty GTIDs + gtidSetIsComplete -> incr
mark_gtid_set_complete(true);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Setting up replica 'myReplicaCluster' of cluster 'cluster' at instance '${__endpoint4}'.`);
EXPECT_OUTPUT_CONTAINS("* Checking transaction state of the instance...");
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty), but the clusterset was configured to assume that replication can completely recover the state of new instances.`);
EXPECT_OUTPUT_CONTAINS("Incremental state recovery was selected because it seems to be safely usable.");

mark_gtid_set_complete(false);

//@<> createReplicaCluster: recoveryMethod:auto, non-interactive, subset GTIDs -> incr
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");
EXPECT_OUTPUT_CONTAINS("WARNING: It should be safe to rely on replication to incrementally recover the state of the new Replica Cluster if you are sure all updates ever executed in the ClusterSet were done with GTIDs enabled, there are no purged transactions and the instance used to create the new Replica Cluster contains the same GTID set as the ClusterSet or a subset of it. To use this method by default, set the 'recoveryMethod' option to 'incremental'.");
EXPECT_OUTPUT_CONTAINS("Incremental state recovery was selected because it seems to be safely usable.");

//@<> createReplicaCluster: recoveryMethod:auto, non-interactive, errant GTIDs -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed+",00025721-1111-1111-1111-111111111111:1"]);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "Instance provisioning required", "MYSQLSH");
EXPECT_OUTPUT_CONTAINS(`Instance '${__endpoint4}' has the following errant GTIDs that do not exist in the clusterset:`);
EXPECT_OUTPUT_CONTAINS("00025721-1111-1111-1111-111111111111:1");
EXPECT_OUTPUT_CONTAINS(`WARNING: Discarding these extra GTID events can either be done manually or by completely overwriting the state of '${__endpoint4}' with a physical snapshot from an existing clusterset member. To use this method by default, set the 'recoveryMethod' option to 'clone'.`);
EXPECT_OUTPUT_CONTAINS("Having extra GTID events is not expected, and it is recommended to investigate this further and ensure that the data can be removed prior to choosing the clone recovery method.");
EXPECT_OUTPUT_CONTAINS("ERROR: The target instance must be either cloned or fully provisioned before it can be added to the target clusterset.");

// interactive tests

// Tests with purged GTIDs
// -----------------------

//@<> purge GTIDs from the primary cluster
session.runSql("FLUSH BINARY LOGS");
session.runSql("PURGE BINARY LOGS BEFORE DATE_ADD(NOW(6), INTERVAL 1 DAY)");

session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("RESET " + get_reset_binary_logs_keyword());

//@<> createReplicaCluster: recoveryMethod:auto, interactive, purged GTID -> prompt c/a
testutil.expectPrompt("Please select a recovery method [C]lone/[A]bort (default Clone): ", "a");
mark_gtid_set_complete(false);

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "Cancelled", "RuntimeError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty). The Shell is unable to determine whether the instance has pre-existing data that would be overwritten with clone based recovery.`);

//@<> createReplicaCluster: recoveryMethod:auto, no-interactive, purged GTID -> error
mark_gtid_set_complete(false);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "Instance provisioning required", "MYSQLSH");

EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);
EXPECT_OUTPUT_CONTAINS(`NOTE: The target instance '${__endpoint4}' has not been pre-provisioned (GTID set is empty). The Shell is unable to determine whether the instance has pre-existing data that would be overwritten with clone based recovery.`);

//@<> createReplicaCluster: recoveryMethod:auto, interactive, purged GTID, subset gtid -> clone, no prompt
mark_gtid_set_complete(false);
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);
EXPECT_OUTPUT_CONTAINS("Clone based recovery was selected because it seems to be safely usable.");

//@<> createReplicaCluster: recoveryMethod:auto, no-interactive, purged GTID, subset gtid -> clone, no prompt
mark_gtid_set_complete(false);
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed]);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);
EXPECT_OUTPUT_CONTAINS("Clone based recovery was selected because it seems to be safely usable.");


//@<> createReplicaCluster: recoveryMethod:auto, interactive, errant GTIDs + purged GTIDs -> prompt c/a
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", ["00025721-1111-1111-1111-111111111111:1"]);
testutil.expectPrompt("Please select a recovery method [C]lone/[A]bort (default Abort): ", "a");

shell.options.useWizards = true;
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster")}, "Cancelled", "RuntimeError");
shell.options.useWizards = false;

EXPECT_OUTPUT_CONTAINS(`Instance '${__endpoint4}' has the following errant GTIDs that do not exist in the clusterset:`);
EXPECT_OUTPUT_CONTAINS("00025721-1111-1111-1111-111111111111:1");
EXPECT_OUTPUT_CONTAINS(`WARNING: Discarding these extra GTID events can either be done manually or by completely overwriting the state of '${__endpoint4}' with a physical snapshot from an existing clusterset member. To use this method by default, set the 'recoveryMethod' option to 'clone'.`);
EXPECT_OUTPUT_CONTAINS("Having extra GTID events is not expected, and it is recommended to investigate this further and ensure that the data can be removed prior to choosing the clone recovery method.");

//@<> createReplicaCluster: recoveryMethod:incremental, purged GTID -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "Cannot use recoveryMethod=incremental option because the GTID state is not compatible or cannot be recovered.", "MYSQLSH");
EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);

//@<> createReplicaCluster: recoveryMethod:incremental, errant GTIDs + purged GTIDs -> error
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", ["00025721-1111-1111-1111-111111111111:1"]);

EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "myReplicaCluster", {recoveryMethod: "incremental"})}, "Cannot use recoveryMethod=incremental option because the GTID state is not compatible or cannot be recovered.", "MYSQLSH");
EXPECT_OUTPUT_CONTAINS(`Instance '${__endpoint4}' has the following errant GTIDs that do not exist in the clusterset:`);
EXPECT_OUTPUT_CONTAINS("00025721-1111-1111-1111-111111111111:1");

//@<> createReplicaCluster: recoveryMethod:clone, purged GTID -> clone {VER(>=8.0.17)}
session4.runSql("RESET " + get_reset_binary_logs_keyword());
mark_gtid_set_complete(false);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "clone", {recoveryMethod: "clone"})}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`NOTE: A GTID set check of the MySQL instance at '${__endpoint4}' determined that it is missing transactions that were purged from all clusterset members.`);
EXPECT_OUTPUT_CONTAINS("Clone based recovery selected through the recoveryMethod option");

//@<> createReplicaCluster: recoveryMethod:clone, errant GTIDs + purged GTIDs -> clone
session4.runSql("RESET " + get_reset_binary_logs_keyword());
session4.runSql("SET GLOBAL gtid_purged=?", [gtid_executed+",00025721-1111-1111-1111-111111111111:1"]);
EXPECT_THROWS_TYPE(function(){cluster_set.createReplicaCluster(__sandbox_uri4, "clone", {recoveryMethod: "clone"})}, "debug", "LogicError");

EXPECT_OUTPUT_CONTAINS(`Instance '${__endpoint4}' has the following errant GTIDs that do not exist in the clusterset:`);
EXPECT_OUTPUT_CONTAINS("00025721-1111-1111-1111-111111111111:1");
EXPECT_OUTPUT_CONTAINS("Clone based recovery selected through the recoveryMethod option");

// cloneDonor tests
testutil.dbugSet("");
testutil.dbugSet("+d,dba_clone_version_check_fail");

//@<> createReplicaCluster: recoveryMethod: clone, errant GTIDs + purged GTIDs + automatic donor not valid
EXPECT_THROWS_TYPE(function() { cluster_set.createReplicaCluster(__sandbox_uri4, "clone", {recoveryMethod: "clone"}); }, "Cannot use recoveryMethod=clone because the selected donor is incompatible or no compatible donors are available due to version/platform incompatibilities.", "MYSQLSH");

EXPECT_OUTPUT_CONTAINS(`WARNING: Clone-based recovery not available: Instance '${__endpoint1}' cannot be a donor because its version (8.0.17) isn't compatible with the recipient's (${__version})`);

//@<> createReplicaCluster: recoveryMethod: clone, errant GTIDs + purged GTIDs + cloneDonor not valid
EXPECT_THROWS_TYPE(function() { cluster_set.createReplicaCluster(__sandbox_uri4, "clone", {recoveryMethod: "clone", cloneDonor: __endpoint1}); }, "Instance '" + hostname + ":" + __mysql_sandbox_port1 + "' cannot be a donor because its version (8.0.17) isn't compatible with the recipient's (" +__version + ").", "MYSQLSH");

//@<> createReplicaCluster: recoveryMethod: clone, errant GTIDs + purged GTIDs + cloneDonor valid

// Disable debug
testutil.dbugSet("");

EXPECT_NO_THROWS(function() { cluster_set.createReplicaCluster(__sandbox_uri4, "clone", {recoveryMethod: "clone", cloneDonor: __endpoint2}); });

// donor must belong to the primary cluster
EXPECT_THROWS_TYPE(function(){
    cluster_set.createReplicaCluster(__sandbox_uri3, "myReplicaCluster2", {recoveryMethod: "clone", cloneDonor: __endpoint4});
}, `Instance '${__endpoint4}' does not belong to the PRIMARY Cluster`, "MYSQLSH");

// donor must belong to the primary cluster and have a valid state
testutil.stopGroup([__mysql_sandbox_port2]);

shell.connect(__sandbox_uri1);
testutil.waitMemberState(__mysql_sandbox_port2, "(MISSING)");

EXPECT_THROWS(function(){
    cluster_set.createReplicaCluster(__sandbox_uri3, "myReplicaCluster2", {recoveryMethod: "clone", cloneDonor: __endpoint2});
}, `Instance '${__endpoint2}' is not an ONLINE member of the PRIMARY Cluster.`);

// re-try with donor in a valid state
EXPECT_NO_THROWS(function() { cluster.rejoinInstance(__sandbox_uri2); });

EXPECT_NO_THROWS(function() { cluster_set.createReplicaCluster(__sandbox_uri3, "myReplicaCluster2", {recoveryMethod: "clone", cloneDonor: __endpoint2}); });

EXPECT_OUTPUT_CONTAINS(`NOTE: ${hostname}:${__mysql_sandbox_port3} is being cloned from ${hostname}:${__mysql_sandbox_port2}`);

//@<> Cleanup
session.close();
session4.close();
scene.destroy();
testutil.destroySandbox(__mysql_sandbox_port3);
testutil.destroySandbox(__mysql_sandbox_port4);
testutil.dbugSet("");
