// Assumptions: smart deployment functions available

function print_metadata_instance_addresses(session) {
    var res = session.runSql("select instance_name, addresses from mysql_innodb_cluster_metadata.instances").fetchAll();
    for (var i = 0; i < res.length; i++) {
        print(res[i][0] + " = " + res[i][1] + "\n");
    }
    print("\n");
}

function get_recovery_user(session) {
    var res = session.runSql(
        "SELECT user_name FROM mysql.slave_master_info WHERE Channel_name = 'group_replication_recovery'");
    var row = res.fetchOne();
    return row[0];
}

function number_of_non_expiring_pwd_accounts(session, user) {
    // Account with non expiring password have password_lifetime = 0.
    var res = session.runSql(
        "SELECT COUNT(*)  FROM mysql.user u WHERE u.user = '" + user +
        "' AND password_lifetime = 0");
    var row = res.fetchOne();
    return row[0];
}

function check_auto_increment_multi_primary(session, base_value) {

    EXPECT_EQ(base_value, get_sysvar(session, "auto_increment_increment"), "Incorrect auto_increment_increment value.");

    var server_id = session.runSql("SELECT @@server_id").fetchOne()[0];
    var expected_offset = 1 + (server_id % base_value)

    EXPECT_EQ(expected_offset, get_sysvar(session, "auto_increment_offset"), "Incorrect auto_increment_offset value.");
}

// BUG#27084767: CREATECLUSTER()/ ADDINSTANCE() DOES NOT CORRECTLY SET AUTO_INCREMENT_OFFSET
//
// dba.createCluster() and addInstance() in single-primary mode, must set the following values:
//
// auto_increment_offset = 2
// auto_increment_increment = 1
//
// And in multi-primary mode:
//
// auto_increment_offset = 1 + server_id % 7
// auto_increment_increment = 7
//
// The value setting should not differ if the target instance is a sandbox or not

// Test with a sandbox

// Test in single-primary mode

//@<> BUG#27084767: Initialize new instances
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port1);
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port2);

shell.connect(__sandbox_uri1);

// Enable offline_mode (BUG#33396423)
var session2 = mysql.getSession(__sandbox_uri2);
session.runSql("SET GLOBAL offline_mode=1");
session2.runSql("SET GLOBAL offline_mode=1");
session2.close();

//@<> BUG#27084767: Create a cluster in single-primary mode
shell.options["logSql"] = "on";
WIPE_SHELL_LOG();

var c = dba.createCluster('test', {gtidSetIsComplete: true});

if (__version_num >= 80400) {
    EXPECT_SHELL_LOG_NOT_CONTAINS("mysql.slave_master_info");
    EXPECT_SHELL_LOG_NOT_CONTAINS("mysql.slave_relay_log_info");
}

//@<> BUG#27084767: Verify the values of auto_increment_% in the seed instance
EXPECT_EQ(1, get_sysvar(session, "auto_increment_increment"));
EXPECT_EQ(2, get_sysvar(session, "auto_increment_offset"));

//@<> BUG#27084767: Add instance to cluster in single-primary mode
shell.options["logSql"] = "on";
WIPE_SHELL_LOG();

EXPECT_NO_THROWS(function(){ c.addInstance(__sandbox_uri2); });
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

if (__version_num >= 80400) {
    EXPECT_SHELL_LOG_NOT_CONTAINS("mysql.slave_master_info");
    EXPECT_SHELL_LOG_NOT_CONTAINS("mysql.slave_relay_log_info");
}

//@<> BUG#27084767: Verify the values of auto_increment_%
EXPECT_EQ(1, get_sysvar(__mysql_sandbox_port2, "auto_increment_increment"));
EXPECT_EQ(2, get_sysvar(__mysql_sandbox_port2, "auto_increment_offset"));

// ensure offline_mode was disabled (BUG#33396423)
EXPECT_EQ(0, get_sysvar(__mysql_sandbox_port1, "offline_mode"));
EXPECT_EQ(0, get_sysvar(__mysql_sandbox_port2, "offline_mode"));

// ensure offline_mode wasn't persisted (BUG#34778797) {VER(<8.0.11)}
dba.configureInstance(__sandbox_uri1, {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port1)});
dba.configureInstance(__sandbox_uri2, {mycnfPath: testutil.getSandboxConfPath(__mysql_sandbox_port2)});
EXPECT_THROWS(function() { testutil.getSandboxConf(__mysql_sandbox_port1, "offline_mode"); }, "Option 'offline_mode' does not exist in group 'mysqld'");
EXPECT_THROWS(function() { testutil.getSandboxConf(__mysql_sandbox_port2, "offline_mode"); }, "Option 'offline_mode' does not exist in group 'mysqld'");

