// Tests for Group Replication's transaction size limit management.
// The tests cover the main features and changes for InnoDB ClusterSet
//
//  - New option 'transactionSizeLimit' in clusterset.createReplicaCluster()
//  - Automatic handling of the option value in Replica Cluster: set to maximum
//    by default
//  - Automatic restore of the option's original value in
//    ClusterSet.setPrimaryInstance(), ClusterSet.forcePrimaryCluster() and
//    ClusterSet.removeCluster()

//@ {VER(>=8.0.27)}

//@<> Setup
testutil.deploySandbox(__mysql_sandbox_port1, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port2, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port3, "root", {report_host: hostname});
testutil.deploySandbox(__mysql_sandbox_port4, "root", {report_host: hostname});

//@<> group_replication_transaction_size_limit handling tests

// Set a value for group_replication_transaction_size_limit on the .cnf file
testutil.changeSandboxConf(__mysql_sandbox_port1, "group_replication_transaction_size_limit", "123456781");
testutil.restartSandbox(__mysql_sandbox_port1);

shell.connect(__sandbox_uri1);

EXPECT_NO_THROWS(function() { cluster = dba.createCluster("primary_cluster", {gtidSetIsComplete: true}) });

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

var clusterset;
EXPECT_NO_THROWS(function() { clusterset = cluster.createClusterSet("cs"); });

// The transaction_size_limit must remain unchanged in the primary cluster
var session1 = mysql.getSession(__sandbox_uri1);
var transaction_size_limit = session1.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(123456781, transaction_size_limit);

var replica_cluster;
EXPECT_NO_THROWS(function() { replica_cluster = clusterset.createReplicaCluster(__sandbox_uri3, "replica_cluster"); });

var session3 = mysql.getSession(__sandbox_uri3);

// The transaction_size_limit must be set to the maximum in the replica cluster
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

var session2 = mysql.getSession(__sandbox_uri2);
var session3 = mysql.getSession(__sandbox_uri3);
reset_instance(session3);
reset_instance(session2);
reset_instance(session1);

// Define a transaction_size_limit using the option 'transactionSizeLimit'
// while the server has a different value set in the .cnf file
shell.connect(__sandbox_uri1);

var transaction_size_limit_set_cluster = 123456783;

EXPECT_NO_THROWS(function() { cluster = dba.createCluster("primary_cluster", {gtidSetIsComplete: true, transactionSizeLimit: transaction_size_limit_set_cluster}) });

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

var clusterset;
EXPECT_NO_THROWS(function() { clusterset = cluster.createClusterSet("cs"); });

var session1 = mysql.getSession(__sandbox_uri1);
var transaction_size_limit = session1.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_cluster, transaction_size_limit);

var replica_cluster;
EXPECT_NO_THROWS(function() { replica_cluster = clusterset.createReplicaCluster(__sandbox_uri3, "replica_cluster", {recoveryMethod: "clone"}); });

var session3 = mysql.getSession(__sandbox_uri3);

// The transaction_size_limit must be set to the maximum in the replica cluster
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

var session2 = mysql.getSession(__sandbox_uri2);
reset_instance(session3);
reset_instance(session2);
reset_instance(session1);

// Define a transaction_size_limit using the option 'transactionSizeLimit'
var transaction_size_limit_set_cluster = 123456789;

shell.connect(__sandbox_uri1);

EXPECT_NO_THROWS(function() { cluster = dba.createCluster("primary_cluster", {gtidSetIsComplete: true, transactionSizeLimit: transaction_size_limit_set_cluster}) });

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

var clusterset;
EXPECT_NO_THROWS(function() { clusterset = cluster.createClusterSet("cs"); });

// create the Replica Cluster
var replica_cluster;
EXPECT_NO_THROWS(function() { replica_cluster = clusterset.createReplicaCluster(__sandbox_uri3, "replica_cluster", {recoveryMethod: "clone"}); });

var session3 = mysql.getSession(__sandbox_uri3);

// The transaction_size_limit must be set to the maximum in the replica cluster
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

// transaction_size_limit must be saved in the Metadata and its inherited from the Primary Cluster
var transaction_size_limit_md = parseInt(session.runSql("select attributes->>'$.opt_transactionSizeLimit' from mysql_innodb_cluster_metadata.clusters where cluster_name='replica_cluster'").fetchOne()[0]);
EXPECT_EQ(transaction_size_limit_set_cluster, transaction_size_limit_md);

EXPECT_EQ(undefined, replica_cluster.status()["defaultReplicaSet"]["topology"][__endpoint3]["instanceErrors"]);

//@<> Change transactionSizeLimit with .setOption() must be forbidden in Replica Clusters
EXPECT_THROWS_TYPE(function() { replica_cluster.setOption("transactionSizeLimit", 123456789) }, "Option 'transactionSizeLimit' not supported on Replica Clusters", "RuntimeError");

//@<> Switchovers must ensure Replica Clusters have the maximum value set for transaction_size_limit
EXPECT_NO_THROWS(function() { clusterset.setPrimaryCluster("replica_cluster"); });

// The transaction_size_limit must remain unchanged in the cluster that was promoted
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_cluster, transaction_size_limit);

// The transaction_size_limit must be set to the maximum in the demoted cluster
var transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

//@<> Change transactionSizeLimit on the Primary Cluster
var transaction_size_limit_set_option = 200000000;
EXPECT_NO_THROWS(function() { replica_cluster.setOption("transactionSizeLimit", transaction_size_limit_set_option) });