//@<> ensure offline_mode wasn't persisted (BUG#34778797) {VER(>=8.0.11)}
shell.connect(__sandbox_uri1);
session2 = mysql.getSession(__sandbox_uri2);

EXPECT_EQ(0, session.runSql("SELECT count(*) FROM performance_schema.persisted_variables WHERE (variable_name = 'offline_mode')").fetchOne()[0], `Variable 'offline_mode' is persisted in '${__sandbox_uri1}'`);
EXPECT_EQ(0, session2.runSql("SELECT count(*) FROM performance_schema.persisted_variables WHERE (variable_name = 'offline_mode')").fetchOne()[0], `Variable 'offline_mode' is persisted in '${__sandbox_uri2}'`);

session2.close();

// Test in multi-primary mode

//@<> BUG#27084767: Dissolve the cluster
c.dissolve({force: true})
session.close();

shell.connect(__sandbox_uri1);

//@<> BUG#27084767: Create a cluster in multi-primary mode
var c = dba.createCluster('test', {multiPrimary: true, force: true, gtidSetIsComplete: true});

// Reconnect the session before validating the values of auto_increment_%
// This must be done because 'SET PERSIST' changes the values globally (GLOBAL) and not per session
session.close();
shell.connect(__sandbox_uri1);

//@<> BUG#27084767: Verify the values of auto_increment_% in the seed instance in multi-primary mode
check_auto_increment_multi_primary(session, 7);

//@<> BUG#27084767: Add instance to cluster in multi-primary mode
EXPECT_NO_THROWS(function(){ c.addInstance(__sandbox_uri2); });
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

// Connect to the instance 2 to perform the auto_increment_% validations
session.close();
shell.connect(__sandbox_uri2);

//@<> BUG#27084767: Verify the values of auto_increment_% multi-primary
check_auto_increment_multi_primary(session, 7);

//@<> BUG#34711038: Verify the values of auto_increment_% multi-primary
shell.connect(__sandbox_uri1);

c.removeInstance(__sandbox_uri2);

var result = session.runSql(`SELECT cluster_id, address, instance_name, addresses, attributes, description FROM mysql_innodb_cluster_metadata.instances WHERE (address = '${hostname}:${__mysql_sandbox_port1}')`);
var row = result.fetchOne();
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-aaaaaaaaaaaa", row[2], row[3], row[4], row[5]]);
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-bbbbbbbbbbbb", row[2], row[3], row[4], row[5]]);
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-cccccccccccc", row[2], row[3], row[4], row[5]]);
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-dddddddddddd", row[2], row[3], row[4], row[5]]);
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", row[2], row[3], row[4], row[5]]);
session.runSql("INSERT INTO mysql_innodb_cluster_metadata.instances(cluster_id, address, mysql_server_uuid, instance_name, addresses, attributes, description) VALUES (?, ?, ?, ?, ?, ?, ?)", [row[0], row[1], "aaaaaaaa-bbbb-cccc-dddd-ffffffffffff", row[2], row[3], row[4], row[5]]);

c.addInstance(__sandbox_uri2);

shell.connect(__sandbox_uri2);
check_auto_increment_multi_primary(session, 8);

shell.connect(__sandbox_uri1);
check_auto_increment_multi_primary(session, 8);

//@<> BUG#27084767: Finalization
c.disconnect()
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// Test with non-sandbox instance

// Test in single-primary mode

//@<> BUG#27084767: Initialize new non-sandbox instance
testutil.deployRawSandbox(__mysql_sandbox_port1, 'root', {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port1);
testutil.deployRawSandbox(__mysql_sandbox_port2, 'root', {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port2);
var sandbox_cnf1 = testutil.getSandboxConfPath(__mysql_sandbox_port1);
dba.configureInstance(__sandbox_uri1, {clusterAdmin:'root', mycnfPath: sandbox_cnf1});
testutil.stopSandbox(__mysql_sandbox_port1);
testutil.startSandbox(__mysql_sandbox_port1);
var sandbox_cnf2 = testutil.getSandboxConfPath(__mysql_sandbox_port2);
dba.configureInstance(__sandbox_uri2, {clusterAdmin:'root', mycnfPath: sandbox_cnf2});
testutil.stopSandbox(__mysql_sandbox_port2);
testutil.startSandbox(__mysql_sandbox_port2);

// Connect to instance1 to create the cluster
shell.connect(__hostname_uri1);

//@<> BUG#27084767: Create a cluster in single-primary mode non-sandbox
var c = dba.createCluster('test', {gtidSetIsComplete: true});

//@<> BUG#27084767: Verify the values of auto_increment_% in the seed instance non-sandbox
EXPECT_EQ(1, get_sysvar(session, "auto_increment_increment"));
EXPECT_EQ(2, get_sysvar(session, "auto_increment_offset"));

//@<> BUG#27084767: Add instance to cluster in single-primary mode non-sandbox
EXPECT_NO_THROWS(function(){ c.addInstance(__hostname_uri2); });
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

//@<> BUG#27084767: Verify the values of auto_increment_% non-sandbox
EXPECT_EQ(1, get_sysvar(__mysql_sandbox_port2, "auto_increment_increment"));
EXPECT_EQ(2, get_sysvar(__mysql_sandbox_port2, "auto_increment_offset"));

// Test in multi-primary mode

//@<> BUG#27084767: Dissolve the cluster non-sandbox
c.dissolve({force: true})

// Connect to instance1 to create the cluster
shell.connect(__hostname_uri1);

//@<> BUG#27084767: Create a cluster in multi-primary mode non-sandbox
var c = dba.createCluster('test', {multiPrimary: true, force: true, gtidSetIsComplete: true});

// Reconnect the session before validating the values of auto_increment_%
// This must be done because 'SET PERSIST' changes the values globally (GLOBAL) and not per session
session.close();
shell.connect(__sandbox_uri1);

// Get the server_id to calculate the expected value of auto_increment_offset
var result = session.runSql("SELECT @@server_id");
var row = result.fetchOne();
var server_id = row[0];

var __expected_auto_inc_offset = 1 + server_id%7

//@<> BUG#27084767: Verify the values of auto_increment_% in multi-primary non-sandbox
EXPECT_EQ(7, get_sysvar(session, "auto_increment_increment"));
EXPECT_EQ(__expected_auto_inc_offset, get_sysvar(session, "auto_increment_offset"));

//@<> BUG#27084767: Add instance to cluster in multi-primary mode non-sandbox
EXPECT_NO_THROWS(function(){ c.addInstance(__hostname_uri2); });
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

// Connect to the instance 2 to perform the auto_increment_% validations
session.close();
shell.connect(__sandbox_uri2);

// Get the server_id to calculate the expected value of auto_increment_offset
var result = session.runSql("SELECT @@server_id");
var row = result.fetchOne();
var server_id = row[0];

var __expected_auto_inc_offset = 1 + server_id%7

//@<> BUG#27084767: Verify the values of auto_increment_% multi-primary non-sandbox
EXPECT_EQ(7, get_sysvar(__mysql_sandbox_port2, "auto_increment_increment"));
EXPECT_EQ(__expected_auto_inc_offset, get_sysvar(__mysql_sandbox_port2, "auto_increment_offset"));

//@<> BUG#27084767: Finalization non-sandbox
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> BUG#27677227 cluster with x protocol disabled setup
WIPE_SHELL_LOG();
testutil.deploySandbox(__mysql_sandbox_port1, "root", {"mysqlx":"0", report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {"mysqlx":"0", report_host: hostname});

shell.connect(__sandbox_uri1);
c = dba.createCluster('noxplugin', {gtidSetIsComplete: true});

EXPECT_NO_THROWS(function(){ c.addInstance(__sandbox_uri2); });

var msg1 = "The X plugin is not enabled on instance '" + hostname + ":" + __mysql_sandbox_port1 + "'.";
var msg2 = "The X plugin is not enabled on instance '" + hostname + ":" + __mysql_sandbox_port2 + "'.";
EXPECT_SHELL_LOG_CONTAINS(msg1);
EXPECT_SHELL_LOG_CONTAINS(msg2);

//@<OUT> BUG#27677227 cluster with x protocol disabled, mysqlx should be NULL
print_metadata_instance_addresses(session);

//@ BUG#27677227 cluster with x protocol disabled cleanup
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// BUG28056944: cannot add instance after removing its metadata
//@ BUG28056944 deploy sandboxes.
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});

//@ BUG28056944 create cluster.
shell.connect(__hostname_uri1);
var c = dba.createCluster('test_cluster', {gtidSetIsComplete: true});
c.addInstance(__hostname_uri2);
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

//@<> BUG28056944 remove instance with wrong password and force = true.
var wrong_pwd_uri = "root:wrongpawd@" + hostname + ":" + __mysql_sandbox_port2;
c.removeInstance(wrong_pwd_uri, {force: true});

//@<> BUG28056944 Error adding instance already in group but not in Metadata.
// This should pass by just adding the instance to the metadata
c.addInstance(__hostname_uri2);

//@ BUG28056944 clean-up.
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// BUG#29305551: ADMINAPI FAILS TO DETECT INSTANCE IS RUNNING ASYNCHRONOUS REPLICATION
//
// dba.checkInstance() reports that a target instance which is running the Slave
// SQL and IO threads is valid for InnoDB Cluster usage.
//
// As a consequence, the AdminAPI fails to detects that an instance has
// asynchronous replication running and both addInstance() and rejoinInstance()
// fail with useless/unfriendly errors on this scenario. There's not even
// information on how to overcome the issue.

//@<> BUG#29305551: Initialization
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port1);
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
testutil.snapshotSandboxConf(__mysql_sandbox_port2);
shell.connect(__sandbox_uri1);
var c = dba.createCluster('test', {gtidSetIsComplete: true});
session.close();