// The value must be changed in the Cluster and the metadata for all ClusterSet clusters
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit);

var transaction_size_limit_md = parseInt(session3.runSql("select attributes->>'$.opt_transactionSizeLimit' from mysql_innodb_cluster_metadata.clusters where cluster_name='replica_cluster'").fetchOne()[0]);
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit_md);

transaction_size_limit_md = parseInt(session3.runSql("select attributes->>'$.opt_transactionSizeLimit' from mysql_innodb_cluster_metadata.clusters where cluster_name='primary_cluster'").fetchOne()[0]);
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit_md);

//@<> Switchovers must ensure Replica Clusters have the value restored
EXPECT_NO_THROWS(function() { clusterset.setPrimaryCluster("primary_cluster"); });

var transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit);

// The transaction_size_limit must be set to the maximum in the demoted cluster
var transaction_size_limit = session3.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);


//@<> removeCluster must ensure the removed Cluster gets the value restored
EXPECT_NO_THROWS(function() { clusterset.setPrimaryCluster("replica_cluster"); });

EXPECT_NO_THROWS(function() { clusterset.removeCluster("primary_cluster"); });

var transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit);

var session2 = mysql.getSession(__sandbox_uri2);

transaction_size_limit = session2.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit);

//@<> Replica Clusters get the value set to the maximum by default
var original_transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];

shell.connect(__sandbox_uri3);
clusterset = dba.getClusterSet();

EXPECT_NO_THROWS(function() { replica_cluster = clusterset.createReplicaCluster(__sandbox_uri1, "new_replica_cluster"); });

EXPECT_NO_THROWS(function() { replica_cluster.addInstance(__sandbox_uri2, {recoveryMethod: "clone"}); });

var session1 = mysql.getSession(__sandbox_uri1);
var session2 = mysql.getSession(__sandbox_uri2);

// The value stored in the metadata must be the original one
var transaction_size_limit_md = parseInt(session1.runSql("select attributes->>'$.opt_transactionSizeLimit' from mysql_innodb_cluster_metadata.clusters where cluster_name='new_replica_cluster'").fetchOne()[0]);
EXPECT_EQ(original_transaction_size_limit, transaction_size_limit_md);

// The value in use by every instance must be zero (maximum)
transaction_size_limit = session1.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

transaction_size_limit = session2.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

EXPECT_EQ(undefined, replica_cluster.status()["defaultReplicaSet"]["topology"][__endpoint1]["instanceErrors"]);
EXPECT_EQ(undefined, replica_cluster.status()["defaultReplicaSet"]["topology"][__endpoint2]["instanceErrors"]);

//@<> Failover must ensure the elected Cluster has the value restored

// Add another replica cluster
EXPECT_NO_THROWS(function() { clusterset.createReplicaCluster(__sandbox_uri4, "another_replica_cluster", {recoveryMethod: "clone"}); });

testutil.killSandbox(__mysql_sandbox_port3);

shell.connect(__sandbox_uri1);
cluster = dba.getCluster();
clusterset = dba.getClusterSet();

EXPECT_NO_THROWS(function() { clusterset.forcePrimaryCluster("new_replica_cluster"); });

// The value in use by every instance must be the original one
transaction_size_limit = session1.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(original_transaction_size_limit, transaction_size_limit);

transaction_size_limit = session2.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(original_transaction_size_limit, transaction_size_limit);

// The remaining Replica Clusters must be using the maximum value
shell.connect(__sandbox_uri4);
replica_cluster2 = dba.getCluster();

transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

EXPECT_EQ(undefined, replica_cluster2.status()["defaultReplicaSet"]["topology"][__endpoint4]["instanceErrors"]);

//@<> rejoinCluster() must restore the value of transaction_size_limit to zero
testutil.startSandbox(__mysql_sandbox_port3);
shell.connect(__sandbox_uri3);

EXPECT_NO_THROWS(function() { rebooted_replica = dba.rebootClusterFromCompleteOutage(); });

EXPECT_NO_THROWS(function() { clusterset.rejoinCluster("replica_cluster") });

transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(0, transaction_size_limit);

//@<> rejoin the primary can't change the transaction_size_limit
status = cluster.status();
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]);
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port2}`]);

EXPECT_NO_THROWS(function() { clusterset.rejoinCluster("new_replica_cluster"); });

status = cluster.status();
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]);
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port2}`]);

//@<> rejoin the primary should fix incorrect transaction_size_limit values
shell.connect(__sandbox_uri1);
session.runSql("SET GLOBAL group_replication_transaction_size_limit=12345678");

status = cluster.status();
EXPECT_TRUE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]);
EXPECT_EQ("WARNING: The value of 'group_replication_transaction_size_limit' does not match the Cluster's configured value. Use Cluster.rescan() to repair.", status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]["instanceErrors"][0])

EXPECT_NO_THROWS(function() { clusterset.rejoinCluster("new_replica_cluster"); });

status = cluster.status();
EXPECT_FALSE("instanceErrors" in status["defaultReplicaSet"]["topology"][`${hostname}:${__mysql_sandbox_port1}`]);

//@<> removeCluster must ensure the removed Cluster gets the value restored even if GR is stopped
shell.connect(__sandbox_uri3);
session.runSql("STOP group_replication");

EXPECT_NO_THROWS(function() { clusterset.removeCluster("replica_cluster", {force: true}); });

var transaction_size_limit = session.runSql("select @@group_replication_transaction_size_limit").fetchOne()[0];
EXPECT_EQ(transaction_size_limit_set_option, transaction_size_limit);

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