//@<> BUG#29305551: Setup asynchronous replication
// Create Replication user
shell.connect(__sandbox_uri1);
session.runSql("CREATE USER 'repl'@'%' IDENTIFIED BY 'password' REQUIRE SSL");
session.runSql("GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';");

// Set async channel on instance2
session.close();
shell.connect(__sandbox_uri2);

session.runSql("change " + get_replication_source_keyword() + " TO " + get_replication_option_keyword() + "_HOST='" + hostname + "', " + get_replication_option_keyword() + "_PORT=" + __mysql_sandbox_port1 + ", " + get_replication_option_keyword() + "_USER='repl', " + get_replication_option_keyword() + "_PASSWORD='password', " + get_replication_option_keyword() + "_AUTO_POSITION=1, " + get_replication_option_keyword() + "_SSL=1");
session.runSql("START " + get_replica_keyword());

testutil.waitMemberTransactions(__mysql_sandbox_port2, __mysql_sandbox_port1);

//@ AddInstance async replication error
c.addInstance(__sandbox_uri2);

// BUG#32197197: ADMINAPI DOES NOT PROPERLY CHECK FOR PRECONFIGURED REPLICATION CHANNELS
//
// Even if replication is not running but configured, the warning/error has to
// be provided as implemented in BUG#29305551
session.runSql("STOP " + get_replica_keyword());

//@ AddInstance async replication error with channels stopped
c.addInstance(__sandbox_uri2);

//@ BUG#29305551: Finalization
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// BUG#29809560: addinstance() does not validate if server_id is unique in the cluster
//@ BUG#29809560: deploy sandboxes.
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});

//@ BUG#29809560: set the same server id an all instances.
shell.connect(__hostname_uri1);
session.runSql("SET GLOBAL server_id = 666");
session.close();
shell.connect(__hostname_uri2);
session.runSql("SET GLOBAL server_id = 666");
session.close();

//@ BUG#29809560: create cluster.
shell.connect(__hostname_uri1);
var c = dba.createCluster('test', {gtidSetIsComplete: true});

//@<> BUG#29809560: add instance fails because server_id is not unique.
c.addInstance(__hostname_uri2);

//@ BUG#29809560: clean-up.
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// BUG#28855764: user created by shell expires with default_password_lifetime
//@ BUG#28855764: deploy sandboxes.
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});

//@ BUG#28855764: create cluster.
shell.connect(__hostname_uri1);
var c = dba.createCluster('test', {gtidSetIsComplete: true});

//@ BUG#28855764: add instance an instance to the cluster
c.addInstance(__hostname_uri2);

//@ BUG#28855764: get recovery user for instance 2.
session.close();
shell.connect(__hostname_uri2);
var recovery_user_2 = get_recovery_user(session);

//@ BUG#28855764: get recovery user for instance 1.
session.close();
shell.connect(__hostname_uri1);
var recovery_user_1 = get_recovery_user(session);

//@<> BUG#28855764: Passwords for recovery users never expire (password_lifetime=0).
print("Number of accounts for '" + recovery_user_1 + "': " + number_of_non_expiring_pwd_accounts(session, recovery_user_1) + "\n");
print("Number of accounts for '" + recovery_user_2 + "': " + number_of_non_expiring_pwd_accounts(session, recovery_user_2) + "\n");

//@ BUG#28855764: clean-up.
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

// WL#12773 AdminAPI: Simplification of internal recovery accounts

//@<> WL#12773: Initialization
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname, server_id: 11111});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname, server_id: 22222});
testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname, server_id: 33333});
shell.connect(__sandbox_uri1);

var c;
if (__version_num < 80027) {
    c = dba.createCluster('test', {gtidSetIsComplete: true});
} else {
    c = dba.createCluster('test', {gtidSetIsComplete: true, communicationStack: "XCOM"});
}

// FR1 - A new auto-generated recovery account must be created whenever creating a cluster and joining an instance to a cluster
//@<> WL#12773: FR1.1 - The account user-name shall be mysql_innodb_cluster_<server_id>
c.addInstance(__sandbox_uri2);
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
var result = session.runSql("SELECT COUNT(*) FROM mysql.user WHERE User = 'mysql_innodb_cluster_22222'");
var number = result.fetchOne()[0];
EXPECT_EQ(1, number);

//@<> WL#12773: FR1.2 - The host-name shall be %
result = session.runSql("SELECT Host FROM mysql.user WHERE User = 'mysql_innodb_cluster_22222'");
var user_host = result.fetchOne()[0];
EXPECT_EQ("%", user_host);

//@ WL#12773: FR4 - The ipAllowlist shall not change the behavior defined by FR1
result = session.runSql("SELECT COUNT(*) FROM mysql.user");
var old_account_number = result.fetchOne()[0];
c.addInstance(__sandbox_uri3, {ipAllowlist:"192.168.2.1/15,127.0.0.1," + hostname_ip});
testutil.waitMemberState(__mysql_sandbox_port3, "ONLINE");
print(get_all_gr_recovery_accounts(session));

result = session.runSql("SELECT COUNT(*) FROM mysql.user");
var new_account_number = result.fetchOne()[0];
EXPECT_EQ(old_account_number + 1, new_account_number);

//@<> Remove an instance to add it back using ipAllowlist
c.removeInstance(__sandbox_uri2);

//@<> Verify that ipAllowlist sets the right sysvars depending on the version
var ip_allow_list = "10.10.10.1/15,127.0.0.1," + hostname_ip;

c.addInstance(__sandbox_uri2, {ipAllowlist: ip_allow_list});
shell.connect(__sandbox_uri2);

//@<> Verify that ipAllowlist sets group_replication_ip_allowlist in 8.0.23 {VER(>=8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_allowlist"));

//@<> Verify that ipAllowlist sets group_replication_ip_whitelist in versions < 8.0.23 {VER(<8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_whitelist"));

//@<> Remove the instance to add it back using ipAllowlist
c.removeInstance(__sandbox_uri2);

//@<> Verify the new option ipAllowlist sets the right sysvars depending on the version
ip_allow_list = "10.1.1.0/15,127.0.0.1," + hostname_ip;

c.addInstance(__sandbox_uri2, {ipAllowlist:ip_allow_list});
shell.connect(__sandbox_uri2);

//@<> Verify the new option ipAllowlist sets group_replication_ip_allowlist in 8.0.23 {VER(>=8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_allowlist"));

//@<> Verify the new option ipAllowlist sets group_replication_ip_whitelist in versions < 8.0.23 {VER(<8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_whitelist"));

//@<> WL#12773: Cleanup
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);
testutil.destroySandbox(__mysql_sandbox_port3);

// BUG#25503159: addInstance() does not drop recovery user when it fails
//@<> BUG#25503159: deploy sandboxes.
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});

//@<> BUG#25503159: create cluster.
shell.connect(__hostname_uri1);
var c;

if (__version_num >= 80027) {
    c = dba.createCluster('test', {gtidSetIsComplete: true, communicationStack: "XCOM"});
} else {
    c = dba.createCluster('test', {gtidSetIsComplete: true});
}

//@ BUG#25503159: number of recovery users before executing addInstance().
var num_recovery_users = number_of_gr_recovery_accounts(session);
print("Number of recovery user before addInstance(): " + num_recovery_users + "\n");

//@<> BUG#25503159: add instance fail (using an invalid localaddress).
EXPECT_THROWS(function(){
    c.addInstance(__hostname_uri2, {localAddress: "invalid_host"});
}, "The 'localAddress' isn't compatible with the Group Replication automatically generated list of allowed IPs.");

EXPECT_OUTPUT_CONTAINS(`The 'localAddress' "invalid_host" isn't compatible with the Group Replication automatically generated list of allowed IPs.`);
if (__os_type != 'windows') {
    EXPECT_OUTPUT_CONTAINS("In this scenario, it's necessary to explicitly use the 'ipAllowlist' option to manually specify the list of allowed IPs.");
}
EXPECT_OUTPUT_CONTAINS("See https://dev.mysql.com/doc/refman/en/group-replication-ip-address-permissions.html for more details.");

//@<> BUG#25503159: number of recovery users is the same.
var num_recovery_users_after = number_of_gr_recovery_accounts(session);
EXPECT_EQ(num_recovery_users, num_recovery_users_after);

// BUG#30281908: MYSQL SHELL CRASHED WHILE ADDING A INSTANCE TO INNODB CLUSTER
// This bug was caused by a segmentation fault in the post-clone restart handling
// that did not cover the timeout waiting for the instance to start after clone restarts it.
// To test it, we change the sandbox configuration file to introduce a bogus setting which will
// cause the failure of the instance restart, simulating then a timeout.

//@ BUG#30281908: add instance using clone and simulating a restart timeout {VER(>= 8.0.17)}
testutil.changeSandboxConf(__mysql_sandbox_port2, "foo", "bar");
// Also tests the restartWaitTimeout option
shell.options["dba.restartWaitTimeout"] = 1;
shell.options["useWizards"] = 1;
c.addInstance(__sandbox_uri2, {recoveryMethod:"clone"});
shell.options["useWizards"] = 0;
shell.options["dba.restartWaitTimeout"] = 60;
testutil.waitSandboxDead(__mysql_sandbox_port2);

//@<> BUG#30281908: restart the instance manually {VER(>= 8.0.17)}
testutil.removeFromSandboxConf(__mysql_sandbox_port2, "foo");
testutil.killSandbox(__mysql_sandbox_port2); //the server could still be restarting
testutil.startSandbox(__mysql_sandbox_port2);
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");

//@ BUG#30281908: complete the process with .rescan() {VER(>= 8.0.17)}
shell.options["useWizards"] = 1;
testutil.expectPrompt("Would you like to add it to the cluster metadata? [Y/n]:", "y");
c.rescan();
shell.options["useWizards"] = 0;

//@<> BUG#25503159: clean-up.
c.disconnect();
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> Initialization canonical IPv6 addresses supported WL#12758 {VER(>= 8.0.14)}
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: "::1"});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: "::1"});
shell.connect(__sandbox_uri1);
var cluster = dba.createCluster("cluster", {gtidSetIsComplete: true});

//@<> canonical IPv6 addresses are supported on addInstance WL#12758 {VER(>= 8.0.14)}
cluster.addInstance(__sandbox_uri2);
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
// Bug #30548843 Validate that IPv6 values stored on metadata for mysqlx are valid
print_metadata_instance_addresses(session);

//@<> Cleanup canonical IPv6 addresses supported WL#12758 {VER(>= 8.0.14)}
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> Initialization sandbox WL#12758 IPv6 addresses supported {VER(>= 8.0.14)}
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: "::1"});
testutil.deploySandbox(__mysql_sandbox_port2, "root");
shell.connect(__sandbox_uri1);
var cluster;
if (__version_num < 80027) {
    cluster = dba.createCluster("cluster", {gtidSetIsComplete: true});
} else {
    cluster = dba.createCluster("cluster", {gtidSetIsComplete: true, communicationStack: "xcom"});
}

//@<> IPv6 addresses are supported on localAddress and ipAllowlist WL#12758 {VER(>= 8.0.14)}
var port = __mysql_sandbox_port2*10+1;
var local_address = "[::1]:" + port;
var ip_allow_list = "::1, 127.0.0.1";
cluster.addInstance(__sandbox_uri2, {ipAllowlist:ip_allow_list, localAddress:local_address});
testutil.waitMemberState(__mysql_sandbox_port2, "ONLINE");
shell.connect(__sandbox_uri2);
EXPECT_EQ(local_address, get_sysvar(session, "group_replication_local_address"));

//@<> Confirm that ipAllowlist is set {VER(>= 8.0.14) && VER(<8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_whitelist"));

//@<> Confirm that ipAllowlist is set {VER(>= 8.0.23)}
EXPECT_EQ(ip_allow_list, get_sysvar(session, "group_replication_ip_allowlist"));

//@<> Cleanup WL#12758 IPv6 addresses supported {VER(>= 8.0.14)}
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> Initialization canonical IPv6 addresses are not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
testutil.deploySandbox(__mysql_sandbox_port1, "root");
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: "::1"});
shell.connect(__sandbox_uri1);
var cluster = dba.createCluster("cluster", {gtidSetIsComplete: true});

//@ canonical IPv6 addresses are not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
cluster.addInstance(__sandbox_uri2);

//@<> Cleanup canonical IPv6 addresses are not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> Initialization IPv6 addresses are not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
testutil.deploySandbox(__mysql_sandbox_port1, "root");
testutil.deploySandbox(__mysql_sandbox_port2, "root");
shell.connect(__sandbox_uri1);
var cluster = dba.createCluster("cluster", {gtidSetIsComplete: true});

//@ IPv6 local_address is not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
var local_address = "[::1]:" + __mysql_sandbox_gr_port1;
cluster.addInstance(__sandbox_uri2, {localAddress: local_address});

//@ IPv6 on ipAllowlist is not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
cluster.addInstance(__sandbox_uri2, {ipAllowlist: "::1, 127.0.0.1"});

//@<> Cleanup IPv6 addresses are not supported below 8.0.14 WL#12758 {VER(< 8.0.14)}
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);

//@<> cluster.addInstance does not error if using instance with binlog_checksum enabled BUG#31329024 {VER(>= 8.0.21)}
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname, binlog_checksum: "NONE"});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname, binlog_checksum: "CRC32"});

shell.connect(__sandbox_uri1);
cluster = dba.createCluster("sample", {gtidSetIsComplete: true});
cluster.addInstance(__sandbox_uri2);
EXPECT_STDERR_EMPTY();

//<> Adding an existing replica set should produce an error BUG#31964273 {VER(>=8.0.19)}
cluster.removeInstance(__sandbox_uri2);

shell.connect(__sandbox_uri2);
EXPECT_NO_THROWS(function(){ dba.createReplicaSet("rs"); });

shell.connect(__sandbox_uri1);
cluster = dba.getCluster();
EXPECT_THROWS(function(){
    cluster.addInstance(__sandbox_uri2);
}, `The instance '${hostname}:${__mysql_sandbox_port2}' is already part of an InnoDB ReplicaSet`);

shell.connect(__sandbox_uri2);
dba.dropMetadataSchema({force:true});
session.runSql("RESET " + get_reset_binary_logs_keyword());

//@<> BUG#31357039 Make sure that a note show when creating the cluster and adding an instance with local address specified {__os_type == 'windows' && !__replaying && !__recording}
if (testutil.versionCheck(__version, "<", "8.0.21")) {
    testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
    testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
    shell.connect(__sandbox_uri1);
}
else {
    shell.connect(__sandbox_uri2);
    reset_instance(session);
    shell.connect(__sandbox_uri1);
    reset_instance(session);
}

EXPECT_NO_THROWS(function(){ cluster = dba.createCluster("sample", {gtidSetIsComplete: true, communicationStack: "XCOM", ipAllowlist: `127.0.0.1, ${hostname_ip}`}); });
EXPECT_OUTPUT_CONTAINS("Because the configured Group Replication communication stack is XCOM and the instance is running on Windows, make sure that the local address (specified with 'localAddress') is within the range of allowed hosts (specified with 'ipAllowlist').");

WIPE_OUTPUT()

EXPECT_NO_THROWS(function(){ cluster.addInstance(__sandbox_uri2, {ipAllowlist: `127.0.0.1, ${hostname_ip}`}); });
EXPECT_OUTPUT_CONTAINS("Because the configured Group Replication communication stack is XCOM and the instance is running on Windows, make sure that the local address (specified with 'localAddress') is within the range of allowed hosts (specified with 'ipAllowlist').");

// cleanup
if (testutil.versionCheck(__version, "<", "8.0.21")) {
    session.close();
    testutil.destroySandbox(__mysql_sandbox_port1);
    testutil.destroySandbox(__mysql_sandbox_port2);
}
else {
    cluster.removeInstance(__sandbox_uri2);
}

//@<> Check if fails when GR local address isn't properly read {!__dbug_off}
if (testutil.versionCheck(__version, "<", "8.0.21")) {
    testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
    testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});

    shell.connect(__sandbox_uri1);
    cluster = dba.createCluster("sample", {gtidSetIsComplete: true});
    EXPECT_STDERR_EMPTY();
}
else {
    shell.connect(__sandbox_uri1);
    cluster = dba.getCluster();
}

testutil.dbugSet("+d,dba_instance_query_gr_local_address");
EXPECT_THROWS(function(){ cluster.addInstance(__sandbox_uri2); }, "group_replication_local_address is empty");
EXPECT_OUTPUT_CONTAINS("Unable to read Group Replication local address setting for instance '" + hostname + ":" + __mysql_sandbox_port2 + "', probably due to connectivity issues. Please retry the operation.");
testutil.dbugSet("");

EXPECT_NO_THROWS(function(){ cluster.addInstance(__sandbox_uri2); });
EXPECT_OUTPUT_CONTAINS("The instance '" + hostname + ":" + __mysql_sandbox_port2 + "' was successfully added to the cluster.");

//@<> Failure in clone should revert changes and stop GR {VER(>=8.0.27) && !__dbug_off}
EXPECT_NO_THROWS(function(){ cluster.removeInstance(__sandbox_uri2); });

testutil.dbugSet("+d,dba_clone_trigger_recovery_exception");

EXPECT_THROWS_TYPE(function(){ cluster.addInstance(__sandbox_uri2, {recoveryMethod: "clone"}); }, "Debug", "MYSQLSH");

status = cluster.status();
EXPECT_EQ(1, Object.keys(status["defaultReplicaSet"]["topology"]).length)
EXPECT_EQ("ONLINE", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]["status"])
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]);

testutil.dbugSet("");

//@<> Check if proper clone validations regarding compatibility are done {!__dbug_off}
shell.connect(__sandbox_uri2);
reset_instance(session);

shell.connect(__sandbox_uri1);
reset_instance(session);

testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname});

cluster = dba.createCluster("sample");
EXPECT_NO_THROWS(function() { cluster.addInstance(__sandbox_uri3, {recoveryMethod: "incremental"}); });

testutil.dbugSet("+d,dba_clone_no_compatible_donors");

WIPE_OUTPUT();
EXPECT_THROWS(function(){
    cluster.addInstance(__sandbox_uri2);
}, "'recoveryMethod' option must be set to 'incremental'");
EXPECT_OUTPUT_CONTAINS(`WARNING: None of the online Cluster members are compatible with the recipient (${__endpoint2}) for cloning due to version/platform incompatibilities. Therefore, no instance is available to serve as a valid donor for cloning`);

EXPECT_OUTPUT_CONTAINS("The incremental state recovery may be safely used if you are sure all updates ever executed in the cluster were done with GTIDs enabled, there are no purged transactions and the new instance contains the same GTID set as the cluster or a subset of it. To use this method by default, set the 'recoveryMethod' option to 'incremental'.");

testutil.assertNoPrompts(); // No prompts

// Test with interaction enabled
shell.options.useWizards=1;

testutil.expectPrompt("Please select a recovery method [I]ncremental recovery/[A]bort (default Incremental recovery): ", "a");
EXPECT_THROWS(function(){
    cluster.addInstance(__sandbox_uri2);
}, "Cancelled");

WIPE_OUTPUT();

EXPECT_THROWS(function(){
    cluster.addInstance(__sandbox_uri2, {recoveryMethod:"clone"});
}, "Cannot use recoveryMethod=clone because the selected donor is incompatible or no compatible donors are available due to version/platform incompatibilities.");
EXPECT_OUTPUT_CONTAINS(`WARNING: None of the online Cluster members are compatible with the recipient (${__endpoint2}) for cloning due to version/platform incompatibilities. Therefore, no instance is available to serve as a valid donor for cloning`);

testutil.dbugSet("");

//@<> Cleanup
session.close();
testutil.destroySandbox(__mysql_sandbox_port1);
testutil.destroySandbox(__mysql_sandbox_port2);
testutil.destroySandbox(__mysql_sandbox_port3);
