From d7d4ec29bb518cf29567d3ff0f49e4f4bd434cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20M=C3=A4kel=C3=A4?= Date: Fri, 26 May 2017 15:40:40 +0300 Subject: [PATCH] Add tests from develop Added tests from develop. The test results need to be modified for 2.0. --- BUILD/build_deb_local.sh | 84 + BUILD/build_rpm_local.sh | 70 + BUILD/install_build_deps.sh | 130 + BUILD/run_test.sh | 6 + maxscale-system-test/CMakeLists.txt | 702 ++++++ maxscale-system-test/CTestConfig.cmake | 15 + maxscale-system-test/ENV_SETUP.md | 289 +++ maxscale-system-test/HOW_TO_WRITE_TEST.md | 204 ++ .../r/select_for_var_set.result.template | 6 + .../r/set_autocommit_disabled.result | 8 + .../r/test_after_autocommit_disabled.result | 4 + .../r/test_autocommit_disabled1.result | 9 + .../r/test_autocommit_disabled1b.result | 9 + .../r/test_autocommit_disabled2.result | 11 + .../r/test_autocommit_disabled3.result | 11 + .../r/test_implicit_commit1.result | 11 + .../r/test_implicit_commit2.result | 17 + .../r/test_implicit_commit3.result | 11 + .../r/test_implicit_commit5.result | 14 + .../r/test_implicit_commit6.result | 13 + .../r/test_implicit_commit7.result | 12 + .../maxscale-mysqltest/r/test_sescmd.result | 6 + .../r/test_transaction_routing2.result | 15 + .../r/test_transaction_routing2b.result | 15 + .../r/test_transaction_routing4.result | 13 + .../r/test_transaction_routing4b.result | 13 + .../maxscale-mysqltest/sleep-1.inc | 5 + .../t/set_autocommit_disabled.test | 11 + .../t/test_after_autocommit_disabled.test | 3 + .../t/test_autocommit_disabled1.test | 11 + .../t/test_autocommit_disabled1b.test | 11 + .../t/test_autocommit_disabled2.test | 13 + .../t/test_autocommit_disabled3.test | 13 + .../maxscale-mysqltest/t/test_sescmd.test | 4 + .../t/test_transaction_routing2.test | 16 + .../t/test_transaction_routing2b.test | 16 + .../t/test_transaction_routing4.test | 16 + .../t/test_transaction_routing4b.test | 16 + maxscale-system-test/JENKINS.md | 127 + maxscale-system-test/LICENSE | 340 +++ maxscale-system-test/README | 0 maxscale-system-test/README.md | 67 + maxscale-system-test/TEST_RUSULTS.md | 32 + maxscale-system-test/astylerc | 14 + maxscale-system-test/auroramon.cpp | 157 ++ maxscale-system-test/avro.cpp | 107 + maxscale-system-test/avro_long.cpp | 60 + maxscale-system-test/backend_auth_fail.cpp | 40 + maxscale-system-test/bad_pers.cpp | 27 + maxscale-system-test/big_load.cpp | 228 ++ maxscale-system-test/big_load.h | 39 + maxscale-system-test/big_transaction.cpp | 23 + maxscale-system-test/big_transaction.h | 17 + .../binlog_big_transaction.cpp | 72 + maxscale-system-test/binlog_change_master.cpp | 322 +++ maxscale-system-test/binlog_enc_aes_cbc.cnf | 12 + maxscale-system-test/binlog_enc_aes_ctr.cnf | 12 + maxscale-system-test/binlog_failover.cpp | 16 + maxscale-system-test/binlog_incompl.cpp | 26 + maxscale-system-test/binlog_semisync.cpp | 91 + maxscale-system-test/blob_test.cpp | 193 ++ maxscale-system-test/blob_test.h | 32 + maxscale-system-test/bug143.cpp | 68 + maxscale-system-test/bug359.cpp | 51 + maxscale-system-test/bug422.cpp | 122 + maxscale-system-test/bug448.cpp | 71 + maxscale-system-test/bug466.cpp | 34 + maxscale-system-test/bug469.cpp | 89 + maxscale-system-test/bug471.cpp | 193 ++ maxscale-system-test/bug473.cpp | 98 + maxscale-system-test/bug475.cpp | 51 + maxscale-system-test/bug479.cpp | 35 + maxscale-system-test/bug488.cpp | 125 + maxscale-system-test/bug493.cpp | 90 + maxscale-system-test/bug495.cpp | 43 + maxscale-system-test/bug507.cpp | 126 + maxscale-system-test/bug509.cpp | 144 ++ maxscale-system-test/bug519.cpp | 152 ++ maxscale-system-test/bug526.cpp | 69 + maxscale-system-test/bug529.cpp | 315 +++ maxscale-system-test/bug539.cpp | 56 + maxscale-system-test/bug547.cpp | 72 + maxscale-system-test/bug561.sh | 73 + maxscale-system-test/bug562.sh | 42 + maxscale-system-test/bug564.sh | 44 + maxscale-system-test/bug565.cpp | 106 + maxscale-system-test/bug567.sh | 34 + maxscale-system-test/bug571.cpp | 141 ++ maxscale-system-test/bug572.cpp | 46 + maxscale-system-test/bug587.cpp | 110 + maxscale-system-test/bug592.cpp | 129 + maxscale-system-test/bug601.cpp | 128 + maxscale-system-test/bug620.cpp | 268 +++ maxscale-system-test/bug626.cpp | 181 ++ maxscale-system-test/bug634.cpp | 42 + maxscale-system-test/bug643.cpp | 64 + maxscale-system-test/bug643_1.cpp | 96 + maxscale-system-test/bug645.cpp | 213 ++ maxscale-system-test/bug645_1.cpp | 104 + maxscale-system-test/bug649.cpp | 156 ++ maxscale-system-test/bug650.cpp | 84 + maxscale-system-test/bug653.cpp | 80 + maxscale-system-test/bug654.cpp | 202 ++ maxscale-system-test/bug656.cpp | 42 + maxscale-system-test/bug657.cpp | 119 + maxscale-system-test/bug658.cpp | 111 + maxscale-system-test/bug662.cpp | 79 + maxscale-system-test/bug664.cpp | 316 +++ maxscale-system-test/bug670.cpp | 267 +++ maxscale-system-test/bug670_sql.h | 43 + maxscale-system-test/bug673.cpp | 30 + maxscale-system-test/bug676.cpp | 44 + maxscale-system-test/bug681.cpp | 53 + maxscale-system-test/bug694.cpp | 88 + maxscale-system-test/bug699.cpp | 247 ++ maxscale-system-test/bug705.cpp | 261 ++ maxscale-system-test/bug711.cpp | 58 + maxscale-system-test/bug718.cpp | 103 + maxscale-system-test/bug729.cpp | 72 + maxscale-system-test/bug729.php | 19 + maxscale-system-test/bug730.cpp | 68 + maxscale-system-test/bulk_insert.cpp | 219 ++ .../cache/cache_basic/cache_rules.json | 9 + .../cache/cache_basic/r/create.result | 4 + .../cache/cache_basic/r/delete.result | 2 + .../cache/cache_basic/r/drop.result | 1 + .../cache/cache_basic/r/insert1.result | 2 + .../cache/cache_basic/r/select0.result | 3 + .../cache/cache_basic/r/select1.result | 4 + .../cache/cache_basic/r/select2.result | 6 + .../cache/cache_basic/r/select3.result | 8 + .../cache/cache_basic/r/update1.result | 4 + .../cache/cache_basic/r/update2.result | 4 + .../cache/cache_basic/r/update3.result | 6 + .../cache/cache_basic/t/create.test | 14 + .../cache/cache_basic/t/delete.test | 11 + .../cache/cache_basic/t/drop.test | 6 + .../cache/cache_basic/t/insert1.test | 11 + .../cache/cache_basic/t/select0.test | 11 + .../cache/cache_basic/t/select1.test | 11 + .../cache/cache_basic/t/select2.test | 11 + .../cache/cache_basic/t/select3.test | 11 + .../cache/cache_basic/t/update1.test | 13 + .../cache/cache_basic/t/update2.test | 13 + .../cache/cache_basic/t/update3.test | 15 + maxscale-system-test/cache_basic.sh | 125 + maxscale-system-test/ccrfilter.cpp | 144 ++ maxscale-system-test/cdc_client.cpp | 254 ++ maxscale-system-test/cdc_connector.cpp | 308 +++ maxscale-system-test/cdc_connector.h | 47 + .../cdc_datatypes/CMakeLists.txt | 3 + .../cdc_datatypes/cdc_datatypes.cpp | 222 ++ .../cdc_datatypes/cdc_result.cpp | 80 + .../cdc_datatypes/cdc_result.h | 60 + maxscale-system-test/change_master.cpp | 32 + .../change_master_during_session.cpp | 66 + maxscale-system-test/change_user.cpp | 90 + maxscale-system-test/check_backend.cpp | 50 + .../cnf/maxscale.cnf.template.add_server | 65 + .../cnf/maxscale.cnf.template.add_service | 73 + .../cnf/maxscale.cnf.template.auroramon | 66 + .../cnf/maxscale.cnf.template.avro | 59 + .../maxscale.cnf.template.avro_compression | 46 + .../cnf/maxscale.cnf.template.bad_pers | 100 + .../cnf/maxscale.cnf.template.bad_ssl | 107 + .../cnf/maxscale.cnf.template.batchinsert | 88 + .../maxscale.cnf.template.binlog_enc_aes_cbc | 38 + .../maxscale.cnf.template.binlog_enc_aes_ctr | 38 + .../cnf/maxscale.cnf.template.binlog_incompl | 38 + .../cnf/maxscale.cnf.template.bug359 | 79 + .../cnf/maxscale.cnf.template.bug471 | 104 + .../cnf/maxscale.cnf.template.bug479 | 79 + .../cnf/maxscale.cnf.template.bug493 | 78 + .../cnf/maxscale.cnf.template.bug495 | 78 + .../cnf/maxscale.cnf.template.bug526 | 95 + .../cnf/maxscale.cnf.template.bug539 | 99 + .../cnf/maxscale.cnf.template.bug547 | 89 + .../cnf/maxscale.cnf.template.bug567 | 95 + .../cnf/maxscale.cnf.template.bug571 | 110 + .../cnf/maxscale.cnf.template.bug585 | 109 + .../cnf/maxscale.cnf.template.bug587 | 103 + .../cnf/maxscale.cnf.template.bug587_1 | 103 + .../cnf/maxscale.cnf.template.bug601 | 89 + .../cnf/maxscale.cnf.template.bug620 | 90 + .../cnf/maxscale.cnf.template.bug643 | 98 + .../cnf/maxscale.cnf.template.bug643_1 | 142 ++ .../cnf/maxscale.cnf.template.bug645 | 108 + .../cnf/maxscale.cnf.template.bug645_1 | 108 + .../cnf/maxscale.cnf.template.bug648 | 97 + .../cnf/maxscale.cnf.template.bug650 | 109 + .../cnf/maxscale.cnf.template.bug664 | 108 + .../cnf/maxscale.cnf.template.bug670 | 134 ++ .../cnf/maxscale.cnf.template.bug673 | 42 + .../cnf/maxscale.cnf.template.bug694 | 91 + .../cnf/maxscale.cnf.template.bug705 | 89 + .../cnf/maxscale.cnf.template.bug711 | 91 + .../cnf/maxscale.cnf.template.bug718 | 119 + .../cnf/maxscale.cnf.template.bug730 | 100 + .../cnf/maxscale.cnf.template.bulk_insert | 68 + .../cnf/maxscale.cnf.template.cache_basic | 76 + .../cnf/maxscale.cnf.template.ccrfilter | 110 + .../cnf/maxscale.cnf.template.check_backend | 140 ++ .../cnf/maxscale.cnf.template.config_reload | 59 + .../maxscale.cnf.template.connection_limit | 97 + .../maxscale.cnf.template.failover_mysqlmon | 88 + .../cnf/maxscale.cnf.template.fwf | 77 + .../cnf/maxscale.cnf.template.fwf_action | 89 + .../cnf/maxscale.cnf.template.fwf_com_ping | 41 + .../cnf/maxscale.cnf.template.fwf_logging | 39 + .../cnf/maxscale.cnf.template.fwf_syntax | 28 + .../cnf/maxscale.cnf.template.galera | 89 + .../cnf/maxscale.cnf.template.galera.bug681 | 107 + .../cnf/maxscale.cnf.template.galera.weight | 78 + .../cnf/maxscale.cnf.template.galera_mxs564 | 88 + .../cnf/maxscale.cnf.template.galera_priority | 66 + .../cnf/maxscale.cnf.template.gatekeeper | 81 + .../cnf/maxscale.cnf.template.hartmut | 93 + .../cnf/maxscale.cnf.template.hints | 98 + .../cnf/maxscale.cnf.template.hints2 | 95 + .../cnf/maxscale.cnf.template.insertstream | 47 + .../cnf/maxscale.cnf.template.java_prep_stmt | 91 + .../cnf/maxscale.cnf.template.kerberos | 106 + .../cnf/maxscale.cnf.template.lag | 104 + .../cnf/maxscale.cnf.template.load | 91 + .../cnf/maxscale.cnf.template.load_galera | 91 + .../maxscale.cnf.template.load_galera_pers1 | 99 + .../maxscale.cnf.template.load_galera_pers10 | 99 + .../cnf/maxscale.cnf.template.load_pers1 | 99 + .../cnf/maxscale.cnf.template.load_pers10 | 99 + .../cnf/maxscale.cnf.template.longblob | 94 + .../maxscale.cnf.template.longblob_filters | 205 ++ .../maxscale.cnf.template.masking_mysqltest | 99 + .../cnf/maxscale.cnf.template.master_only | 74 + .../cnf/maxscale.cnf.template.maxinfo | 105 + .../cnf/maxscale.cnf.template.maxpasswd | 94 + .../cnf/maxscale.cnf.template.maxrows | 101 + .../cnf/maxscale.cnf.template.mm | 79 + .../cnf/maxscale.cnf.template.mm_mysqlmon | 89 + .../cnf/maxscale.cnf.template.mxs1045 | 93 + .../cnf/maxscale.cnf.template.mxs1123 | 89 + .../cnf/maxscale.cnf.template.mxs118 | 98 + .../cnf/maxscale.cnf.template.mxs127 | 90 + .../cnf/maxscale.cnf.template.mxs361 | 154 ++ .../cnf/maxscale.cnf.template.mxs501 | 99 + .../cnf/maxscale.cnf.template.mxs548 | 92 + .../cnf/maxscale.cnf.template.mxs559 | 94 + .../maxscale.cnf.template.mxs710_bad_socket | 95 + .../maxscale.cnf.template.mxs711_two_ports | 98 + ...ale.cnf.template.mxs720_line_with_no_equal | 93 + .../maxscale.cnf.template.mxs720_wierd_line | 95 + .../cnf/maxscale.cnf.template.mxs722 | 109 + .../cnf/maxscale.cnf.template.mxs799 | 90 + ...maxscale.cnf.template.mxs827_write_timeout | 91 + .../cnf/maxscale.cnf.template.mxs874 | 82 + .../cnf/maxscale.cnf.template.mxs922 | 57 + .../cnf/maxscale.cnf.template.mxs922_base | 32 + .../cnf/maxscale.cnf.template.mysqlmon_backup | 86 + .../maxscale.cnf.template.namedserverfilter | 96 + .../maxscale.cnf.template.no_ses_cmd_store | 92 + .../cnf/maxscale.cnf.template.nsfilter | 93 + .../cnf/maxscale.cnf.template.open_close | 101 + .../cnf/maxscale.cnf.template.pers_01 | 154 ++ .../cnf/maxscale.cnf.template.regexfilter1 | 103 + .../cnf/maxscale.cnf.template.repl_lgc | 92 + .../cnf/maxscale.cnf.template.replication | 90 + .../maxscale.cnf.template.replication.bug539 | 55 + ...axscale.cnf.template.replication.one_slave | 79 + .../maxscale.cnf.template.replication_manager | 69 + ...le.cnf.template.replication_manager_2nodes | 56 + ...le.cnf.template.replication_manager_3nodes | 61 + .../maxscale.cnf.template.rwsplit_multi_stmt | 92 + ...axscale.cnf.template.rwsplit_read_only_trx | 87 + .../maxscale.cnf.template.rwsplit_readonly | 88 + ...ale.cnf.template.schemarouter_duplicate_db | 59 + .../cnf/maxscale.cnf.template.script | 144 ++ .../cnf/maxscale.cnf.template.session_limits | 91 + .../cnf/maxscale.cnf.template.setup_binlog | 38 + .../cnf/maxscale.cnf.template.setup_binlog.in | 38 + .../cnf/maxscale.cnf.template.setup_binlog1 | 40 + .../cnf/maxscale.cnf.template.setup_binlog2 | 41 + ...axscale.cnf.template.setup_binlog_semisync | 41 + ...nf.template.setup_binlog_semisync_txs0_ss0 | 41 + ...nf.template.setup_binlog_semisync_txs0_ss1 | 40 + ...nf.template.setup_binlog_semisync_txs1_ss0 | 40 + ...maxscale.cnf.template.setup_binlog_tx_safe | 41 + .../cnf/maxscale.cnf.template.sharding | 62 + .../maxscale.cnf.template.show_monitor_crash | 93 + .../cnf/maxscale.cnf.template.simplejavatest | 92 + .../maxscale.cnf.template.sql_queries_pers1 | 100 + .../maxscale.cnf.template.sql_queries_pers10 | 100 + .../cnf/maxscale.cnf.template.ssl | 95 + .../cnf/maxscale.cnf.template.ssl_load | 106 + .../cnf/maxscale.cnf.template.ssl_load_galera | 106 + .../cnf/maxscale.cnf.template.user_cache | 89 + maxscale-system-test/config_operations.cpp | 222 ++ maxscale-system-test/config_operations.h | 142 ++ maxscale-system-test/config_test.cpp | 48 + .../connect_to_nonexisting_db.cpp | 66 + maxscale-system-test/connection_limit.cpp | 60 + maxscale-system-test/copy_logs.sh | 54 + maxscale-system-test/crash_out_of_files.cpp | 52 + .../crash_out_of_files_galera.cpp | 54 + maxscale-system-test/create.sql | 2 + maxscale-system-test/create_rds.cpp | 17 + maxscale-system-test/create_rds.sh | 31 + maxscale-system-test/create_user.sh | 33 + maxscale-system-test/create_user_galera.sh | 40 + maxscale-system-test/create_user_ssl.sh | 7 + maxscale-system-test/delete_rds.cpp | 16 + maxscale-system-test/demo_prep1.sh | 61 + maxscale-system-test/demo_prep2.sh | 61 + maxscale-system-test/demo_script1.sh | 101 + maxscale-system-test/demo_script2.sh | 107 + maxscale-system-test/demo_script3.sh | 119 + maxscale-system-test/demo_testing.sh | 156 ++ maxscale-system-test/different_size.cpp | 136 ++ maxscale-system-test/different_size.h | 38 + .../different_size_binlog.cpp | 29 + .../different_size_rwsplit.cpp | 27 + maxscale-system-test/encrypted_passwords.cpp | 74 + maxscale-system-test/execute_cmd.cpp | 44 + maxscale-system-test/execute_cmd.h | 17 + maxscale-system-test/failover_mysqlmon.cpp | 71 + .../false_monitor_state_change.cpp | 28 + maxscale-system-test/fw/deny1 | 1 + maxscale-system-test/fw/deny10 | 4 + maxscale-system-test/fw/deny11 | 3 + maxscale-system-test/fw/deny2 | 1 + maxscale-system-test/fw/deny3 | 3 + maxscale-system-test/fw/deny4 | 1 + maxscale-system-test/fw/deny5 | 3 + maxscale-system-test/fw/deny6 | 1 + maxscale-system-test/fw/deny7 | 1 + maxscale-system-test/fw/deny8 | 1 + maxscale-system-test/fw/deny9 | 3 + maxscale-system-test/fw/pass1 | 6 + maxscale-system-test/fw/pass10 | 1 + maxscale-system-test/fw/pass11 | 3 + maxscale-system-test/fw/pass2 | 2 + maxscale-system-test/fw/pass3 | 6 + maxscale-system-test/fw/pass4 | 6 + maxscale-system-test/fw/pass5 | 5 + maxscale-system-test/fw/pass6 | 5 + maxscale-system-test/fw/pass7 | 6 + maxscale-system-test/fw/pass8 | 1 + maxscale-system-test/fw/pass9 | 4 + maxscale-system-test/fw/rules1 | 2 + maxscale-system-test/fw/rules10 | 3 + maxscale-system-test/fw/rules11 | 2 + maxscale-system-test/fw/rules2 | 2 + maxscale-system-test/fw/rules3 | 2 + maxscale-system-test/fw/rules4 | 2 + maxscale-system-test/fw/rules5 | 2 + maxscale-system-test/fw/rules6 | 2 + maxscale-system-test/fw/rules7 | 2 + maxscale-system-test/fw/rules8 | 2 + maxscale-system-test/fw/rules9 | 3 + maxscale-system-test/fw/rules_actions | 2 + maxscale-system-test/fw/rules_at_time | 2 + maxscale-system-test/fw/rules_limit_queries | 2 + maxscale-system-test/fw/rules_logging | 2 + maxscale-system-test/fw/rules_syntax_error | 2 + maxscale-system-test/fw2/deny1 | 3 + maxscale-system-test/fw2/deny2 | 2 + maxscale-system-test/fw2/deny3 | 2 + maxscale-system-test/fw2/deny4 | 10 + maxscale-system-test/fw2/pass1 | 3 + maxscale-system-test/fw2/pass2 | 3 + maxscale-system-test/fw2/pass3 | 2 + maxscale-system-test/fw2/pass4 | 5 + maxscale-system-test/fw2/rules1 | 4 + maxscale-system-test/fw2/rules2 | 3 + maxscale-system-test/fw2/rules3 | 3 + maxscale-system-test/fw2/rules4 | 4 + maxscale-system-test/fw_copy_rules.cpp | 30 + maxscale-system-test/fw_copy_rules.h | 14 + maxscale-system-test/fwf.cpp | 213 ++ maxscale-system-test/fwf2.cpp | 104 + maxscale-system-test/fwf_actions.cpp | 75 + maxscale-system-test/fwf_com_ping.cpp | 33 + maxscale-system-test/fwf_duplicate_rules.cpp | 36 + maxscale-system-test/fwf_logging.cpp | 56 + maxscale-system-test/fwf_prepared_stmt.cpp | 53 + maxscale-system-test/fwf_reload.cpp | 112 + maxscale-system-test/fwf_syntax.cpp | 85 + maxscale-system-test/galera_priority.cpp | 159 ++ maxscale-system-test/gatekeeper.cpp | 92 + maxscale-system-test/generate_log_sql.cpp | 30 + .../get_com_select_insert.cpp | 105 + maxscale-system-test/get_com_select_insert.h | 29 + maxscale-system-test/get_logs.sh | 15 + maxscale-system-test/get_my_ip.cpp | 59 + maxscale-system-test/get_my_ip.h | 13 + maxscale-system-test/insertstream.sh | 3 + .../insertstream/r/insert.result | 118 + .../insertstream/r/mixed.result | 68 + .../insertstream/r/non-trx.result | 29 + .../insertstream/t/insert.test | 108 + .../insertstream/t/mixed.test | 59 + .../insertstream/t/non-trx.test | 31 + maxscale-system-test/install_aws_tool.sh | 4 + maxscale-system-test/kerb.cnf | 3 + maxscale-system-test/kerberos_setup.cpp | 139 ++ maxscale-system-test/kill_master.cpp | 48 + maxscale-system-test/krb5.conf | 24 + maxscale-system-test/labels_list.sh | 1 + maxscale-system-test/load_balancing.cpp | 90 + .../load_balancing_galera.cpp | 101 + .../local_tests/cleanup_db.sh | 16 + .../local_tests/create_repl_user.sql | 4 + .../local_tests/create_skysql_user.sql | 18 + .../local_tests/multiple_servers.cnf | 79 + .../local_tests/set_env_local.sh | 93 + .../local_tests/start_multiple_mariadb.sh | 34 + maxscale-system-test/long_insert.sh | 29 + maxscale-system-test/long_insert_sql/test.sql | 4 + .../long_insert_sql/test_init.sql | 4 + .../long_insert_sql/test_query.sql | 2 + maxscale-system-test/long_sysbench.cpp | 92 + maxscale-system-test/longblob.cpp | 86 + maxscale-system-test/lots_of_rows.cpp | 39 + maxscale-system-test/macros.cmake | 70 + maxscale-system-test/manage_mrm.sh | 182 ++ maxscale-system-test/mariadb_binlog_keys.txt | 1 + maxscale-system-test/mariadb_func.cpp | 656 +++++ maxscale-system-test/mariadb_func.h | 235 ++ maxscale-system-test/mariadb_nodes.cpp | 1369 +++++++++++ maxscale-system-test/mariadb_nodes.h | 471 ++++ maxscale-system-test/mariadb_tests_hartmut.sh | 18 + maxscale-system-test/masking/README.TXT | 4 + .../masking_mysqltest/masking_rules.json | 29 + .../masking_mysqltest/r/masking_column.result | 57 + .../r/masking_replace.result | 19 + .../masking_mysqltest/r/masking_smoke.result | 123 + .../masking_mysqltest/t/masking_column.test | 75 + .../masking_mysqltest/t/masking_replace.test | 83 + .../masking_mysqltest/t/masking_smoke.test | 126 + .../masking/masking_user/masking_rules.json | 76 + .../r/masking_user_maxskysql.result | 8 + .../masking_user/r/masking_user_skysql.result | 8 + .../masking/masking_user/t/masking_user.test | 46 + .../masking_mysqltest_driver.sh | 54 + maxscale-system-test/masking_user.sh | 86 + maxscale-system-test/max_connections.cpp | 73 + maxscale-system-test/maxadmin_operations.cpp | 277 +++ maxscale-system-test/maxadmin_operations.h | 106 + maxscale-system-test/maxinfo.cpp | 80 + maxscale-system-test/maxinfo.py | 56 + maxscale-system-test/maxinfo_func.cpp | 301 +++ maxscale-system-test/maxinfo_func.h | 25 + maxscale-system-test/maxpython.py | 89 + maxscale-system-test/maxscale-system-test.env | 232 ++ .../maxscale/java/CMakeLists.txt | 30 + .../java/MaxScaleConfiguration.java.in | 39 + .../maxscale/java/MaxScaleConnection.java | 102 + .../maxscale/java/batch/BatchInsert.java | 48 + .../maxscale/java/batch/CMakeLists.txt | 2 + .../java/mariadb-java-client-1.5.4.jar | Bin 0 -> 446997 bytes .../maxscale/java/prep_stmt/CMakeLists.txt | 1 + .../maxscale/java/prep_stmt/PrepStmtTest.java | 128 + .../maxscale/java/test1/CMakeLists.txt | 1 + .../java/test1/SimpleConnectorJTest.java | 69 + .../maxscale_process_user.cpp | 30 + maxscale-system-test/mm.cpp | 170 ++ maxscale-system-test/mm_mysqlmon.cpp | 220 ++ maxscale-system-test/mrm/config1.toml | 73 + maxscale-system-test/mrm/config2.toml | 81 + maxscale-system-test/mxs1045.cpp | 34 + maxscale-system-test/mxs1071_maxrows.cpp | 555 +++++ maxscale-system-test/mxs1073_binlog_enc.cpp | 247 ++ maxscale-system-test/mxs1110_16mb.cpp | 54 + maxscale-system-test/mxs1123.cpp | 23 + maxscale-system-test/mxs118.cpp | 27 + maxscale-system-test/mxs127.cpp | 51 + .../mxs244_prepared_stmt_loop.cpp | 60 + .../mxs280_select_outfile.cpp | 56 + maxscale-system-test/mxs314.cpp | 74 + maxscale-system-test/mxs321.cpp | 114 + maxscale-system-test/mxs365.cpp | 88 + .../mxs37_table_privilege.cpp | 82 + .../mxs419_lots_of_connections.cpp | 37 + maxscale-system-test/mxs431.cpp | 66 + maxscale-system-test/mxs47.cpp | 30 + maxscale-system-test/mxs501_tee_usedb.cpp | 76 + .../mxs548_short_session_change_user.cpp | 270 +++ maxscale-system-test/mxs559_block_master.cpp | 145 ++ maxscale-system-test/mxs564_big_dump.cpp | 237 ++ maxscale-system-test/mxs585.py | 16 + maxscale-system-test/mxs598.py | 27 + .../mxs621_unreadable_cnf.cpp | 32 + maxscale-system-test/mxs652_bad_ssl.cpp | 57 + maxscale-system-test/mxs657_restart.cpp | 122 + .../mxs657_restart_service.cpp | 97 + maxscale-system-test/mxs682_cyrillic.cpp | 100 + maxscale-system-test/mxs710_bad_socket.cpp | 25 + maxscale-system-test/mxs716.cpp | 86 + .../mxs720_line_with_no_equal.cpp | 26 + maxscale-system-test/mxs720_wierd_line.cpp | 27 + maxscale-system-test/mxs722.cpp | 57 + maxscale-system-test/mxs729_maxadmin.cpp | 149 ++ .../mxs781_binlog_wrong_passwrd.cpp | 74 + maxscale-system-test/mxs791.sh | 17 + maxscale-system-test/mxs791_base.sh | 32 + maxscale-system-test/mxs791_galera.sh | 17 + maxscale-system-test/mxs812_1.cpp | 82 + maxscale-system-test/mxs812_2.cpp | 101 + maxscale-system-test/mxs813_long_hostname.cpp | 47 + maxscale-system-test/mxs822_maxpasswd.cpp | 63 + maxscale-system-test/mxs827_write_timeout.cpp | 41 + .../mxs874_slave_recovery.cpp | 83 + maxscale-system-test/mxs922_bad_server.cpp | 113 + .../mxs922_double_listener.cpp | 27 + maxscale-system-test/mxs922_listener_ssl.cpp | 31 + maxscale-system-test/mxs922_monitor.cpp | 82 + maxscale-system-test/mxs922_restart.cpp | 78 + maxscale-system-test/mxs922_scaling.cpp | 93 + maxscale-system-test/mxs922_server.cpp | 99 + maxscale-system-test/mxs951_utfmb4.cpp | 66 + maxscale-system-test/mxs957.cpp | 56 + maxscale-system-test/mysqlmon_backup.cpp | 112 + maxscale-system-test/mysqltest_driver.sh | 77 + maxscale-system-test/namedserverfilter.cpp | 45 + maxscale-system-test/no_password.cpp | 24 + maxscale-system-test/non_native_setup.cpp | 30 + maxscale-system-test/nsfilter.py | 16 + .../open_close_connections.cpp | 110 + maxscale-system-test/pers_01.cpp | 212 ++ maxscale-system-test/pers_02.cpp | 43 + maxscale-system-test/prepared_statement.cpp | 58 + maxscale-system-test/rds_vpc.cpp | 796 +++++++ maxscale-system-test/rds_vpc.h | 263 +++ .../readconnrouter_master.cpp | 84 + maxscale-system-test/readconnrouter_slave.cpp | 71 + maxscale-system-test/regexfilter1.cpp | 27 + maxscale-system-test/replication_manager.cpp | 157 ++ .../replication_manager_2nodes.cpp | 202 ++ .../replication_manager_3nodes.cpp | 224 ++ maxscale-system-test/run_ctrl_c.sh | 31 + maxscale-system-test/run_session_hang.sh | 57 + .../rw_galera_select_insert.cpp | 62 + maxscale-system-test/rw_select_insert.cpp | 224 ++ maxscale-system-test/rwsplit_conn_num.cpp | 85 + maxscale-system-test/rwsplit_connect.cpp | 46 + maxscale-system-test/rwsplit_multi_stmt.cpp | 71 + .../rwsplit_read_only_trx.cpp | 56 + maxscale-system-test/rwsplit_readonly.cpp | 255 ++ .../rwsplit_readonly_stress.cpp | 95 + .../schemarouter_duplicate_db.cpp | 35 + maxscale-system-test/script.cpp | 182 ++ maxscale-system-test/server_weight.cpp | 158 ++ maxscale-system-test/ses_bigmem.cpp | 53 + .../session_hang/run_setmix.sh | 6 + maxscale-system-test/session_hang/setmix.sql | 37 + .../session_hang/simpletest.pl | 31 + maxscale-system-test/session_limits.cpp | 62 + maxscale-system-test/setup_binlog.cpp | 69 + maxscale-system-test/setup_binlog_crc_32.cpp | 46 + .../setup_binlog_crc_none.cpp | 42 + maxscale-system-test/sharding.cpp | 152 ++ maxscale-system-test/sharding_load_data.cpp | 53 + maxscale-system-test/short_sessions.cpp | 69 + maxscale-system-test/show_monitor_crash.cpp | 31 + maxscale-system-test/slave_failover.cpp | 74 + maxscale-system-test/slave_lag.cpp | 196 ++ maxscale-system-test/sql_const.h | 25 + maxscale-system-test/sql_queries.cpp | 112 + maxscale-system-test/sql_t1.cpp | 258 ++ maxscale-system-test/sql_t1.h | 77 + maxscale-system-test/ssl-cert/ca-key.pem | 27 + maxscale-system-test/ssl-cert/ca.pem | 22 + maxscale-system-test/ssl-cert/client-cert.pem | 20 + maxscale-system-test/ssl-cert/client-key.pem | 27 + maxscale-system-test/ssl-cert/client-req.pem | 17 + maxscale-system-test/ssl-cert/server-cert.pem | 20 + maxscale-system-test/ssl-cert/server-key.pem | 27 + maxscale-system-test/ssl-cert/server-req.pem | 17 + maxscale-system-test/ssl.cnf | 4 + maxscale-system-test/stale_slaves.cpp | 106 + maxscale-system-test/start_without_root.sh | 27 + maxscale-system-test/sysbench_commands.h | 71 + maxscale-system-test/sysbench_example.cpp | 69 + maxscale-system-test/sysbench_kill_slave.cpp | 145 ++ maxscale-system-test/t.cpp | 28 + maxscale-system-test/templates.h.in | 17 + maxscale-system-test/temporal_tables.cpp | 99 + maxscale-system-test/test_binlog_fnc.cpp | 238 ++ maxscale-system-test/test_binlog_fnc.h | 30 + .../test_ctrl_c/start_killer.sh | 3 + .../test_ctrl_c/test_ctrl_c.sh | 28 + maxscale-system-test/test_hints.cpp | 101 + maxscale-system-test/testconnections.cpp | 2102 +++++++++++++++++ maxscale-system-test/testconnections.h | 711 ++++++ .../transaction_test_wo_maxscale.cpp | 129 + maxscale-system-test/user_cache.cpp | 72 + maxscale-system-test/utf64.cnf | 3 + maxscale-system-test/utilities.cmake | 104 + 596 files changed, 48543 insertions(+) create mode 100755 BUILD/build_deb_local.sh create mode 100755 BUILD/build_rpm_local.sh create mode 100755 BUILD/install_build_deps.sh create mode 100644 BUILD/run_test.sh create mode 100644 maxscale-system-test/CMakeLists.txt create mode 100644 maxscale-system-test/CTestConfig.cmake create mode 100644 maxscale-system-test/ENV_SETUP.md create mode 100644 maxscale-system-test/HOW_TO_WRITE_TEST.md create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/select_for_var_set.result.template create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/set_autocommit_disabled.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_after_autocommit_disabled.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1b.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled2.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled3.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit1.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit2.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit3.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit5.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit6.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit7.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_sescmd.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2b.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4b.result create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/sleep-1.inc create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/set_autocommit_disabled.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_after_autocommit_disabled.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1b.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled2.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled3.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_sescmd.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2b.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4.test create mode 100644 maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4b.test create mode 100644 maxscale-system-test/JENKINS.md create mode 100644 maxscale-system-test/LICENSE create mode 100644 maxscale-system-test/README create mode 100644 maxscale-system-test/README.md create mode 100644 maxscale-system-test/TEST_RUSULTS.md create mode 100644 maxscale-system-test/astylerc create mode 100644 maxscale-system-test/auroramon.cpp create mode 100644 maxscale-system-test/avro.cpp create mode 100644 maxscale-system-test/avro_long.cpp create mode 100644 maxscale-system-test/backend_auth_fail.cpp create mode 100644 maxscale-system-test/bad_pers.cpp create mode 100644 maxscale-system-test/big_load.cpp create mode 100644 maxscale-system-test/big_load.h create mode 100644 maxscale-system-test/big_transaction.cpp create mode 100644 maxscale-system-test/big_transaction.h create mode 100644 maxscale-system-test/binlog_big_transaction.cpp create mode 100644 maxscale-system-test/binlog_change_master.cpp create mode 100644 maxscale-system-test/binlog_enc_aes_cbc.cnf create mode 100644 maxscale-system-test/binlog_enc_aes_ctr.cnf create mode 100644 maxscale-system-test/binlog_failover.cpp create mode 100644 maxscale-system-test/binlog_incompl.cpp create mode 100644 maxscale-system-test/binlog_semisync.cpp create mode 100644 maxscale-system-test/blob_test.cpp create mode 100644 maxscale-system-test/blob_test.h create mode 100644 maxscale-system-test/bug143.cpp create mode 100644 maxscale-system-test/bug359.cpp create mode 100644 maxscale-system-test/bug422.cpp create mode 100644 maxscale-system-test/bug448.cpp create mode 100644 maxscale-system-test/bug466.cpp create mode 100644 maxscale-system-test/bug469.cpp create mode 100644 maxscale-system-test/bug471.cpp create mode 100644 maxscale-system-test/bug473.cpp create mode 100644 maxscale-system-test/bug475.cpp create mode 100644 maxscale-system-test/bug479.cpp create mode 100644 maxscale-system-test/bug488.cpp create mode 100644 maxscale-system-test/bug493.cpp create mode 100644 maxscale-system-test/bug495.cpp create mode 100644 maxscale-system-test/bug507.cpp create mode 100644 maxscale-system-test/bug509.cpp create mode 100644 maxscale-system-test/bug519.cpp create mode 100644 maxscale-system-test/bug526.cpp create mode 100644 maxscale-system-test/bug529.cpp create mode 100644 maxscale-system-test/bug539.cpp create mode 100644 maxscale-system-test/bug547.cpp create mode 100755 maxscale-system-test/bug561.sh create mode 100755 maxscale-system-test/bug562.sh create mode 100755 maxscale-system-test/bug564.sh create mode 100644 maxscale-system-test/bug565.cpp create mode 100755 maxscale-system-test/bug567.sh create mode 100644 maxscale-system-test/bug571.cpp create mode 100644 maxscale-system-test/bug572.cpp create mode 100644 maxscale-system-test/bug587.cpp create mode 100644 maxscale-system-test/bug592.cpp create mode 100644 maxscale-system-test/bug601.cpp create mode 100644 maxscale-system-test/bug620.cpp create mode 100644 maxscale-system-test/bug626.cpp create mode 100644 maxscale-system-test/bug634.cpp create mode 100644 maxscale-system-test/bug643.cpp create mode 100644 maxscale-system-test/bug643_1.cpp create mode 100644 maxscale-system-test/bug645.cpp create mode 100644 maxscale-system-test/bug645_1.cpp create mode 100644 maxscale-system-test/bug649.cpp create mode 100644 maxscale-system-test/bug650.cpp create mode 100644 maxscale-system-test/bug653.cpp create mode 100644 maxscale-system-test/bug654.cpp create mode 100644 maxscale-system-test/bug656.cpp create mode 100644 maxscale-system-test/bug657.cpp create mode 100644 maxscale-system-test/bug658.cpp create mode 100644 maxscale-system-test/bug662.cpp create mode 100644 maxscale-system-test/bug664.cpp create mode 100644 maxscale-system-test/bug670.cpp create mode 100644 maxscale-system-test/bug670_sql.h create mode 100644 maxscale-system-test/bug673.cpp create mode 100644 maxscale-system-test/bug676.cpp create mode 100644 maxscale-system-test/bug681.cpp create mode 100644 maxscale-system-test/bug694.cpp create mode 100644 maxscale-system-test/bug699.cpp create mode 100644 maxscale-system-test/bug705.cpp create mode 100644 maxscale-system-test/bug711.cpp create mode 100644 maxscale-system-test/bug718.cpp create mode 100644 maxscale-system-test/bug729.cpp create mode 100644 maxscale-system-test/bug729.php create mode 100644 maxscale-system-test/bug730.cpp create mode 100644 maxscale-system-test/bulk_insert.cpp create mode 100644 maxscale-system-test/cache/cache_basic/cache_rules.json create mode 100644 maxscale-system-test/cache/cache_basic/r/create.result create mode 100644 maxscale-system-test/cache/cache_basic/r/delete.result create mode 100644 maxscale-system-test/cache/cache_basic/r/drop.result create mode 100644 maxscale-system-test/cache/cache_basic/r/insert1.result create mode 100644 maxscale-system-test/cache/cache_basic/r/select0.result create mode 100644 maxscale-system-test/cache/cache_basic/r/select1.result create mode 100644 maxscale-system-test/cache/cache_basic/r/select2.result create mode 100644 maxscale-system-test/cache/cache_basic/r/select3.result create mode 100644 maxscale-system-test/cache/cache_basic/r/update1.result create mode 100644 maxscale-system-test/cache/cache_basic/r/update2.result create mode 100644 maxscale-system-test/cache/cache_basic/r/update3.result create mode 100644 maxscale-system-test/cache/cache_basic/t/create.test create mode 100644 maxscale-system-test/cache/cache_basic/t/delete.test create mode 100644 maxscale-system-test/cache/cache_basic/t/drop.test create mode 100644 maxscale-system-test/cache/cache_basic/t/insert1.test create mode 100644 maxscale-system-test/cache/cache_basic/t/select0.test create mode 100644 maxscale-system-test/cache/cache_basic/t/select1.test create mode 100644 maxscale-system-test/cache/cache_basic/t/select2.test create mode 100644 maxscale-system-test/cache/cache_basic/t/select3.test create mode 100644 maxscale-system-test/cache/cache_basic/t/update1.test create mode 100644 maxscale-system-test/cache/cache_basic/t/update2.test create mode 100644 maxscale-system-test/cache/cache_basic/t/update3.test create mode 100755 maxscale-system-test/cache_basic.sh create mode 100644 maxscale-system-test/ccrfilter.cpp create mode 100644 maxscale-system-test/cdc_client.cpp create mode 100644 maxscale-system-test/cdc_connector.cpp create mode 100644 maxscale-system-test/cdc_connector.h create mode 100644 maxscale-system-test/cdc_datatypes/CMakeLists.txt create mode 100644 maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp create mode 100644 maxscale-system-test/cdc_datatypes/cdc_result.cpp create mode 100644 maxscale-system-test/cdc_datatypes/cdc_result.h create mode 100644 maxscale-system-test/change_master.cpp create mode 100644 maxscale-system-test/change_master_during_session.cpp create mode 100644 maxscale-system-test/change_user.cpp create mode 100644 maxscale-system-test/check_backend.cpp create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.add_server create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.add_service create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.auroramon create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.avro create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.avro_compression create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bad_pers create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bad_ssl create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.batchinsert create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_cbc create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_ctr create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.binlog_incompl create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug359 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug471 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug479 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug493 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug495 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug526 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug539 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug547 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug567 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug571 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug585 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug587 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug587_1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug601 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug620 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug643 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug643_1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug645 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug645_1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug648 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug650 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug664 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug670 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug673 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug694 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug705 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug711 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug718 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bug730 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.bulk_insert create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.cache_basic create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.ccrfilter create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.check_backend create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.config_reload create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.connection_limit create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.failover_mysqlmon create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.fwf create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.fwf_action create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.fwf_com_ping create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.fwf_logging create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.fwf_syntax create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.galera create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.galera.bug681 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.galera.weight create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.galera_mxs564 create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.galera_priority create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.gatekeeper create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.hartmut create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.hints create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.hints2 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.insertstream create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.java_prep_stmt create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.kerberos create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.lag create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load_galera create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers10 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load_pers1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.load_pers10 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.longblob create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.longblob_filters create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.masking_mysqltest create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.master_only create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.maxinfo create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.maxpasswd create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.maxrows create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mm create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mm_mysqlmon create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs1045 create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs1123 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs118 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs127 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs361 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs501 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs548 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs559 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs710_bad_socket create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs711_two_ports create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs720_line_with_no_equal create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs720_wierd_line create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs722 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs799 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.mxs827_write_timeout create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs874 create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs922 create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mxs922_base create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.mysqlmon_backup create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.namedserverfilter create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.no_ses_cmd_store create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.nsfilter create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.open_close create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.pers_01 create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.regexfilter1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.repl_lgc create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.replication create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.replication.bug539 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.replication.one_slave create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.replication_manager create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_2nodes create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_3nodes create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_multi_stmt create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_read_only_trx create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_readonly create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.schemarouter_duplicate_db create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.script create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.session_limits create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.sharding create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.show_monitor_crash create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.simplejavatest create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers1 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers10 create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.ssl create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.ssl_load create mode 100755 maxscale-system-test/cnf/maxscale.cnf.template.ssl_load_galera create mode 100644 maxscale-system-test/cnf/maxscale.cnf.template.user_cache create mode 100644 maxscale-system-test/config_operations.cpp create mode 100644 maxscale-system-test/config_operations.h create mode 100644 maxscale-system-test/config_test.cpp create mode 100644 maxscale-system-test/connect_to_nonexisting_db.cpp create mode 100644 maxscale-system-test/connection_limit.cpp create mode 100755 maxscale-system-test/copy_logs.sh create mode 100644 maxscale-system-test/crash_out_of_files.cpp create mode 100644 maxscale-system-test/crash_out_of_files_galera.cpp create mode 100644 maxscale-system-test/create.sql create mode 100644 maxscale-system-test/create_rds.cpp create mode 100755 maxscale-system-test/create_rds.sh create mode 100755 maxscale-system-test/create_user.sh create mode 100755 maxscale-system-test/create_user_galera.sh create mode 100755 maxscale-system-test/create_user_ssl.sh create mode 100644 maxscale-system-test/delete_rds.cpp create mode 100755 maxscale-system-test/demo_prep1.sh create mode 100755 maxscale-system-test/demo_prep2.sh create mode 100755 maxscale-system-test/demo_script1.sh create mode 100755 maxscale-system-test/demo_script2.sh create mode 100755 maxscale-system-test/demo_script3.sh create mode 100755 maxscale-system-test/demo_testing.sh create mode 100644 maxscale-system-test/different_size.cpp create mode 100644 maxscale-system-test/different_size.h create mode 100644 maxscale-system-test/different_size_binlog.cpp create mode 100644 maxscale-system-test/different_size_rwsplit.cpp create mode 100644 maxscale-system-test/encrypted_passwords.cpp create mode 100644 maxscale-system-test/execute_cmd.cpp create mode 100644 maxscale-system-test/execute_cmd.h create mode 100644 maxscale-system-test/failover_mysqlmon.cpp create mode 100644 maxscale-system-test/false_monitor_state_change.cpp create mode 100644 maxscale-system-test/fw/deny1 create mode 100644 maxscale-system-test/fw/deny10 create mode 100644 maxscale-system-test/fw/deny11 create mode 100644 maxscale-system-test/fw/deny2 create mode 100644 maxscale-system-test/fw/deny3 create mode 100644 maxscale-system-test/fw/deny4 create mode 100644 maxscale-system-test/fw/deny5 create mode 100644 maxscale-system-test/fw/deny6 create mode 100644 maxscale-system-test/fw/deny7 create mode 100644 maxscale-system-test/fw/deny8 create mode 100644 maxscale-system-test/fw/deny9 create mode 100644 maxscale-system-test/fw/pass1 create mode 100644 maxscale-system-test/fw/pass10 create mode 100644 maxscale-system-test/fw/pass11 create mode 100644 maxscale-system-test/fw/pass2 create mode 100644 maxscale-system-test/fw/pass3 create mode 100644 maxscale-system-test/fw/pass4 create mode 100644 maxscale-system-test/fw/pass5 create mode 100644 maxscale-system-test/fw/pass6 create mode 100644 maxscale-system-test/fw/pass7 create mode 100644 maxscale-system-test/fw/pass8 create mode 100644 maxscale-system-test/fw/pass9 create mode 100644 maxscale-system-test/fw/rules1 create mode 100644 maxscale-system-test/fw/rules10 create mode 100644 maxscale-system-test/fw/rules11 create mode 100644 maxscale-system-test/fw/rules2 create mode 100644 maxscale-system-test/fw/rules3 create mode 100644 maxscale-system-test/fw/rules4 create mode 100644 maxscale-system-test/fw/rules5 create mode 100644 maxscale-system-test/fw/rules6 create mode 100644 maxscale-system-test/fw/rules7 create mode 100644 maxscale-system-test/fw/rules8 create mode 100644 maxscale-system-test/fw/rules9 create mode 100644 maxscale-system-test/fw/rules_actions create mode 100644 maxscale-system-test/fw/rules_at_time create mode 100644 maxscale-system-test/fw/rules_limit_queries create mode 100644 maxscale-system-test/fw/rules_logging create mode 100644 maxscale-system-test/fw/rules_syntax_error create mode 100644 maxscale-system-test/fw2/deny1 create mode 100644 maxscale-system-test/fw2/deny2 create mode 100644 maxscale-system-test/fw2/deny3 create mode 100644 maxscale-system-test/fw2/deny4 create mode 100644 maxscale-system-test/fw2/pass1 create mode 100644 maxscale-system-test/fw2/pass2 create mode 100644 maxscale-system-test/fw2/pass3 create mode 100644 maxscale-system-test/fw2/pass4 create mode 100644 maxscale-system-test/fw2/rules1 create mode 100644 maxscale-system-test/fw2/rules2 create mode 100644 maxscale-system-test/fw2/rules3 create mode 100644 maxscale-system-test/fw2/rules4 create mode 100644 maxscale-system-test/fw_copy_rules.cpp create mode 100644 maxscale-system-test/fw_copy_rules.h create mode 100644 maxscale-system-test/fwf.cpp create mode 100644 maxscale-system-test/fwf2.cpp create mode 100644 maxscale-system-test/fwf_actions.cpp create mode 100644 maxscale-system-test/fwf_com_ping.cpp create mode 100644 maxscale-system-test/fwf_duplicate_rules.cpp create mode 100644 maxscale-system-test/fwf_logging.cpp create mode 100644 maxscale-system-test/fwf_prepared_stmt.cpp create mode 100644 maxscale-system-test/fwf_reload.cpp create mode 100644 maxscale-system-test/fwf_syntax.cpp create mode 100644 maxscale-system-test/galera_priority.cpp create mode 100644 maxscale-system-test/gatekeeper.cpp create mode 100644 maxscale-system-test/generate_log_sql.cpp create mode 100644 maxscale-system-test/get_com_select_insert.cpp create mode 100644 maxscale-system-test/get_com_select_insert.h create mode 100755 maxscale-system-test/get_logs.sh create mode 100644 maxscale-system-test/get_my_ip.cpp create mode 100644 maxscale-system-test/get_my_ip.h create mode 100755 maxscale-system-test/insertstream.sh create mode 100644 maxscale-system-test/insertstream/r/insert.result create mode 100644 maxscale-system-test/insertstream/r/mixed.result create mode 100644 maxscale-system-test/insertstream/r/non-trx.result create mode 100644 maxscale-system-test/insertstream/t/insert.test create mode 100644 maxscale-system-test/insertstream/t/mixed.test create mode 100644 maxscale-system-test/insertstream/t/non-trx.test create mode 100755 maxscale-system-test/install_aws_tool.sh create mode 100644 maxscale-system-test/kerb.cnf create mode 100644 maxscale-system-test/kerberos_setup.cpp create mode 100644 maxscale-system-test/kill_master.cpp create mode 100644 maxscale-system-test/krb5.conf create mode 100755 maxscale-system-test/labels_list.sh create mode 100644 maxscale-system-test/load_balancing.cpp create mode 100644 maxscale-system-test/load_balancing_galera.cpp create mode 100755 maxscale-system-test/local_tests/cleanup_db.sh create mode 100644 maxscale-system-test/local_tests/create_repl_user.sql create mode 100644 maxscale-system-test/local_tests/create_skysql_user.sql create mode 100644 maxscale-system-test/local_tests/multiple_servers.cnf create mode 100644 maxscale-system-test/local_tests/set_env_local.sh create mode 100755 maxscale-system-test/local_tests/start_multiple_mariadb.sh create mode 100755 maxscale-system-test/long_insert.sh create mode 100644 maxscale-system-test/long_insert_sql/test.sql create mode 100644 maxscale-system-test/long_insert_sql/test_init.sql create mode 100644 maxscale-system-test/long_insert_sql/test_query.sql create mode 100644 maxscale-system-test/long_sysbench.cpp create mode 100644 maxscale-system-test/longblob.cpp create mode 100644 maxscale-system-test/lots_of_rows.cpp create mode 100644 maxscale-system-test/macros.cmake create mode 100755 maxscale-system-test/manage_mrm.sh create mode 100644 maxscale-system-test/mariadb_binlog_keys.txt create mode 100644 maxscale-system-test/mariadb_func.cpp create mode 100644 maxscale-system-test/mariadb_func.h create mode 100644 maxscale-system-test/mariadb_nodes.cpp create mode 100644 maxscale-system-test/mariadb_nodes.h create mode 100755 maxscale-system-test/mariadb_tests_hartmut.sh create mode 100644 maxscale-system-test/masking/README.TXT create mode 100644 maxscale-system-test/masking/masking_mysqltest/masking_rules.json create mode 100644 maxscale-system-test/masking/masking_mysqltest/r/masking_column.result create mode 100644 maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result create mode 100644 maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result create mode 100644 maxscale-system-test/masking/masking_mysqltest/t/masking_column.test create mode 100644 maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test create mode 100644 maxscale-system-test/masking/masking_mysqltest/t/masking_smoke.test create mode 100644 maxscale-system-test/masking/masking_user/masking_rules.json create mode 100644 maxscale-system-test/masking/masking_user/r/masking_user_maxskysql.result create mode 100644 maxscale-system-test/masking/masking_user/r/masking_user_skysql.result create mode 100644 maxscale-system-test/masking/masking_user/t/masking_user.test create mode 100755 maxscale-system-test/masking_mysqltest_driver.sh create mode 100755 maxscale-system-test/masking_user.sh create mode 100644 maxscale-system-test/max_connections.cpp create mode 100644 maxscale-system-test/maxadmin_operations.cpp create mode 100644 maxscale-system-test/maxadmin_operations.h create mode 100644 maxscale-system-test/maxinfo.cpp create mode 100755 maxscale-system-test/maxinfo.py create mode 100644 maxscale-system-test/maxinfo_func.cpp create mode 100644 maxscale-system-test/maxinfo_func.h create mode 100644 maxscale-system-test/maxpython.py create mode 100644 maxscale-system-test/maxscale-system-test.env create mode 100644 maxscale-system-test/maxscale/java/CMakeLists.txt create mode 100644 maxscale-system-test/maxscale/java/MaxScaleConfiguration.java.in create mode 100644 maxscale-system-test/maxscale/java/MaxScaleConnection.java create mode 100644 maxscale-system-test/maxscale/java/batch/BatchInsert.java create mode 100644 maxscale-system-test/maxscale/java/batch/CMakeLists.txt create mode 100644 maxscale-system-test/maxscale/java/mariadb-java-client-1.5.4.jar create mode 100644 maxscale-system-test/maxscale/java/prep_stmt/CMakeLists.txt create mode 100644 maxscale-system-test/maxscale/java/prep_stmt/PrepStmtTest.java create mode 100644 maxscale-system-test/maxscale/java/test1/CMakeLists.txt create mode 100644 maxscale-system-test/maxscale/java/test1/SimpleConnectorJTest.java create mode 100644 maxscale-system-test/maxscale_process_user.cpp create mode 100644 maxscale-system-test/mm.cpp create mode 100644 maxscale-system-test/mm_mysqlmon.cpp create mode 100644 maxscale-system-test/mrm/config1.toml create mode 100644 maxscale-system-test/mrm/config2.toml create mode 100644 maxscale-system-test/mxs1045.cpp create mode 100644 maxscale-system-test/mxs1071_maxrows.cpp create mode 100644 maxscale-system-test/mxs1073_binlog_enc.cpp create mode 100644 maxscale-system-test/mxs1110_16mb.cpp create mode 100644 maxscale-system-test/mxs1123.cpp create mode 100644 maxscale-system-test/mxs118.cpp create mode 100644 maxscale-system-test/mxs127.cpp create mode 100644 maxscale-system-test/mxs244_prepared_stmt_loop.cpp create mode 100644 maxscale-system-test/mxs280_select_outfile.cpp create mode 100644 maxscale-system-test/mxs314.cpp create mode 100644 maxscale-system-test/mxs321.cpp create mode 100644 maxscale-system-test/mxs365.cpp create mode 100644 maxscale-system-test/mxs37_table_privilege.cpp create mode 100644 maxscale-system-test/mxs419_lots_of_connections.cpp create mode 100644 maxscale-system-test/mxs431.cpp create mode 100644 maxscale-system-test/mxs47.cpp create mode 100644 maxscale-system-test/mxs501_tee_usedb.cpp create mode 100644 maxscale-system-test/mxs548_short_session_change_user.cpp create mode 100644 maxscale-system-test/mxs559_block_master.cpp create mode 100644 maxscale-system-test/mxs564_big_dump.cpp create mode 100755 maxscale-system-test/mxs585.py create mode 100755 maxscale-system-test/mxs598.py create mode 100644 maxscale-system-test/mxs621_unreadable_cnf.cpp create mode 100644 maxscale-system-test/mxs652_bad_ssl.cpp create mode 100644 maxscale-system-test/mxs657_restart.cpp create mode 100644 maxscale-system-test/mxs657_restart_service.cpp create mode 100644 maxscale-system-test/mxs682_cyrillic.cpp create mode 100644 maxscale-system-test/mxs710_bad_socket.cpp create mode 100644 maxscale-system-test/mxs716.cpp create mode 100644 maxscale-system-test/mxs720_line_with_no_equal.cpp create mode 100644 maxscale-system-test/mxs720_wierd_line.cpp create mode 100644 maxscale-system-test/mxs722.cpp create mode 100644 maxscale-system-test/mxs729_maxadmin.cpp create mode 100644 maxscale-system-test/mxs781_binlog_wrong_passwrd.cpp create mode 100755 maxscale-system-test/mxs791.sh create mode 100755 maxscale-system-test/mxs791_base.sh create mode 100755 maxscale-system-test/mxs791_galera.sh create mode 100644 maxscale-system-test/mxs812_1.cpp create mode 100644 maxscale-system-test/mxs812_2.cpp create mode 100644 maxscale-system-test/mxs813_long_hostname.cpp create mode 100644 maxscale-system-test/mxs822_maxpasswd.cpp create mode 100644 maxscale-system-test/mxs827_write_timeout.cpp create mode 100644 maxscale-system-test/mxs874_slave_recovery.cpp create mode 100644 maxscale-system-test/mxs922_bad_server.cpp create mode 100644 maxscale-system-test/mxs922_double_listener.cpp create mode 100644 maxscale-system-test/mxs922_listener_ssl.cpp create mode 100644 maxscale-system-test/mxs922_monitor.cpp create mode 100644 maxscale-system-test/mxs922_restart.cpp create mode 100644 maxscale-system-test/mxs922_scaling.cpp create mode 100644 maxscale-system-test/mxs922_server.cpp create mode 100644 maxscale-system-test/mxs951_utfmb4.cpp create mode 100644 maxscale-system-test/mxs957.cpp create mode 100644 maxscale-system-test/mysqlmon_backup.cpp create mode 100755 maxscale-system-test/mysqltest_driver.sh create mode 100644 maxscale-system-test/namedserverfilter.cpp create mode 100644 maxscale-system-test/no_password.cpp create mode 100644 maxscale-system-test/non_native_setup.cpp create mode 100755 maxscale-system-test/nsfilter.py create mode 100644 maxscale-system-test/open_close_connections.cpp create mode 100644 maxscale-system-test/pers_01.cpp create mode 100644 maxscale-system-test/pers_02.cpp create mode 100644 maxscale-system-test/prepared_statement.cpp create mode 100644 maxscale-system-test/rds_vpc.cpp create mode 100644 maxscale-system-test/rds_vpc.h create mode 100644 maxscale-system-test/readconnrouter_master.cpp create mode 100644 maxscale-system-test/readconnrouter_slave.cpp create mode 100644 maxscale-system-test/regexfilter1.cpp create mode 100644 maxscale-system-test/replication_manager.cpp create mode 100644 maxscale-system-test/replication_manager_2nodes.cpp create mode 100644 maxscale-system-test/replication_manager_3nodes.cpp create mode 100755 maxscale-system-test/run_ctrl_c.sh create mode 100755 maxscale-system-test/run_session_hang.sh create mode 100644 maxscale-system-test/rw_galera_select_insert.cpp create mode 100644 maxscale-system-test/rw_select_insert.cpp create mode 100644 maxscale-system-test/rwsplit_conn_num.cpp create mode 100644 maxscale-system-test/rwsplit_connect.cpp create mode 100644 maxscale-system-test/rwsplit_multi_stmt.cpp create mode 100644 maxscale-system-test/rwsplit_read_only_trx.cpp create mode 100644 maxscale-system-test/rwsplit_readonly.cpp create mode 100644 maxscale-system-test/rwsplit_readonly_stress.cpp create mode 100644 maxscale-system-test/schemarouter_duplicate_db.cpp create mode 100644 maxscale-system-test/script.cpp create mode 100644 maxscale-system-test/server_weight.cpp create mode 100644 maxscale-system-test/ses_bigmem.cpp create mode 100755 maxscale-system-test/session_hang/run_setmix.sh create mode 100644 maxscale-system-test/session_hang/setmix.sql create mode 100644 maxscale-system-test/session_hang/simpletest.pl create mode 100644 maxscale-system-test/session_limits.cpp create mode 100644 maxscale-system-test/setup_binlog.cpp create mode 100644 maxscale-system-test/setup_binlog_crc_32.cpp create mode 100644 maxscale-system-test/setup_binlog_crc_none.cpp create mode 100644 maxscale-system-test/sharding.cpp create mode 100644 maxscale-system-test/sharding_load_data.cpp create mode 100644 maxscale-system-test/short_sessions.cpp create mode 100644 maxscale-system-test/show_monitor_crash.cpp create mode 100644 maxscale-system-test/slave_failover.cpp create mode 100644 maxscale-system-test/slave_lag.cpp create mode 100644 maxscale-system-test/sql_const.h create mode 100644 maxscale-system-test/sql_queries.cpp create mode 100644 maxscale-system-test/sql_t1.cpp create mode 100644 maxscale-system-test/sql_t1.h create mode 100644 maxscale-system-test/ssl-cert/ca-key.pem create mode 100644 maxscale-system-test/ssl-cert/ca.pem create mode 100644 maxscale-system-test/ssl-cert/client-cert.pem create mode 100644 maxscale-system-test/ssl-cert/client-key.pem create mode 100644 maxscale-system-test/ssl-cert/client-req.pem create mode 100644 maxscale-system-test/ssl-cert/server-cert.pem create mode 100644 maxscale-system-test/ssl-cert/server-key.pem create mode 100644 maxscale-system-test/ssl-cert/server-req.pem create mode 100644 maxscale-system-test/ssl.cnf create mode 100644 maxscale-system-test/stale_slaves.cpp create mode 100755 maxscale-system-test/start_without_root.sh create mode 100644 maxscale-system-test/sysbench_commands.h create mode 100644 maxscale-system-test/sysbench_example.cpp create mode 100644 maxscale-system-test/sysbench_kill_slave.cpp create mode 100644 maxscale-system-test/t.cpp create mode 100644 maxscale-system-test/templates.h.in create mode 100644 maxscale-system-test/temporal_tables.cpp create mode 100644 maxscale-system-test/test_binlog_fnc.cpp create mode 100644 maxscale-system-test/test_binlog_fnc.h create mode 100755 maxscale-system-test/test_ctrl_c/start_killer.sh create mode 100755 maxscale-system-test/test_ctrl_c/test_ctrl_c.sh create mode 100644 maxscale-system-test/test_hints.cpp create mode 100644 maxscale-system-test/testconnections.cpp create mode 100644 maxscale-system-test/testconnections.h create mode 100644 maxscale-system-test/transaction_test_wo_maxscale.cpp create mode 100644 maxscale-system-test/user_cache.cpp create mode 100644 maxscale-system-test/utf64.cnf create mode 100644 maxscale-system-test/utilities.cmake diff --git a/BUILD/build_deb_local.sh b/BUILD/build_deb_local.sh new file mode 100755 index 000000000..2a3cc8352 --- /dev/null +++ b/BUILD/build_deb_local.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# do the real building work +# this script is executed on build VM + +set -x + +cd ./MaxScale + + +mkdir _build +cd _build +cmake .. $cmake_flags +export LD_LIBRARY_PATH=$PWD/log_manager:$PWD/query_classifier +make + +export LD_LIBRARY_PATH=$(for i in `find $PWD/ -name '*.so*'`; do echo $(dirname $i); done|sort|uniq|xargs|sed -e 's/[[:space:]]/:/g') +make package +res=$? +if [ $res != 0 ] ; then + echo "Make package failed" + exit $res +fi + +sudo rm ../CMakeCache.txt +sudo rm CMakeCache.txt + +echo "Building tarball..." +cmake .. $cmake_flags -DTARBALL=Y +sudo make package + + +cp _CPack_Packages/Linux/DEB/*.deb ../ + +rm ../CMakeCache.txt +rm CMakeCache.txt +cd .. +cp _build/*.deb . +cp *.deb .. +cp _build/*.gz . + +set -x +if [ "$build_experimental" == "yes" ] ; then + rm -rf _bild + mkdir _build + cd _build + export LD_LIBRARY_PATH="" + cmake .. $cmake_flags -DTARGET_COMPONENT=experimental + export LD_LIBRARY_PATH=$(for i in `find $PWD/ -name '*.so*'`; do echo $(dirname $i); done|sort|uniq|xargs|sed -e 's/[[:space:]]/:/g') + make package + cp _CPack_Packages/Linux/DEB/*.deb ../ + cd .. + cp _build/*.deb . + cp *.deb .. + cp _build/*.gz . + + rm -rf _bild + mkdir _build + cd _build + export LD_LIBRARY_PATH="" + cmake .. $cmake_flags -DTARGET_COMPONENT=devel + export LD_LIBRARY_PATH=$(for i in `find $PWD/ -name '*.so*'`; do echo $(dirname $i); done|sort|uniq|xargs|sed -e 's/[[:space:]]/:/g') + make package + cp _CPack_Packages/Linux/DEB/*.deb ../ + cd .. + cp _build/*.deb . + cp *.deb .. + cp _build/*.gz . +fi + +if [ "$BUILD_RABBITMQ" == "yes" ] ; then + cmake ../rabbitmq_consumer/ $cmake_flags + sudo make package + res=$? + if [ $res != 0 ] ; then + exit $res + fi + cp _CPack_Packages/Linux/DEB/*.deb ../ + cd .. + cp _build/*.deb . + cp *.deb .. +fi +sudo dpkg -i ../maxscale*.dev +set +x diff --git a/BUILD/build_rpm_local.sh b/BUILD/build_rpm_local.sh new file mode 100755 index 000000000..ae78b6698 --- /dev/null +++ b/BUILD/build_rpm_local.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +# do the real building work +# this script is executed on build VM + +set -x + +cd ./MaxScale + +mkdir _build +cd _build +cmake .. $cmake_flags +make + +if [ $remove_strip == "yes" ] ; then + sudo rm -rf /usr/bin/strip + sudo touch /usr/bin/strip + sudo chmod a+x /usr/bin/strip +fi +sudo make package +res=$? +if [ $res != 0 ] ; then + echo "Make package failed" + exit $res +fi + +sudo rm ../CMakeCache.txt +sudo rm CMakeCache.txt + +echo "Building tarball..." +cmake .. $cmake_flags -DTARBALL=Y +sudo make package + +cd .. +cp _build/*.rpm . +cp _build/*.gz . + +if [ "$build_experimental" == "yes" ] ; then + sudo rm -rf _build + mkdir _build + cd _build + cmake .. $cmake_flags -DTARGET_COMPONENT=experimental + sudo make package + cd .. + cp _build/*.rpm . + cp _build/*.gz . + + sudo rm -rf _build + mkdir _build + cd _build + cmake .. $cmake_flags -DTARGET_COMPONENT=devel + sudo make package + cd .. + cp _build/*.rpm . + cp _build/*.gz . +fi + +if [ "$BUILD_RABBITMQ" == "yes" ] ; then + cmake ../rabbitmq_consumer/ $cmake_flags + sudo make package + res=$? + if [ $res != 0 ] ; then + exit $res + fi + cd .. + cp _build/*.rpm . + cp _build/*.gz . +fi + +sudo rpm -i maxscale*.rpm diff --git a/BUILD/install_build_deps.sh b/BUILD/install_build_deps.sh new file mode 100755 index 000000000..8ea47b6be --- /dev/null +++ b/BUILD/install_build_deps.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +# Do the real building work. This script is executed on build VM and +# requires a working installation of CMake. + +# Check if CMake needs to be installed +command -v cmake || install_cmake="cmake" + + +command -v apt-get + +if [ $? -e 0 ] +then + # DEB-based distro + + sudo apt-get update + + sudo apt-get install -y --force-yes dpkg-dev git gcc g++ ncurses-dev bison \ + build-essential libssl-dev libaio-dev perl make libtool libcurl4-openssl-dev \ + libpcre3-dev flex tcl libeditline-dev uuid-dev liblzma-dev libsqlite3-dev \ + sqlite3 liblua5.1 liblua5.1-dev libgnutls30 libgcrypt20 $install_cmake +else + ## RPM-based distro + command -v yum + + if [ $? -ne 0 ] + then + # We need zypper here + sudo zypper -n install gcc gcc-c++ ncurses-devel bison glibc-devel libgcc_s1 perl \ + make libtool libopenssl-devel libaio libaio-devel flex libcurl-devel \ + pcre-devel git wget tcl libuuid-devel \ + xz-devel sqlite3 sqlite3-devel pkg-config lua lua-devel \ + gnutls gcrypt $install_cmake + sudo zypper -n install rpm-build + cat /etc/*-release | grep "SUSE Linux Enterprise Server 11" + + if [ $? -ne 0 ] + then + sudo zypper -n install libedit-devel + fi + else + # YUM! + sudo yum clean all + sudo yum install -y --nogpgcheck gcc gcc-c++ ncurses-devel bison glibc-devel \ + libgcc perl make libtool openssl-devel libaio libaio-devel libedit-devel \ + libedit-devel libcurl-devel curl-devel systemtap-sdt-devel rpm-sign \ + gnupg pcre-devel flex rpmdevtools git wget tcl openssl libuuid-devel xz-devel \ + sqlite sqlite-devel pkgconfig lua lua-devel rpm-build createrepo yum-utils \ + gnutls gcrypt $install_cmake + + cat /etc/redhat-release | grep "release 5" + if [ $? -eq 0 ] + then + sudo yum remove -y libedit-devel libedit + fi + fi + +fi + +# Flex +wget http://maxscale-jenkins.mariadb.com/x/flex-2.5.35-0.8.el5.rfb.x86_64.rpm +sudo yum install flex-2.5.35-0.8.el5.rfb.x86_64.rpm -y --nogpgcheck +rm flex-2.5.35-0.8.el5.rfb.x86_64* + +# RabbitMQ C client +mkdir rabbit +cd rabbit +git clone https://github.com/alanxz/rabbitmq-c.git + +if [ $? -ne 0 ] +then + echo "Error cloning rabbitmq-c" + exit 1 +fi + +cd rabbitmq-c +git checkout v0.7.1 +cmake . -DCMAKE_C_FLAGS=-fPIC -DBUILD_SHARED_LIBS=N -DCMAKE_INSTALL_PREFIX=/usr +sudo make install +cd ../../ + +# TCL +mkdir tcl +cd tcl +wget --no-check-certificate http://prdownloads.sourceforge.net/tcl/tcl8.6.5-src.tar.gz + +if [ $? -ne 0 ] +then + echo "Error getting tcl" + exit 1 +fi + +tar xzvf tcl8.6.5-src.tar.gz +cd tcl8.6.5/unix +./configure +sudo make install +cd ../../.. + + +# Jansson +git clone https://github.com/akheron/jansson.git +if [ $? != 0 ] +then + echo "Error cloning jansson" + exit 1 +fi + +mkdir -p jansson/build +pushd jansson/build +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS=-fPIC -DJANSSON_INSTALL_LIB_DIR=/usr/lib64 +make +sudo make install +popd + +# Avro C API +wget -r -l1 -nH --cut-dirs=2 --no-parent -A.tar.gz --no-directories http://mirror.netinch.com/pub/apache/avro/stable/c +if [ $? != 0 ] +then + echo "Error getting avro-c" + exit 1 +fi +avro_filename=`ls -1 *.tar.gz` +avro_dir=`echo "$avro_filename" | sed "s/.tar.gz//"` +tar -axf $avro_filename +mkdir $avro_dir/build +pushd $avro_dir/build +cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS=-fPIC -DCMAKE_CXX_FLAGS=-fPIC +make +sudo make install +popd diff --git a/BUILD/run_test.sh b/BUILD/run_test.sh new file mode 100644 index 000000000..57ea40d3b --- /dev/null +++ b/BUILD/run_test.sh @@ -0,0 +1,6 @@ +cd ~/Maxscale/maxscale-system-test + +cmake . +make + +ctest -LE HEAVY -VV diff --git a/maxscale-system-test/CMakeLists.txt b/maxscale-system-test/CMakeLists.txt new file mode 100644 index 000000000..ea935739e --- /dev/null +++ b/maxscale-system-test/CMakeLists.txt @@ -0,0 +1,702 @@ +# Building test package: +# +# apt-get install libssl-dev libmariadbclient-dev php5 perl \ +# coreutils realpath libjansson-dev openjdk-7-jdk +# pip install JayDeBeApi + + +# Backend labes: +# REPL_BACKEND +# GALERA_BACKEND +# EXTERN_BACKEND +# BREAKS_REPL +# BREAKS_GALERA + +project(maxscale_system_test) +cmake_minimum_required(VERSION 2.8) +include_directories("/usr/include/mysql/") +set(CTEST_BUILD_NAME "${BUILDNAME}") + +set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Choose the type of +build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug +Release RelWithDebInfo MinSizeRel.") + +set(CMAKE_CXX_FLAGS "-std=c++11 -ggdb") +set(CMAKE_CXX_FLAGS_DEBUG "-std=c++11 -ggdb") +set(CMAKE_CXX_FLAGS_RELEASE "-std=c++11 -ggdb") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-std=c++11 -ggdb") + +enable_testing() + +# utilities.cmake contains all helper functions and extra tools +include(utilities.cmake) + +# Is this needed? +configure_file(${CMAKE_SOURCE_DIR}/cnf/maxscale.cnf.template.setup_binlog.in ${CMAKE_BINARY_DIR}/cnf/maxscale.cnf.template.setup_binlog @ONLY) + +# Enable Java +find_package(Java) +if(EXISTS ${Java_JAVA_EXECUTABLE} ${JAVA_JAVAC_EXECUTABLE} ${JAVA_JAR_EXECUTABLE}) + include(UseJava) + if(Java_FOUND) + add_subdirectory(maxscale/java/) + endif() +else() + message(WARNING "Java not found, Java based tests are not run.") +endif() + +# The core library +add_library(testcore SHARED testconnections.cpp mariadb_nodes.cpp + mariadb_func.cpp get_com_select_insert.cpp maxadmin_operations.cpp big_transaction.cpp + sql_t1.cpp test_binlog_fnc.cpp get_my_ip.cpp big_load.cpp get_com_select_insert.cpp + different_size.cpp fw_copy_rules maxinfo_func.cpp config_operations.cpp rds_vpc.cpp execute_cmd.cpp + blob_test.cpp cdc_connector.cpp) +target_link_libraries(testcore ${MYSQL_CLIENT} z crypt nsl m pthread ssl crypto dl rt jansson) +install(TARGETS testcore DESTINATION system-test) +add_dependencies(testcore connector-c) + +# Tool used to check backend state +add_test_executable_notest(check_backend.cpp check_backend check_backend LABELS CONFIG) + +# Configuration tests +add_template(bug359 bug359) +add_template(bug495 bug495) +add_template(bug526 bug526) +add_template(bug479 bug479) +add_template(bug493 bug493) +add_template(bug643_1 bug643_1) +add_template(mxs652_bad_ssl bad_ssl) +add_template(mxs710_bad_socket mxs710_bad_socket) +add_template(mxs710_bad_socket mxs711_two_ports) +add_template(mxs720_line_with_no_equal mxs720_line_with_no_equal) +add_template(mxs720_wierd_line mxs720_wierd_line) +add_template(mxs710_bad_socket mxs799) +add_test_executable(config_test.cpp config_test replication LABELS CONFIG) + +add_subdirectory(cdc_datatypes) + +# Repeatedly connect to maxscale while the backends reject all connections, expect no crash +add_test_executable(backend_auth_fail.cpp backend_auth_fail replication LABELS readconnroute REPL_BACKEND) + +# Regression case for the bug "MaxScale ignores host in user authentication" +add_test_executable(bug143.cpp bug143 replication LABELS MySQLAuth REPL_BACKEND) + +# Regression case for the bug "Executing '\s' doesn't always produce complete result set" +add_test_executable(bug422.cpp bug422 replication LABELS readwritesplit readconnroute maxscale REPL_BACKEND) + +# Regression case for the bug "Wildcard in host column of mysql.user table don't work properly" +add_test_executable(bug448.cpp bug448 replication LABELS MySQLAuth LIGHT REPL_BACKEND) + +# Regression case for the bug "rwsplit counts every connection twice in master - counnection counts leak" +add_test_executable(bug469.cpp bug469 replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Regression case for the bug "Routing Hints route to server sometimes doesn't work" +add_test_executable(bug471.cpp bug471 bug471 LABELS readwritesplit hintfilter REPL_BACKEND) + +# Regression case for the bugs "malformed hints cause crash" +add_test_executable(bug473.cpp bug473 hints LABELS readwritesplit hintfilter REPL_BACKEND) + +# Regression case for the bug "The end comment tag in hints isn't properly detected" +add_test_executable(bug475.cpp bug475 hints LABELS readwritesplit hintfilter REPL_BACKEND) + +# Regression case for the bug "SHOW VARIABLES randomly failing with "Lost connection to MySQL server" +add_test_executable(bug488.cpp bug488 galera LABELS readwritesplit readconnroute maxscale GALERA_BACKEND) + +# Regression case for the bug "rw-split router does not send last_insert_id() to master" +add_test_executable(bug507.cpp bug507 replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Regression case for the bug "Referring to a nonexisting server in servers=... doesn't even raise a warning" +add_test_executable(bug509.cpp bug509 galera LABELS readwritesplit GALERA_BACKEND) + +# Checks "SELECT * INTO OUTFILE" and "LOAD DATA LOCAL INFILE" +add_test_executable(bug519.cpp bug519 replication LABELS readwritesplit HEAVY REPL_BACKEND) + +# Regression case for the bug "'Current no. of conns' not going down" +add_test_executable(bug529.cpp bug529 replication LABELS readwritesplit readconnroute maxscale REPL_BACKEND) + +# Regression case for the bugs "get_dcb fails if slaves are not available" and "Maxscale fails to start without anything in the logs if there is no slave available" +add_test_executable(bug547.cpp bug547 replication LABELS readwritesplit REPL_BACKEND) + +# Regression case for the bug "crash if max_slave_connections=10% and 4 or less backends are configured" +add_test_executable(bug681.cpp bug681 galera.bug681 LABELS readwritesplit GALERA_BACKEND) + +# Regression case for the bug "crash with tee filter" +add_test_executable(bug643.cpp bug643 bug643 LABELS tee REPL_BACKEND) + +# Regression case for the bug ""Different error messages from MariaDB and Maxscale" +add_test_script(bug561.sh bug561.sh replication LABELS MySQLAuth REPL_BACKEND) + +# Regression case for the bug "Wrong error message for Access denied error" +add_test_script(bug562.sh bug562.sh replication LABELS MySQLAuth REPL_BACKEND) + +# Regression case for the bug "Wrong charset settings" +add_test_script(bug564.sh bug564.sh replication LABELS MySQLProtocol REPL_BACKEND) + +# Regression case for the bug "Clients CLIENT_FOUND_ROWS setting is ignored by maxscale" +add_test_executable(bug565.cpp bug565 replication LABELS MySQLProtocol REPL_BACKEND) + +# Regression case for the bug "Crash if files from /dev/shm/ removed" +add_test_script(bug567.sh bug567.sh bug567 LABELS maxscale REPL_BACKEND) + +# Regression case for the bug "Using regex filter hangs MaxScale" +add_test_executable(bug571.cpp bug571 bug571 LABELS regexfilter REPL_BACKEND) + +# Attempt to use GRANT with wrong IP, expect no crash or hangs +add_test_executable(bug572.cpp bug572 replication LABELS readwritesplit REPL_BACKEND) + +# Regression cases for the bug "Hint filter don't work if listed before regex filter in configuration file" +# (different filter sequence and configuration, but the same test, see .cnf for details) +add_test_script(bug585 bug587 bug585 LABELS regexfilter REPL_BACKEND) +add_test_executable(bug587.cpp bug587 bug587 LABELS regexfilter hintfilter REPL_BACKEND) +add_test_script(bug587_1 bug587 bug587_1 LABELS regexfilter hintfilter REPL_BACKEND) + +# Tries to connect Maxscale when all slaves stopped +add_test_executable(bug592.cpp bug592 replication LABELS MySQLAuth readwritesplit REPL_BACKEND) + +# Tries to do change user in the loop, checks that autorization is still ok +add_test_executable(bug601.cpp bug601 bug601 LABELS MySQLAuth MySQLProtocol REPL_BACKEND) + +# Simple test with enable_root_user=true +add_test_executable(bug620.cpp bug620 bug620 LABELS MySQLAuth MySQLProtocol REPL_BACKEND) + +# Regression case for the bug "Crash when user define with old password style (before 4.1 protocol)" +add_test_executable(bug626.cpp bug626 replication LABELS MySQLAuth MySQLProtocol REPL_BACKEND) + +# Regression case for the bug 634 "SHOW SLAVE STATUS in RW SPLITTER is send to master" +add_test_executable(bug634.cpp bug634 replication LABELS readwritesplit REPL_BACKEND) + +# Regression cases for several TEE filter hangs +add_test_executable(bug645.cpp bug645 bug645 LABELS tee REPL_BACKEND) +add_test_executable(bug645_1.cpp bug645_1 bug645_1 LABELS tee REPL_BACKEND) +add_test_executable(bug649.cpp bug649 bug645 LABELS tee) +add_test_executable(bug650.cpp bug650 bug650 LABELS tee REPL_BACKEND) + +# Heavy test for TEE filter +add_test_script(bug648 sql_queries bug648 LABELS tee UNSTABLE HEAVY REPL_BACKEND) + +# Crash when host name for some user in mysql.user is very long +add_test_executable(bug653.cpp bug653 replication LABELS MySQLAuth MySQLProtocol REPL_BACKEND) + +# Crash with malformed Maxadmin command +add_test_executable(bug654.cpp bug654 replication LABELS maxscale REPL_BACKEND) + +# Regression case for the bug "Tee filter: closing child session causes MaxScale to fail" +add_test_executable(bug657.cpp bug657 bug657 LABELS tee REPL_BACKEND) + +# Block backends (master or all slaves) and tries to connect Maxscale +add_test_executable(bug658.cpp bug658 replication LABELS readwritesplit readconnroute maxscale REPL_BACKEND) + +# Block all backends +add_test_executable(bug662.cpp bug662 replication LABELS readwritesplit readconnroute maxscale REPL_BACKEND) + +# Bad TEE filter configuration +add_test_executable(bug664.cpp bug664 bug664 LABELS MySQLAuth MySQLProtocol) + +# TEE fileter: execute long sequence of queries ans session commands in the loop +add_test_executable(bug670.cpp bug670 bug670 LABELS tee REPL_BACKEND) + +# Regression case for the bug "MaxScale crashes if "Users table data" is empty and "show dbusers" is executed in maxadmin" +add_test_executable(bug673.cpp bug673 bug673 LABELS MySQLAuth REPL_BACKEND) + +# Crash in case of backend node in Galera cluster stopping and then reconnect to Maxscale +add_test_executable(bug676.cpp bug676 galera LABELS galeramon GALERA_BACKEND) + +# Rgression test for th bug "RWSplit: 'SELECT @a:=@a+1 as a, test.b FROM test' breaks client session" +add_test_executable(bug694.cpp bug694 bug694 LABELS readwritesplit REPL_BACKEND) + +# Compare @@hostname from "select @@wsrep_node_name, @@hostname" and from "select @@hostname, @@wsrep_node_name" +add_test_executable(bug699.cpp bug699 galera LABELS readwritesplit LIGHT GALERA_BACKEND) + +# Wrong processing of 'SET GLOBAL sql_mode="ANSI"' +add_test_executable(bug705.cpp bug705 bug705 LABELS MySQLAuth REPL_BACKEND) + +# Try SHOW GLOBAL STATUS via Maxscale +add_test_executable(bug711.cpp bug711 bug711 LABELS readwritesplit REPL_BACKEND) + +# Prepared statement from PHP application +add_test_executable(bug729.cpp bug729 replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Regression case for the bug "Regex filter and shorter than original replacement queries MaxScale" (crash) +add_test_executable(bug730.cpp bug730 bug730 LABELS regexfilter REPL_BACKEND) + +# Test MariaDB 10.2 bulk inserts +add_test_executable(bulk_insert.cpp bulk_insert bulk_insert LABELS MySQLProtocol REPL_BACKEND 10.2) + +# Tests for the CCRFilter module +add_test_executable(ccrfilter.cpp ccrfilter ccrfilter LABELS ccrfilter LIGHT REPL_BACKEND) + +# Tries to reconfigure replication setup to use another node as a Master +add_test_executable(change_master_during_session.cpp change_master_during_session replication LABELS readwritesplit mysqlmon REPL_BACKEND) + +# Executes change_user command in the loop +add_test_executable(change_user.cpp change_user replication LABELS MySQLAuth MySQLProtocol LIGHT REPL_BACKEND) + +# Tries to connect to non existing DB, expects no crash +add_test_executable(connect_to_nonexisting_db.cpp connect_to_nonexisting_db replication LABELS MySQLAuth MySQLProtoco LIGHT REPL_BACKEND) + +# check if max_connections parameter works +add_test_executable(connection_limit.cpp connection_limit connection_limit LABELS maxscale LIGHT REPL_BACKEND) + +# Tries to open to many connections, expect no crash +add_test_executable(crash_out_of_files.cpp crash_out_of_files load LABELS maxscale HEAVY REPL_BACKEND) + +# Tries to open to many connections, expect no crash, with Galera backend +add_test_executable(crash_out_of_files_galera.cpp crash_out_of_files_galera galera LABELS maxscale HEAVY GALERA_BACKEND) + +# Tries INSERTs with size close to 0x0ffffff * N +add_test_executable(different_size_rwsplit.cpp different_size_rwsplit replication LABELS readwritesplit HEAVY REPL_BACKEND) + +# Tries to use 'maxkeys', 'maxpasswrd' +add_test_executable(encrypted_passwords.cpp encrypted_passwords replication LABELS maxscale LIGHT REPL_BACKEND) + +# MySQL Monitor Failover Test +add_test_executable(failover_mysqlmon.cpp failover_mysqlmon failover_mysqlmon LABELS mysqlmon REPL_BACKEND) + +# Test monitor state change events when manually clearing server bits +add_test_executable(false_monitor_state_change.cpp false_monitor_state_change replication LABELS mysqlmon REPL_BACKEND) + +# A set of tests for Firewall filter +add_test_executable(fwf.cpp fwf fwf LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf2.cpp fwf2 fwf LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_duplicate_rules.cpp fwf_duplicate_rules fwf LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_prepared_stmt.cpp fwf_prepared_stmt fwf LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_actions.cpp fwf_actions fwf_action LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_logging.cpp fwf_logging fwf_logging LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_reload.cpp fwf_reload fwf LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_syntax.cpp fwf_syntax fwf_syntax LABELS dbfwfilter REPL_BACKEND) +add_test_executable(fwf_com_ping.cpp fwf_com_ping fwf_com_ping LABELS dbfwfilter REPL_BACKEND) + +# Galera node priority test +add_test_executable(galera_priority.cpp galera_priority galera_priority LABELS galeramon LIGHT GALERA_BACKEND) + +# Block and unblock Master and check that Maxscale survived +add_test_executable(kill_master.cpp kill_master replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Test insertstream filter +add_test_script(insertstream insertstream.sh insertstream LABELS insertstream REPL_BACKEND) + +# Check load balancing +add_test_executable(load_balancing.cpp load_balancing load LABELS readwritesplit LIGHT REPL_BACKEND) + +# Check load balancing with Galera backend +add_test_executable(load_balancing_galera.cpp load_balancing_galera load_galera LABELS readwritesplit GALERA_BACKEND) + +# Check load balancing parameters with Galera backend and 1 persistent connection +add_test_script(load_balancing_galera_pers1 load_balancing_galera load_galera_pers1 LABELS readwritesplit HEAVY GALERA_BACKEND) + +# Check load balancing parameters with Galera backend and 10 persistent connections +add_test_script(load_balancing_galera_pers10 load_balancing_galera load_galera_pers10 LABELS readwritesplit HEAVY GALERA_BACKEND) + +# Check load balancing parameters with 1 persistent connection +add_test_script(load_balancing_pers1 load_balancing load_pers1 LABELS readwritesplit HEAVY REPL_BACKEND) + +# Check load balancing parameters with 10 persistent connections +add_test_script(load_balancing_pers10 load_balancing load_pers10 LABELS readwritesplit HEAVY REPL_BACKEND) + +# Test with extremely big blob inserting +add_test_executable(longblob.cpp longblob longblob LABELS readwritesplit readconnroute UNSTABLE HEAVY REPL_BACKEND) + +# Test with extremely big blob inserting/selecting with > 16 mb data blocks +add_test_executable(mxs1110_16mb.cpp mxs1110_16mb longblob_filters LABELS readwritesplit readconnroute HEAVY REPL_BACKEND) + +# INSERT extremelly big number of rows +add_test_executable(lots_of_rows.cpp lots_of_rows galera LABELS readwritesplit HEAVY GALERA_BACKEND) + +# A set of MariaDB server tests executed against Maxscale RWSplit +add_test_script(mariadb_tests_hartmut mariadb_tests_hartmut.sh replication LABELS readwritesplit REPL_BACKEND) + +# A set of MariaDB server tests executed against Maxscale RWSplit (Galera backend) +add_test_script(mariadb_tests_hartmut_galera mariadb_tests_hartmut.sh galera LABELS readwritesplit GALERA_BACKEND) + +# Creates a number of connections > max_connections setting +add_test_executable(max_connections.cpp max_connections replication LABELS MySQLAuth MySQLProtocol UNSTABLE HEAVY REPL_BACKEND) + +# Test of Maxinfo interface (http) +#add_test_executable(maxinfo.cpp maxinfocpp maxinfo LABELS maxinfo UNSTABLE HEAVY REPL_BACKEND) + +# Test of Maxinfo interface (http), python impelemntation +add_test_script(maxinfo.py maxinfo.py maxinfo LABELS maxinfo LIGHT REPL_BACKEND) + +# Checks tha Maxscale processis running as 'maxscale' user +add_test_executable(maxscale_process_user.cpp maxscale_process_user replication LABELS maxscale LIGHT REPL_BACKEND) + +# Test of multi master monitor +add_test_executable(mm.cpp mm mm LABELS mmmon BREAKS_REPL) + +# MySQL Monitor with Multi-master configurations +add_test_executable(mm_mysqlmon.cpp mm_mysqlmon mm_mysqlmon LABELS mysqlmon REPL_BACKEND BREAKS_REPL) + +# MySQL Monitor crash safety +add_test_executable(mysqlmon_backup.cpp mysqlmon_backup mysqlmon_backup LABELS mysqlmon REPL_BACKEND) + +# Regression case for the bug "Two monitors loaded at the same time result into not working installation" +add_test_executable(mxs118.cpp mxs118 mxs118 LABELS maxscale LIGHT REPL_BACKEND) + +# Regression case for the bug "disable_sescmd_history causes MaxScale to crash under load" +add_test_executable(mxs127.cpp mxs127 mxs127 LABELS readwritesplit LIGHT REPL_BACKEND) + +# Prepearing and execution statements in the loop +add_test_executable(mxs244_prepared_stmt_loop.cpp mxs244_prepared_stmt_loop galera LABELS readwritesplit readconnroute LIGHT GALERA_BACKEND) + +# Regression case for the bug "SELECT INTO OUTFILE query succeeds even if backed fails" +add_test_executable(mxs280_select_outfile.cpp mxs280_select_outfile replication LABELS readwritesplit REPL_BACKEND) + +# Tries prepared stmt 'SELECT 1,1,1,1...." with different nu,ber of '1' +add_test_executable(mxs314.cpp mxs314 galera LABELS MySQLProtocol LIGHT GALERA_BACKEND) + +# Creates and closes a lot of connections, checks that 'maxadmin list servers' shows 0 connections at the end +add_test_executable(mxs321.cpp mxs321 replication LABELS maxscale REPL_BACKEND) + +# Crash with Galera and backend restart when persistant cfonnections are in use +add_test_script(mxs361 pers_02 mxs361 mxs361 LABELS maxscale GALERA_BACKEND) + +# Load huge file with 'LOAD DATA LOCAL INFILE' +add_test_executable(mxs365.cpp mxs365 replication LABELS readwritesplit REPL_BACKEND) + +# Connect to Maxscale with user with only 'SELECT' priveledge +add_test_executable(mxs37_table_privilege.cpp mxs37_table_privilege replication LABELS MySQLAuth LIGHT REPL_BACKEND) + +# Connect to Maxscale with user with only 'SELECT' priveledge (Galera backend) +add_test_script(mxs37_table_privilege_galera mxs37_table_privilege galera LABELS MySQLAuth GALERA_BACKEND) + +# Connect repeatedly to Schema router and execute simple query, check if auth is ok +add_test_executable(mxs431.cpp mxs431 sharding LABELS schemarouter REPL_BACKEND BREAKS_REPL) + +# execute SELECT REPEAT('a',i), where 'i' is changing from 1 to 50000 (bug "Session freeze when small tail packet") +add_test_executable(mxs47.cpp mxs47 replication LABELS MySQLProtocol LIGHT REPL_BACKEND) + +# Regression case for the bug "USE hangs when Tee filter uses matching" +add_test_executable(mxs501_tee_usedb.cpp mxs501_tee_usedb mxs501 LABELS tee REPL_BACKEND) + +# Open connection, execute 'change user', close connection in the loop +add_test_executable(mxs548_short_session_change_user.cpp mxs548_short_session_change_user mxs548 LABELS MySQLProtocol REPL_BACKEND) + +# Playing with blocking and unblocking Master under load +add_test_executable(mxs559_block_master.cpp mxs559_block_master mxs559 LABELS readwritesplit REPL_BACKEND) + +# Playing with blocking and unblocking nodes under INSERT load +add_test_executable(mxs564_big_dump.cpp mxs564_big_dump galera_mxs564 LABELS readwritesplit readconnroute GALERA_BACKEND) + +# Executes simple queries from python script in the loop +add_test_script(mxs585.py mxs585.py replication LABELS readwritesplit readconnroute UNSTABLE HEAVY REPL_BACKEND) + +# Simple transactions in the loop from python script with client SSL on +add_test_script(mxs598.py mxs598.py ssl LABELS MySQLProtocol UNSTABLE HEAVY REPL_BACKEND) + +# Regression case for the bug "MaxScale fails to start silently if config file is not readable" +add_test_executable(mxs621_unreadable_cnf.cpp mxs621_unreadable_cnf replication LABELS maxscale REPL_BACKEND) + +# playing with 'restart service' and restart Maxscale under load +add_test_executable(mxs657_restart.cpp mxs657_restart replication LABELS maxscale HEAVY REPL_BACKEND) +add_test_executable(mxs657_restart_service.cpp mxs657_restart_service replication LABELS maxscale REPL_BACKEND) + +# put cyrillic letters to the table and check from backend +add_test_executable(mxs682_cyrillic.cpp mxs682_cyrillic replication LABELS maxscale LIGHT REPL_BACKEND) + +# put cyrillic letters to the table and check from backend (Galera backend) +add_test_script(mxs682_cyrillic_galera mxs682_cyrillic galera LABELS maxscale GALERA_BACKEND) + +# Connect using different default database using user with database and table level grants +add_test_executable(mxs716.cpp mxs716 replication LABELS MySQLAuth LIGHT REPL_BACKEND) + +# MaxScale configuration check functionality test (maxscale -c) +add_test_executable(mxs722.cpp mxs722 mxs722 LABELS maxscale LIGHT REPL_BACKEND) + +# Test of 'maxadmin' user Unix accounts enable/disable +add_test_executable(mxs729_maxadmin.cpp mxs729_maxadmin replication LABELS MaxAdminAuth LIGHT REPL_BACKEND) + +# Simple connect test in bash, checks that defined in cmd line DB is selected +add_test_script(mxs791.sh mxs791.sh replication LABELS UNSTABLE HEAVY REPL_BACKEND) + +# Simple connect test in bash, checks that defined in cmd line DB is selected (Galera backend) +add_test_script(mxs791_galera.sh mxs791_galera.sh galera LABELS UNSTABLE HEAVY GALERA_BACKEND) + +# Checks "Current no. of conns" maxadmin output after long blob inserting +add_test_executable(mxs812_1.cpp mxs812_1 longblob LABELS readwritesplit REPL_BACKEND) + +# Checks "Current no. of conns" maxadmin output after long blob inserting +add_test_executable(mxs812_2.cpp mxs812_2 longblob LABELS readwritesplit REPL_BACKEND) + +# Execute prepared statements while master is blocked, checks "Current no. of conns" after the test +add_test_executable(mxs822_maxpasswd.cpp mxs822_maxpasswd maxpasswd LABELS maxscale REPL_BACKEND) + +# Do only SELECTS during time > wait_timeout and then do INSERT +# This test will fail because the functionality hasn't been implemented +add_test_executable(mxs827_write_timeout.cpp mxs827_write_timeout mxs827_write_timeout LABELS readwritesplit REPL_BACKEND) + +# Block and unblock first and second slaves and check that they are recovered +add_test_executable(mxs874_slave_recovery.cpp mxs874_slave_recovery mxs874 LABELS readwritesplit REPL_BACKEND) + +# A set of dynamic configuration tests +# Server removal test +add_test_executable(mxs922_bad_server.cpp mxs922_bad_server mxs922 LABELS maxscale REPL_BACKEND) + +# Server creation test +add_test_executable(mxs922_server.cpp mxs922_server mxs922_base LABELS maxscale REPL_BACKEND) + +# Monitor creation test +add_test_executable(mxs922_monitor.cpp mxs922_monitor mxs922_base LABELS maxscale REPL_BACKEND) + +# Double creation of listeners, expect no crash +add_test_executable(mxs922_double_listener.cpp mxs922_double_listener mxs922_base LABELS maxscale REPL_BACKEND) + +# Test persisting of configuration changes +add_test_executable(mxs922_restart.cpp mxs922_restart mxs922 LABELS maxscale REPL_BACKEND) + +# Server scaling test +add_test_executable(mxs922_scaling.cpp mxs922_scaling mxs922_base LABELS maxscale REPL_BACKEND) + +# Dynamic listener SSL test +add_test_executable(mxs922_listener_ssl.cpp mxs922_listener_ssl mxs922_base LABELS maxscale REPL_BACKEND) + +# Test of MaxRows filter +add_test_executable(mxs1071_maxrows.cpp mxs1071_maxrows maxrows LABELS maxrowsfilter REPL_BACKEND) + +# Test of Masking filter +add_test_script(masking_mysqltest masking_mysqltest_driver.sh masking_mysqltest LABELS maskingfilter REPL_BACKEND) + +add_test_script(masking_user masking_user.sh masking_mysqltest LABELS maskingfilter REPL_BACKEND) + +# Test of Cache filter +add_test_script(cache_basic cache_basic.sh cache_basic LABELS cachefilter REPL_BACKEND) + +# Set utf8mb4 in the backend and restart Maxscale +add_test_executable(mxs951_utfmb4.cpp mxs951_utfmb4 replication LABELS REPL_BACKEND) + +# Execute given SQL through readwritesplit (with temporary tables usage) +add_test_executable(mxs957.cpp mxs957 replication LABELS readwritesplit REPL_BACKEND) + +# Regression case for the bug "Defunct processes after maxscale have executed script during failover" +add_test_executable(mxs1045.cpp mxs1045 mxs1045 LABELS maxscale REPL_BACKEND) + +# MXS-1123: connect_timeout setting causes frequent disconnects +# https://jira.mariadb.org/browse/MXS-1123 +add_test_executable(mxs1123.cpp mxs1123 mxs1123 LABELS maxscale REPL_BACKEND) + +# 'namedserverfilter' test +add_test_executable(namedserverfilter.cpp namedserverfilter namedserverfilter LABELS namedserverfilter LIGHT REPL_BACKEND) + +# Authentication error testing +add_test_executable(no_password.cpp no_password replication LABELS MySQLAuth LIGHT REPL_BACKEND) + +# Open and immediatelly close a big number of connections +add_test_executable(open_close_connections.cpp open_close_connections replication LABELS maxscale REPL_BACKEND) + +# Open and immediatelly close a big number of connections, ssl is in use +# +# The test is broken due to some problem in the connector. It crashes with a +# double free error somewhere deep inside the connector/SSL libraries. +# +# add_test_script(open_close_connections_ssl open_close_connections ssl LABELS maxscale REPL_BACKEND) + +# Persistant connection test +add_test_executable(pers_01.cpp pers_01 pers_01 LABELS maxscale REPL_BACKEND GALERA_BACKEND) + +# Test with persistant connections configured and big number iof opened connections ,expect no crash +add_test_executable(pers_02.cpp pers_02 pers_01 LABELS maxscale REPL_BACKEND GALERA_BACKEND) + +# Check if prepared statement works via Maxscale (via RWSplit) +add_test_executable(prepared_statement.cpp prepared_statement replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Connect to ReadConn in master mode and check if there is only one backend connection to master +add_test_executable(readconnrouter_master.cpp readconnrouter_master replication LABELS readconnroute LIGHT REPL_BACKEND) + +# Creates 100 connections to ReadConn in slave mode and check if connections are distributed among all slaves +add_test_executable(readconnrouter_slave.cpp readconnrouter_slave replication LABELS readconnroute LIGHT REPL_BACKEND) + +# Regex filter test +add_test_executable(regexfilter1.cpp regexfilter1 regexfilter1 LABELS regexfilter LIGHT REPL_BACKEND) + +# check that Maxscale is reacting correctly on ctrc+c signal and termination does not take ages +add_test_script(run_ctrl_c.sh run_ctrl_c.sh replication LABELS maxscale LIGHT REPL_BACKEND) + +# run a set of queries in the loop (see setmix.sql) using Perl client +add_test_script(run_session_hang.sh run_session_hang.sh replication LABELS readwritesplit REPL_BACKEND) + +# Checks changes of COM_SELECT and COM_INSERT after queris to check if RWSplit sends queries to master or to slave depending on if it is write or read only query +add_test_executable(rw_select_insert.cpp rw_select_insert replication LABELS readwritesplit REPL_BACKEND) + +# Checks connections are distributed equaly among backends +add_test_executable(rwsplit_conn_num.cpp rwsplit_conn_num repl_lgc LABELS readwritesplit LIGHT REPL_BACKEND) + +# Check that there is one connection to Master and one connection to one of slaves +add_test_executable(rwsplit_connect.cpp rwsplit_connect replication LABELS readwritesplit LIGHT REPL_BACKEND) + +# Test of the read-only mode for readwritesplit when master fails (blocked) +add_test_executable(rwsplit_readonly.cpp rwsplit_readonly rwsplit_readonly LABELS readwritesplit REPL_BACKEND) + +# Test of the read-only mode for readwritesplit when master fails (blocked), under load +add_test_executable(rwsplit_readonly_stress.cpp rwsplit_readonly_stress rwsplit_readonly LABELS readwritesplit HEAVY REPL_BACKEND) + +# Test readwritesplit multi-statement handling +add_test_executable(rwsplit_multi_stmt.cpp rwsplit_multi_stmt rwsplit_multi_stmt LABELS readwritesplit REPL_BACKEND) + +# Test readwritesplit multi-statement handling +add_test_executable(rwsplit_read_only_trx.cpp rwsplit_read_only_trx rwsplit_read_only_trx LABELS readwritesplit REPL_BACKEND) + +# Test replication-manager with MaxScale +add_test_executable(replication_manager.cpp replication_manager replication_manager LABELS maxscale REPL_BACKEND) +add_test_executable_notest(replication_manager_2nodes.cpp replication_manager_2nodes replication_manager_2nodes LABELS maxscale REPL_BACKEND) +add_test_executable_notest(replication_manager_3nodes.cpp replication_manager_3nodes replication_manager_3nodes LABELS maxscale REPL_BACKEND) + +# Schemarouter duplicate database detection test: create DB on all nodes and then try query againt schema router +add_test_executable(schemarouter_duplicate_db.cpp schemarouter_duplicate_db schemarouter_duplicate_db LABELS schemarouter REPL_BACKEND) + +# Test of external script execution +add_test_executable(script.cpp script script LABELS maxscale REPL_BACKEND) + +# Check if 'weightby' parameter works +add_test_executable(server_weight.cpp server_weight galera.weight LABELS readwritesplit readconnroute LIGHT GALERA_BACKEND) + +# Executes a lot of session commands with "disable_sescmd_history=true" and check that memory consumption is not increasing +add_test_executable(ses_bigmem.cpp ses_bigmem no_ses_cmd_store LABELS readwritesplit REPL_BACKEND) + +# test for 'max_sescmd_history' and 'connection_timeout' parameters +add_test_executable(session_limits.cpp session_limits session_limits LABELS readwritesplit REPL_BACKEND) + +# Test of schema router +add_test_executable(sharding.cpp sharding sharding LABELS schemarouter BREAKS_REPL) + +# MXS-1160: LOAD DATA LOCAL INFILE with schemarouter +add_test_executable(sharding_load_data.cpp sharding_load_data sharding LABELS schemarouter BREAKS_REPL) + +# Do short sessions (open conn, short query, close conn) in the loop +add_test_executable(short_sessions.cpp short_sessions replication LABELS readwritesplit readconnroute REPL_BACKEND) + +# Do short sessions (open conn, short query, close conn) in the loop, client ssl is ON +add_test_script(short_sessions_ssl short_sessions ssl LABELS readwritesplit readconnroute REPL_BACKEND) + +# Regression case for crash if maxadmin 'show monitors' command is issued, but no monitor is not running +add_test_executable(show_monitor_crash.cpp show_monitor_crash show_monitor_crash LABELS maxscale) + +# Check how Maxscale works in case of one slave failure, only one slave is configured +add_test_executable(slave_failover.cpp slave_failover replication.one_slave LABELS readwritesplit REPL_BACKEND) + +# Execute queries of different size, check data is the same when accessing via Maxscale and directly to backend +add_test_executable(sql_queries.cpp sql_queries replication LABELS readwritesplit REPL_BACKEND) + +# Execute queries of different size, check data is the same when accessing via Maxscale and directly to backend, one persistant connection configured +add_test_script(sql_queries_pers1 sql_queries sql_queries_pers1 LABELS maxscale readwritesplit HEAVY REPL_BACKEND) + +# Execute queries of different size, check data is the same when accessing via Maxscale and directly to backend, 10 persistant connections configured +add_test_script(sql_queries_pers10 sql_queries sql_queries_pers10 LABELS maxscale readwritesplit HEAVY REPL_BACKEND) + +# Execute queries of different size, check data is the same when accessing via Maxscale and directly to backend, client ssl is ON +add_test_script(ssl sql_queries ssl LABELS maxscale readwritesplit REPL_BACKEND) + +# Check load balancing, client ssl is ON +add_test_script(ssl_load load_balancing ssl_load LABELS maxscale readwritesplit REPL_BACKEND) + +# Check load balancing, client ssl is ON, Galera backend +add_test_script(ssl_load_galera load_balancing_galera ssl_load_galera LABELS maxscale readwritesplit GALERA_BACKEND) + +# Testing slaves who have lost their master and how MaxScale works with them +add_test_executable(stale_slaves.cpp stale_slaves replication LABELS mysqlmon REPL_BACKEND) + +# Run sysbech test and block one slave during test execution +add_test_executable(sysbench_kill_slave.cpp sysbench_kill_slave replication LABELS UNSTABLE HEAVY REPL_BACKEND) + +# Check temporal tables commands functionality +add_test_executable(temporal_tables.cpp temporal_tables replication LABELS readwritesplit REPL_BACKEND) + +# Test routing hints +add_test_executable(test_hints.cpp test_hints hints2 LABELS hintfilter LIGHT REPL_BACKEND) + +# Binlogrouter tests, these heavily alter the replication so they are run last +add_test_executable(avro.cpp avro avro LABELS avrorouter binlogrouter LIGHT BREAKS_REPL) + +# Test avrorouter file compression +add_test_script(avro_compression avro avro_compression LABELS avrorouter binlogrouter LIGHT BREAKS_REPL) + +# In the binlog router setup stop Master and promote one of the Slaves to be new Master +add_test_executable(binlog_change_master.cpp binlog_change_master setup_binlog_tx_safe LABELS binlogrouter BREAKS_REPL) + +# trying to start binlog setup with incomplete Maxscale.cnf +add_test_executable(binlog_incompl.cpp binlog_incompl binlog_incompl LABELS binlogrouter BREAKS_REPL) + +# configure binlog router setup, execute queries and transactions, check data; install semysync plugin, router options semisync=1,transaction_safety=1 +add_test_executable(binlog_semisync.cpp binlog_semisync setup_binlog_semisync LABELS binlogrouter HEAVY BREAKS_REPL) + +# configure binlog router setup, execute queries and transactions, check data; install semysync plugin, router options semisync=0,transaction_safety=0 +add_test_script(binlog_semisync_txs0_ss0 binlog_semisync setup_binlog_semisync_txs0_ss0 LABELS binlogrouter HEAVY BREAKS_REPL) + +# configure binlog router setup, execute queries and transactions, check data; install semysync plugin, router options semisync=0,transaction_safety=1 +add_test_script(binlog_semisync_txs0_ss1 binlog_semisync setup_binlog_semisync_txs0_ss1 LABELS binlogrouter HEAVY BREAKS_REPL) + +# configure binlog router setup, execute queries and transactions, check data; install semysync plugin, router options semisync=1,transaction_safety=0 +add_test_script(binlog_semisync_txs1_ss0 binlog_semisync setup_binlog_semisync_txs1_ss0 LABELS binlogrouter HEAVY BREAKS_REPL) + +set_tests_properties(binlog_semisync PROPERTIES TIMEOUT 3600) +set_tests_properties(binlog_semisync_txs0_ss0 PROPERTIES TIMEOUT 3600) +set_tests_properties(binlog_semisync_txs0_ss1 PROPERTIES TIMEOUT 3600) +set_tests_properties(binlog_semisync_txs1_ss0 PROPERTIES TIMEOUT 3600) + +# +# The encryption tests don't work as they require the file key management plugin +# +# Binlog encription test (aes_cbr encryption) +#add_test_executable(mxs1073_binlog_enc.cpp mxs1073_binlog_enc binlog_enc_aes_cbc LABELS binlogrouter 10.1 BREAKS_REPL) +# Binlog encription test (aes_ctr encryption) +#add_test_script(mxs1073_binlog_enc_aes_ctr mxs1073_binlog_enc binlog_enc_aes_ctr LABELS binlogrouter 10.1 BREAKS_REPL) + +# Test of CDC protocol (avro listener) +add_test_executable(cdc_client.cpp cdc_client avro LABELS avrorouter binlogrouter BREAKS_REPL) + +# Tries INSERTs with size close to 0x0ffffff * N (with binlog backend) +add_test_executable(different_size_binlog.cpp different_size_binlog setup_binlog LABELS binlogrouter HEAVY BREAKS_REPL) + +# Try to configure binlog router to use wrong password for Master and check 'slave status' on binlog +add_test_executable(mxs781_binlog_wrong_passwrd.cpp mxs781_binlog_wrong_passwrd setup_binlog LABELS binlogrouter BREAKS_REPL) + +# Regression case for crash if long host name is used for binlog router (in 'change master to ...') +add_test_executable(mxs813_long_hostname.cpp mxs813_long_hostname setup_binlog LABELS binlogrouter BREAKS_REPL) + +# configure binlog rouer setup, execute queries and transactions, check data; +add_test_executable(setup_binlog.cpp setup_binlog setup_binlog LABELS binlogrouter BREAKS_REPL) + +# configure binlog rouer setup, execute queries and transactions, check data; install semysync plugin, backends started with --binlog-checksum=CRC32 option +# disabled because it is included into setup_binlog test, separate test was created for debugging +# add_test_executable(setup_binlog_crc_32.cpp setup_binlog_crc_32 setup_binlog LABELS binlogrouter BREAKS_REPL) + +# configure binlog rouer setup, execute queries and transactions, check data; install semysync plugin, backends started with --binlog-checksum=NONE option +# disabled because it is included into setup_binlog test, separate test was created for debugging +# add_test_executable(setup_binlog_crc_none.cpp setup_binlog_crc_none setup_binlog LABELS binlogrouter LIGHT BREAKS_REPL) + + +# Creates KDC and tries authrization via GSSAPI (both client and backend) +# works only with yum-based distributions +# TODO: make it working with zypper and apt, move part of KDC setup to MDBCI +add_test_executable(kerberos_setup.cpp kerberos_setup kerberos LABELS HEAVY gssapi REPL_BACKEND) + + +# enable after fixing MXS-419 +# add_test_executable(mxs419_lots_of_connections.cpp mxs419_lots_of_connections replication LABELS REPL_BACKEND) + +# TODO: Alter the user_cache test +# add_test_executable(user_cache.cpp user_cache user_cache LABELS REPL_BACKEND) + +# https://mariadb.atlassian.net/browse/MXS-576 - it is possible to set negative value for +# 'persistpoolmax' without any warning +#add_test_executable(bad_pers.cpp bad_pers bad_pers LABELS REPL_BACKEND) + +# Test Aurora RDS monitor +add_test_executable(auroramon.cpp auroramon auroramon LABELS HEAVY EXTERNAL_BACKEND) + +# Disabled for the time being +# add_test_executable(gatekeeper.cpp gatekeeper gatekeeper LABELS gatekeeper) + +# not implemented, just template +#add_test_executable(rw_galera_select_insert.cpp rw_galera_select_insert galera LABELS readwritesplit GALERA_BACKEND) + +# a tool to delete RDS Aurora cluster +add_test_executable_notest(delete_rds.cpp delete_rds replication LABELS EXTERN_BACKEND) + +# a tool to create RDS Aurora cluster +add_test_executable_notest(create_rds.cpp create_rds replication LABELS EXTERN_BACKEND) + +# start sysbench ageints RWSplit for infinite execution +add_test_executable_notest(long_sysbench.cpp long_sysbench replication LABELS readwritesplit REPL_BACKEND) + +configure_file(templates.h.in templates.h @ONLY) + +include(CTest) diff --git a/maxscale-system-test/CTestConfig.cmake b/maxscale-system-test/CTestConfig.cmake new file mode 100644 index 000000000..1b96cfda2 --- /dev/null +++ b/maxscale-system-test/CTestConfig.cmake @@ -0,0 +1,15 @@ +## This file should be placed in the root directory of your project. +## Then modify the CMakeLists.txt file in the root directory of your +## project to incorporate the testing dashboard. +## +## # The following are required to submit to the CDash dashboard: +## ENABLE_TESTING() +## INCLUDE(CTest) + +set(CTEST_PROJECT_NAME "MaxScale") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "http") +set(CTEST_DROP_SITE "jenkins.engskysql.com") +set(CTEST_DROP_LOCATION "/CDash/submit.php?project=MaxScale") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/maxscale-system-test/ENV_SETUP.md b/maxscale-system-test/ENV_SETUP.md new file mode 100644 index 000000000..4a03242e9 --- /dev/null +++ b/maxscale-system-test/ENV_SETUP.md @@ -0,0 +1,289 @@ +# Build and test environment setup + +### Full build and test environment setup + +
+# install ruby
+sudo apt-get install ruby
+
+# install all needed libraries
+sudo apt-get install libxslt-dev libxml2-dev libvirt-dev zlib1g-dev
+
+# install vagrant
+# it is also possible to install Vagrant from distribution repository, but in case of problems please use 1.7.2
+wget https://dl.bintray.com/mitchellh/vagrant/vagrant_1.7.2_x86_64.deb
+sudo dpkg -i vagrant_1.7.2_x86_64.deb
+
+# install Vagrant plugins
+vagrant plugin install vagrant-aws vagrant-libvirt vagrant-mutate
+
+# get MDBCI, build scripts, descriptions of MDBCI boxes and keys from GitHub
+git clone https://github.com/OSLL/mdbci.git
+git clone git@github.com:mariadb-corporation/mdbci-repository-config.git
+git clone git@github.com:mariadb-corporation/build-scripts-vagrant.git
+git clone git@github.com:mariadb-corporation/mdbci-boxes
+
+# Copy scripts and boxes to proper places
+mv build-scripts-vagrant build-scripts
+scp -r mdbci-boxes/* mdbci/
+
+# set proper access rights for ssh keys (for ppc64 machines)
+chmod 400 mdbci/KEYS/*
+
+# install all the stuff for test package build
+sudo apt-get install cmake gcc g++ libssl-dev
+sudo apt-get install mariadb-client shellcheck
+
+# install MariaDB development library
+sudo apt-get install libmariadbclient-dev
+# Ubuntu repos can contain the sa,e package with different name 'libmariadb-client-lgpl-dev'
+# but it can not be used to build maxscale-system-test; please use mariadb.org repositories
+# https://downloads.mariadb.org/mariadb/repositories/
+# Do not forget to remove all other MariaDB and MySQL packages!
+
+# install qemu (more info https://en.wikibooks.org/wiki/QEMU/Installing_QEMU)
+sudo apt-get install qemu qemu-kvm libvirt-bin
+
+# install virt-manager (if you prefer UI)
+sudo apt-get install virt-manager
+
+# install docker (if needed) - see https://docs.docker.com/engine/installation/
+
+# if cmake from distribution repository is too old it is recommended to build it from latest sources
+wget https://cmake.org/files/v3.4/cmake-3.4.1.tar.gz # replace 3.4.1 to latest version
+tar xzvf cmake-3.4.1.tar.gz
+cd cmake-3.4.1
+./bootstrap
+make
+sudo make install
+cd
+
+# sysbench 0.5 should be in sysbench_deb7 directory; it can be built from source:
+git clone https://github.com/akopytov/sysbench.git
+cd sysbench
+./autogen.sh
+./configure
+make
+cd ..
+mv sysbench sysbench_deb7
+
+# for OVH servers it is needed to move 'docker' and 'libvirt' working directories to /home
+# (replace 'vagrant' to your home directory name)
+cd /var/lib/
+sudo mv docker /home/vagrant/
+sudo ln -s /home/vagrant/docker docker
+cd libvirt
+sudo mv images /home/vagrant/
+sudo ln -s /home/vagrant/images images
+cd
+
+# (HACK) in case of problem with building sysbench:
+scp -r vagrant@maxscale-jenkins.mariadb.com:/home/vagrant/sysbench_deb7  .
+
+# (HACK) in case of problem with 'dummy' box (problem is caused by MDBCI bug):
+scp -r vagrant@maxscale-jenkins.mariadb.com:/home/vagrant/.vagrant.d/boxes/dummy .vagrant.d/boxes/
+
+# MariaDBManager-GPG* files are needed for Maxscale builds in the home directory
+
+# put AWS keys to aws-config.yml (see https://github.com/OSLL/mdbci/blob/master/aws-config.yml.template)
+
+# add curent user to the group 'libvirtd'
+sudo usermod -a -G user_name libvirtd
+
+# start libvirt default pool
+virsh pool-start default
+
+
+ +### Setup VMs manually + +#### Empty virtual machine + +Following template can be used to create empty VM (for qemu machines): +
+{
+  "cookbook_path" : "../recipes/cookbooks/",
+  "build" :
+  {
+        "hostname" : "default",
+        "box" : "###box###",
+        "product" : {
+                "name" : "packages"
+        }
+  }
+}
+
+ +for AWS machines: +
+{
+  "cookbook_path" : "../recipes/cookbooks/",
+  "aws_config" : "../aws-config.yml",
+  "build" :
+  {
+        "hostname" : "build",
+        "box" : "###box###"
+  }
+}
+
+ +Following boxes are availabe: +* qemu: debian_7.5_libvirt, ubuntu_trusty_libvirt, centos_7.0_libvirt, centos_6.5_libvirt +* AWS: rhel5, rhel6, rhel7, sles11, sles12, fedora20, fedora21, fediora22, ubuntu_wily, ubuntu_vivid, centos7, deb_jessie + +#### Maxscale and backend machines creation + +* Generation of Maxscale repository description +It is necessary to generate descriptions of MariaDB and Maxscale repositories before bringin up Maxscale machine with Vagrant +
+export ci_url="http://my_repository_site.com/repostory/"
+~/mdbci-repository-config/generate_all.sh $repo_dir
+~/mdbci-repository-config/maxscale-ci.sh $target $repo_dir
+
+where +
+$repo_dir - directory where repository descriptions will be created
+$target - directory with MaxScale packages in the repositoy
+
+example: +
+export ci_url="http://max-tst-01.mariadb.com/ci-repository/"
+~/mdbci-repository-config/generate_all.sh repo.d
+~/mdbci-repository-config/maxscale-ci.sh develop repo.d
+
+More information can be found in the [MDBCI documentation](https://github.com/OSLL/mdbci#repod-files) and in the [mdbci-repository-config documentaion](https://github.com/mariadb-corporation/mdbci-repository-config#mdbci-repository-config) + +* Preparing configuration description +Virtual machines should be described in JSON format. Example template can be found in the [build-scripts package](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/test/template.libvirt.json). + +MariaDB machine description example: +
+"node0" :
+  {
+        "hostname" : "node0",
+        "box" : "centos_7.0_libvirt",
+        "product" : {
+                "name": "mariadb",
+                "version": "10.0",
+                "cnf_template" : "server1.cnf",
+                "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+        }
+
+  }
+
+ +"cnf_template" defines .cnf file which will be places into MariaDB machine. [build-scripts package](https://github.com/mariadb-corporation/build-scripts-vagrant/tree/master/test-setup-scripts/cnf) contains examples of .cnf files. + +MariaDB Galera machine description example: +
+"galera0" :
+  {
+        "hostname" : "galera0",
+        "box" : "centos_7.0_libvirt",
+        "product" : {
+                "name": "galera",
+                "version": "10.0",
+                "cnf_template" : "galera_server1.cnf",
+                "cnf_template_path": "~/build-scripts/test-setup-scripts/cnf"
+        }
+  }
+
+ +For Galera machines MDBCI automatically puts following information into .cnf file: + +|field|description| +|------|----| +|###NODE-ADDRESS###|IP address of the node (for AWS - private IP)| +|###NODE-NAME###|Replaces by node name ("node0" in this example)| +|###GALERA-LIB-PATH###|Path to the Galera library file (.so file)| + +Example of Maxscale machine description: +
+"maxscale" :
+  {
+        "hostname" : "maxscale",
+        "box" : "centos_7.0_libvirt",
+        "product" : {
+                "name": "maxscale"
+        }
+
+  }
+
+ +#### Generation configuration and bringing machines up + +After creation machines description JSON two steps are needed. + +1. Generate configuration +
+./mdbci --override --template $template_name.json --repo-dir $repo_dir generate $name
+
+ +where + +|variable|description| +|----|----| +|$template_name|name of machines descripiton JSON file| +|$repo_dir|directory with repositories description generated by mdbci-repository-config (repo.d)| +|$name|name of test configuration; will be used as directory name for Vagrant files| + +2. Bringing machines up +
+./mdbci up $name
+
+ +#### Configuring DB users + +Automatic DB users is not implemented yet, so it have to be done manually. See [setup_repl.sh](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/test-setup-scripts/setup_repl.sh) and [setup_galera.sh](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/test-setup-scripts/galera/setup_galera.sh) for details. + +Any test from 'maxscale-system-test' checks Master/Slave and Galera configurations and restores them if they are broken, but it works only if DB users are created. + +TODO: add it into 'maxscale-system-test' + +### Access VMs + +MDBCI provides a number of commands to get information about running vrtial machines. See [MDBCI documentation](https://github.com/OSLL/mdbci#mdbci-syntax) for details. + +[set_env_vagrant.sh script](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/test/set_env_vagrant.sh) defines environmental variables needed by 'maxscale-system-test'. The same variables can be used to access VMs manually. + +Script have to be executed fro 'mbdci' directory. Do not forget '.': +
+cd ~/mdbci/
+. ../build-scripts/test/set_env_vagrant.sh $name
+
+ +After it virual machines can be accessed via ssh, for example: +
+ssh -i $maxscale_sshkey $maxscale_access_user@$maxscale_IP
+
+ +Another way is to use 'vagrant ssh': +
+cd ~/mdbci/$name/
+vagrant ssh <node_name>
+
+ +MDBCI can give IP address, path to ssh key: +
+./mdbci show network <configuration_name>/<node_name> --silent
+./mdbci show keyfile <configuration_name>/<node_name> --silent
+./mdbci ssh --command 'whoami' <configuration_name>/<node_name> --silent
+
+ +Node name for build machine is 'build' + +Nodes names for typical test setup are node0, ..., node3, galera0, ..., galera3, maxscale + +Example: +
+./mdbci show network centos6_vm01/build --silent
+./mdbci show keyfile centos6_vm01/build --silent
+./mdbci ssh --command 'whoami' centos6_vm01/build --silent
+
+ +### Destroying configuration + +
+cd ~/mdbci/$name
+vagrant destroy -f
+
diff --git a/maxscale-system-test/HOW_TO_WRITE_TEST.md b/maxscale-system-test/HOW_TO_WRITE_TEST.md new file mode 100644 index 000000000..3d82c1c03 --- /dev/null +++ b/maxscale-system-test/HOW_TO_WRITE_TEST.md @@ -0,0 +1,204 @@ +# Creating a test case + +This document describes basic principles of test case creation and provides list of basic usefull functions and properties. +For detailed function and properties description and thier full list please see documetation generated by Doxygen. + +## Test case basics + +For every test case following should be created: +- test executable +- record in the 'templates' file +- Maxscale configuration template (if test requires special Maxscale configuration) +- [CMakeLists.txt](CMakeLists.txt) record: + - add_test_executable( ) + - 'set_tests_properties' if test should be added to the group or bigger timeout should be defined (> default 1800s) + +## 'templates' file + +'templates' file contains information about Maxscale configuration template for every test in plain text format: + +\ \ + +Template itself should be: + +cnf/maxscale.cnf.template.\ + +## Maxscale configuration template + +All templates are in cnf/ directory: + +cnf/maxscale.cnf.template.\ + +Template can contain following varables: + +|Variable|Maeaning| +|--------|--------| +|###threads###| Number of Maxscale treads| +|###node_server_IP_N###|IP of Master/Slave node N| +|###node_server_port_N###|port of Master/Slave node N| +|###galera_server_IP_N###|IP of Galera node N| +|###galera_server_port_N###|port of Galera node N| + +## Test creation principles + +* start from initializing of an object of TestConnections class +* set timeout before every operation which can got stuck, do not forget to disable timeout before long sleep() +* use TestConnections::tprintf function to print test log +* use TestConnections::add_result() to idicate test failure and print explanation message +* execute TestConnections::copy_all_logs at the end of test +* return TestConnections::global_result value +* do not leave any node blocked by firewall + +## Class TestConnections + +This class contains all information about Maxscale node and about all backend nodes as well as a set of functions +to handle Maxscale and backends, interact with Maxscale routers and Maxadmin. +Here is only list of main functions, for all details see Doxygen comments in [testconnections.h](testconnections.h) + +Currently two backend sets are supported (represented by Mariadb_nodes class objects): 'repl' and 'galera' +- contains all info and operations for Master/Slave and Galera setups +(see Doxygen comments in [mariadb_nodes.h](mariadb_nodes.h) ) + +It is assumed that following routers+listers are configured + +|Router|Port| +|------|----| +|RWSplit|4006| +|ReadConn master|4008| +|ReadConn Slave|4009| +|binlog|5306| +|test case -specific|4016| + +### Most important fuctions and variables + +Please check Doxygen comments for details + +#### TestConnections(int argc, char *argv[]); + +* reads all information from environmental variables +* checks backends, if broken - does one attempt to restore +* create maxscale.cnf out of template and copy it to Maxscale node +* create needed directories, set access righs for them, cleanup logs, coredumps +* start Maxscale +* initialize internal structures + +#### Timeout functions + +int set_timeout(int timeout_seconds) +stop_timeout() + +If after set_timeout() a new call of set_timeout() or stop_timeout() is not done the test execution terminated, +logs from Maxscale are copied to host. + +#### Open connection functions +|Function|Short description| +|----|---| +| int connect_maxscale();
int connect_rwsplit();
int connect_readconn_master();
int connect_maxscale_slave();|store MYSQL handler in TestConnections object (only one cnnection can be created by them, second call leads to MYSQL handler leak)| +|MYSQL * open_rwsplit_connection()
MYSQL * open_readconn_master_connection()
MYSQL * open_readconn_slave_connection() |returns MYSQL handler (can be used to create a number of connections to each router)| +| int create_connections(int conn_N) |- open and then close N connections to each router| + +A number of useful wrappers for mysql_real_connect() are not included into TestConnections class, but +they are availve from [mariadb_func.h](mariadb_func.h) + +#### Backend check and setup functions +|Function|Short description| +|----|---| +|start_replication()|Configure nodes from 'repl' object as Master/Slave| +|start_galera()|Configure nodes from 'galera'| +|start_binlog()|Configure nodes from 'repl' in following way: node0 - Master, node1 - slave of node0, all others - slaves of Maxscale binlog router| +|start_mm()|Configure nodes from 'repl' in multimuster setup| + +#### Result reporting functions +|Function|Short description| +|----|---| +|add_result()|failure printing, increase global_result| +|tprint()| printing with timestamp| +|copy_logs()|copy Maxscale log, maxscale.cnf file and core dump from Maxscale machine to current directory| + +#### Different checks functions +|Function|Short description| +|----|---| +|try_query()|try SQL query and print error message in case of failure, increase global_result| +|check_t1_table()|check if t1 present in give DB| +|test_maxscale_connections|check status of connections to RWSplit, ReadConn master, ReadConn slave routers| +|check_maxscale_alive()|Check if RWSplit, ReadConn master, ReadConn slave routers are alive| +|check_log_err()|Check Maxscale log for presence of absence of specific string| +|find_connected_slave|find first slave that have connections from Maxscale| + +#### Maxscale machine control functions +|Function|Short description| +|----|---| +|start_maxscale()|| +|stop_maxscale()|| +|restart_maxscale()|| +|execute_ssh_maxscale()|execute command on Maxscale node via ssh| + +#### Properties +|Name|Short description|Corresponding env variable| +|----|-----|----| +|global_result|0 if there is not single failure during the test| - | +|repl|Mariadb_nodes object for Master/Slave nodes| - | +|galera|Mariadb_nodes object for Galera nodes| - | +|smoke|do short tests if TRUE|smoke| +|maxscale_IP|IP address of Maxscale machine|maxscale_IP| +|maxscale_user|DB user name to access via Maxscale|maxscale_user| +|maxscale_password|password for MaxscaleDB user|maxscale_password| +|maxadmin_password|password for MaxAdmin interface (user name is hard coded 'admin')|maxadmin_password| +|conn_rwsplit|MYSQL handler of connections to RWSplit router| - | +|conn_master|MYSQL handler of connections to ReadConn router in master mode| - | +|conn_slave|MYSQL handler of connections to ReadConn router in master mode| - | + +### Mariadb_nodes class + +#### Master/Slave and Galera setup and check +|Function|Short description| +|----|---| +|start_replication()|Configure nodes from 'repl' object as Master/Slave| +|start_galera()|Configure nodes from 'galera'| +|set_slave()|execute CHANGE MASTER TO agains the node| +|check_replication()|Check if 'repl' nodes are properly configured as Master/Slave| +|check_galera()|Check if 'galera' nodes are are properly configured as Galera cluster| +|change_master|Make another node to be a master| + +#### Connections functions +|Function|Short description| +|----|---| +|connect()|open connections to all nodes, store MYSQL handlers in internal variable, second call leads to handlers leak| +|close_connections()|close connections to all nodes| + +#### Nodes control functions +|Function|Short description| +|----|---| +|block_node()|block MariaDB server on the node by firawall| +|unblock_node()|unblock MariaDB server on the node by firawall| +|unblock_all_nodes()|unblock MariaDB server on all nodes by firawall| +|stop_node()|stop MariaDB server on the node| +|start node()|start MariaDB server on the node| +|restart_node()|stop and restart MariaDB server on the node| +|check_node()|check if MariaDB server on the node is alive| +|check_and_restart_node()|check if MariaDB server on the node is alive and restart it if it is not alive| +|stop_nodes()|stop MariaDB server on all nodes| +|ssh_node()|Execute command on the node via ssh, return error code| +|ssh_node_output()|Same as ssh_nodE(), but return command output| +|flush_hosts()|Execute 'mysqladmin flush-hosts' on all nodes| +|execute_query_all_nodes()|Execute same query on all nodes| + +#### Properties +|Name|Short description|Corresponding env variable| +|----|-----|----| +|N|Number of nodes|node_N
galera_N| +|user_name|DB user name|node_user
galera_user| +|password|password for DB user|node_password
galera_password| +|IP[ ]|IP address of the node|node_XXX
galera_XXX| +|IP_private[ ]|private IP of the node (for AWS nodes)|node_private_XXX
galera_private_XXX| +|port[ ]|MariaDB port for the node|node_port_XXX
galera_port_XXX| +|nodes[ ]|MYSQL handler| - | + +### Maxadmin operations functions +[maxadmin_operations.h](maxadmin_operations.h) contains fuctions to communicate to Maxscale via MaxAdmin interface + +|Function|Short description| +|----|---| +|execute_maxadmin_command()|send MaxAdmin command to Maxscale| +|execute_maxadmin_command_print()|send MaxAdmin command to Maxscale and print reply| +|get_maxadmin_param()|send MaxAdmin command to Maxscale and try to find the value of given parameter in output| diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/select_for_var_set.result.template b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/select_for_var_set.result.template new file mode 100644 index 000000000..477535acd --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/select_for_var_set.result.template @@ -0,0 +1,6 @@ +BEGIN; +SELECT (@@server_id) INTO @a; +SELECT @a; +@a +####server_id#### +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/set_autocommit_disabled.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/set_autocommit_disabled.result new file mode 100644 index 000000000..ddf4d887b --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/set_autocommit_disabled.result @@ -0,0 +1,8 @@ +USE test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; +begin; +insert into t1 values(1); +commit; +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_after_autocommit_disabled.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_after_autocommit_disabled.result new file mode 100644 index 000000000..db781d358 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_after_autocommit_disabled.result @@ -0,0 +1,4 @@ +USE test; +SELECT IF(@@server_id <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1.result new file mode 100644 index 000000000..d3bb23c5b --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1.result @@ -0,0 +1,9 @@ +USE test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; +insert into t1 values(1); +select count(*) from t1; +count(*) +1 +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1b.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1b.result new file mode 100644 index 000000000..3276acad0 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled1b.result @@ -0,0 +1,9 @@ +USE test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=OFF; +insert into t1 values(1); +select count(*) from t1; +count(*) +1 +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled2.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled2.result new file mode 100644 index 000000000..05a3e5f0e --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled2.result @@ -0,0 +1,11 @@ +USE test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; +begin; +insert into t1 values(1); +commit; +select count(*) from t1; +count(*) +1 +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled3.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled3.result new file mode 100644 index 000000000..05a3e5f0e --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_autocommit_disabled3.result @@ -0,0 +1,11 @@ +USE test; +drop table if exists t1; +create table t1 (id integer); +set autocommit=0; +begin; +insert into t1 values(1); +commit; +select count(*) from t1; +count(*) +1 +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit1.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit1.result new file mode 100644 index 000000000..281a2f2ad --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit1.result @@ -0,0 +1,11 @@ +USE test; +DROP DATABASE If EXISTS FOO; +SET autocommit=1; +BEGIN; +CREATE DATABASE FOO; +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP DATABASE FOO; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit2.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit2.result new file mode 100644 index 000000000..0f3187049 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit2.result @@ -0,0 +1,17 @@ +USE test; +DROP TABLE IF EXISTS T1; +DROP EVENT IF EXISTS myevent; +SET autocommit=1; +BEGIN; +CREATE TABLE T1 (id integer); +CREATE EVENT myevent +ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 HOUR +DO +UPDATE t1 SET id = id + 1; +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP TABLE T1; +DROP EVENT myevent; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit3.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit3.result new file mode 100644 index 000000000..7281c8e54 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit3.result @@ -0,0 +1,11 @@ +USE test; +DROP TABLE IF EXISTS T1; +SET autocommit=1; +BEGIN; +CREATE TABLE T1 (id integer); +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP TABLE T1; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit5.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit5.result new file mode 100644 index 000000000..44985cd1f --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit5.result @@ -0,0 +1,14 @@ +USE test; +DROP PROCEDURE IF EXISTS simpleproc; +SET autocommit=1; +BEGIN; +CREATE PROCEDURE simpleproc (OUT param1 INT) +BEGIN +SELECT COUNT(*) INTO param1 FROM t; +END // +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP PROCEDURE simpleproc; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit6.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit6.result new file mode 100644 index 000000000..32c6173dd --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit6.result @@ -0,0 +1,13 @@ +USE test; +DROP FUNCTION IF EXISTS hello; +SET autocommit=1; +BEGIN; +CREATE FUNCTION hello (s CHAR(20)) +RETURNS CHAR(50) DETERMINISTIC +RETURN CONCAT('Hello, ',s,'!'); +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP FUNCTION hello; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit7.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit7.result new file mode 100644 index 000000000..31ce55ebb --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_implicit_commit7.result @@ -0,0 +1,12 @@ +USE test; +DROP TABLE IF EXISTS T1; +CREATE TABLE T1 (id integer); +SET autocommit=1; +BEGIN; +CREATE INDEX foo_t1 on T1 (id); +SELECT (@@server_id) INTO @a; +SELECT IF(@a <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; +result +OK (slave) +DROP TABLE T1; +COMMIT; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_sescmd.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_sescmd.result new file mode 100644 index 000000000..5ac67caa2 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_sescmd.result @@ -0,0 +1,6 @@ +use test; +set autocommit=1; +use mysql; +select count(*) from user where user='skysql'; +count(*) +2 diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2.result new file mode 100644 index 000000000..c347a1ee4 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2.result @@ -0,0 +1,15 @@ +USE test; +DROP TABLE IF EXISTS myCity; +SET autocommit = 0; +START TRANSACTION; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +START TRANSACTION; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; +COUNT(*) +0 +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2b.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2b.result new file mode 100644 index 000000000..f87f01614 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing2b.result @@ -0,0 +1,15 @@ +USE test; +DROP TABLE IF EXISTS myCity; +SET autocommit = Off; +START TRANSACTION; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +START TRANSACTION; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; +COUNT(*) +0 +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4.result new file mode 100644 index 000000000..4a7799fcb --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4.result @@ -0,0 +1,13 @@ +USE test; +DROP TABLE IF EXISTS myCity; +SET autocommit = 0; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; +COUNT(*) +0 +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4b.result b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4b.result new file mode 100644 index 000000000..4349c5c63 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/r/test_transaction_routing4b.result @@ -0,0 +1,13 @@ +USE test; +DROP TABLE IF EXISTS myCity; +SET autocommit = oFf; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; +COUNT(*) +0 +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/sleep-1.inc b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/sleep-1.inc new file mode 100644 index 000000000..6d80a161a --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/sleep-1.inc @@ -0,0 +1,5 @@ +--disable_query_log +--disable_result_log +SELECT SLEEP(5); +--enable_result_log +--enable_query_log diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/set_autocommit_disabled.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/set_autocommit_disabled.test new file mode 100644 index 000000000..61b66a37f --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/set_autocommit_disabled.test @@ -0,0 +1,11 @@ +--source testconf.inc +USE test; +--disable_warnings +drop table if exists t1; +--enable_warnings +create table t1 (id integer); +set autocommit=0; # open transaction +begin; +insert into t1 values(1); # write to master +commit; +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_after_autocommit_disabled.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_after_autocommit_disabled.test new file mode 100644 index 000000000..032c7423b --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_after_autocommit_disabled.test @@ -0,0 +1,3 @@ +--source testconf.inc +USE test; +SELECT IF(@@server_id <> @TMASTER_ID,'OK (slave)','FAIL (master)') AS result; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1.test new file mode 100644 index 000000000..62bcf1238 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1.test @@ -0,0 +1,11 @@ +--source testconf.inc +USE test; +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 (id integer); +set autocommit=0; # open transaction +insert into t1 values(1); # write to master +select count(*) from t1; # read from master +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1b.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1b.test new file mode 100644 index 000000000..9edeacbf0 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled1b.test @@ -0,0 +1,11 @@ +--source testconf.inc +USE test; +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 (id integer); +set autocommit=OFF; # open transaction +insert into t1 values(1); # write to master +select count(*) from t1; # read from master +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled2.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled2.test new file mode 100644 index 000000000..957ca4e84 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled2.test @@ -0,0 +1,13 @@ +--source testconf.inc +USE test; +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 (id integer); +set autocommit=0; # open transaction +begin; +insert into t1 values(1); # write to master +commit; +select count(*) from t1; # read from master since autocommit is disabled +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled3.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled3.test new file mode 100644 index 000000000..957ca4e84 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_autocommit_disabled3.test @@ -0,0 +1,13 @@ +--source testconf.inc +USE test; +--disable_warnings +drop table if exists t1; +--enable_warnings + +create table t1 (id integer); +set autocommit=0; # open transaction +begin; +insert into t1 values(1); # write to master +commit; +select count(*) from t1; # read from master since autocommit is disabled +drop table t1; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_sescmd.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_sescmd.test new file mode 100644 index 000000000..7e75d1bc5 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_sescmd.test @@ -0,0 +1,4 @@ +use test; +set autocommit=1; +use mysql; +select count(*) from user where user='skysql'; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2.test new file mode 100644 index 000000000..f8159e058 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2.test @@ -0,0 +1,16 @@ +--source testconf.inc +USE test; +--disable_warnings +DROP TABLE IF EXISTS myCity; +--enable_warnings +SET autocommit = 0; +START TRANSACTION; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +START TRANSACTION; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; # read transaction's modifications from master +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2b.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2b.test new file mode 100644 index 000000000..e0af75ed8 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing2b.test @@ -0,0 +1,16 @@ +--source testconf.inc +USE test; +--disable_warnings +DROP TABLE IF EXISTS myCity; +--enable_warnings +SET autocommit = Off; +START TRANSACTION; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +START TRANSACTION; +DELETE FROM myCity; +SELECT COUNT(*) FROM myCity; # read transaction's modifications from master +COMMIT; +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4.test new file mode 100644 index 000000000..3ddae06fb --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4.test @@ -0,0 +1,16 @@ +--source testconf.inc +USE test; +--disable_warnings +DROP TABLE IF EXISTS myCity; +--enable_warnings + +SET autocommit = 0; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; # implicit transaction started +SELECT COUNT(*) FROM myCity; # read transaction's modifications from master +COMMIT; + +DROP TABLE myCity; diff --git a/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4b.test b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4b.test new file mode 100644 index 000000000..36804d326 --- /dev/null +++ b/maxscale-system-test/Hartmut_tests/maxscale-mysqltest/t/test_transaction_routing4b.test @@ -0,0 +1,16 @@ +--source testconf.inc +USE test; +--disable_warnings +DROP TABLE IF EXISTS myCity; +--enable_warnings + +SET autocommit = oFf; +CREATE TABLE myCity (a int, b char(20)); +INSERT INTO myCity VALUES (1, 'Milan'); +INSERT INTO myCity VALUES (2, 'London'); +COMMIT; +DELETE FROM myCity; # implicit transaction started +SELECT COUNT(*) FROM myCity; # read transaction's modifications from master +COMMIT; + +DROP TABLE myCity; diff --git a/maxscale-system-test/JENKINS.md b/maxscale-system-test/JENKINS.md new file mode 100644 index 000000000..d43344bff --- /dev/null +++ b/maxscale-system-test/JENKINS.md @@ -0,0 +1,127 @@ +# Jenkins + +## List of Jenkins installations + +| URL | Description | +|----|----| +|[max-tst-01.mariadb.com:8089](http://max-tst-01.mariadb.com:8089)|AWS, qemu; Regular testing for different MariaDB versions, different Linux distributions, Developers testing| +|[maxscale-jenkins.mariadb.com:8089/](http://maxscale-jenkins.mariadb.com:8089/)|AWS, VBox; Regular builds for all distributions, build for Coverity, regular test VBox+CentOS6+MariaDB5.5| +|[maxscale-jenkins.mariadb.com:8090](http://maxscale-jenkins.mariadb.com:8090/)|MDBCI testing and debugging, Jenkins experiments| + +## Basic Jenkins jobs + +### [max-tst-01.mariadb.com:8089](http://max-tst-01.mariadb.com:8089) + +| Job | Description | +|----|----| +|[build_and_test](http://max-tst-01.mariadb.com:8089/view/test/job/build_and_test/)|Build Maxscale and run systems tests| +|[run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/)|Run system tests, Maxscale package should be in the repository| +|[build](http://max-tst-01.mariadb.com:8089/job/build/build)|Build Maxscale, create repository and publish it to [http://max-tst-01.mariadb.com/ci-repository/](http://max-tst-01.mariadb.com/ci-repository/)| +|[run_test_no_env_rebuild](http://max-tst-01.mariadb.com:8089/view/test/job/run_test_no_env_rebuild/)|Run system tests without creating a new set of VMs| +|[create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/)|Create VMs, install build environment to Maxscale machine, build Maxscale on Maxscale machine| +|[destroy](http://max-tst-01.mariadb.com:8089/view/axilary/job/destroy/)|Destroy VMs created by [run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/) or [create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/)| +|[remove_lock](http://max-tst-01.mariadb.com:8089/view/axilary/job/remove_lock/)|Remove Vagrant lock set by [run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/) or [create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/)| + +Every test run should have unique name (parameter 'name'). This name is used as a name of MDBCI configuration. +If parameter 'do_not_destroy' is set to 'yes' virtual machines (VM) are not destroyed after tests execution and can be laters used +for debugging or new test runs (see [run_test_no_env_rebuild](http://max-tst-01.mariadb.com:8089/view/test/job/run_test_no_env_rebuild/)) +VMs can be accessed from vagrant@max-tst-01.mariadb.com machine using 'mdbci ssh' or 'vagrant ssh' as well as direct ssh +access using environmental variables provided by +[set_env_vagrant.sh](https://github.com/mariadb-corporation/maxscale-system-test/blob/master/ENV_SETUP.md#access-vms) +script. + +Parameter 'box' defines type of VM and Linux distribution to be used for tests. + +Test results go to [CDash](http://jenkins.engskysql.com/CDash/index.php?project=MaxScale), logs and core dumps are +stored [here](http://max-tst-01.mariadb.com/LOGS/). + +[create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/) job allows to create a set of VMs +(for backend and Maxscale) and does Maxscale build on the Maxscale VM. After execution this job Maxscale machine +contains Maxscale source and binaries. *NOTE:* to properly configure Maxscale init scripts it is necessary to +use rpm/dpkg tool to install Maxscale package (package can be found in the Maxscale build directory). + +[run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/) and +[create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/) +jobs create Vagrant lock which prevents running two Vagrant instances in parallel (such parallel execution can +cause Vagrant of VM provider failures). In case of job crash or interruption by user Vagrant lock stays in locked state +and prevents any new VM creation. To remove lock job +[remove_lock](http://max-tst-01.mariadb.com:8089/view/axilary/job/remove_lock/) +should be used. + +## Process examples + +### Running regression test against a branch + +Execute [build_and_test](http://max-tst-01.mariadb.com:8089/view/test/job/build_and_test/) + +Recommendations regarding parameters: + +* 'name' - unique name: it can be any text string, but as a good practice rule: 'name' should refer to branch, +Linux distribution, date/time of testing, MariaDB version +* 'box' - most recommended boxes are 'centos_7.0_libvirt' (QEMU box) and 'centos7' (Amazon Web Services box) +* 'source' - which type of source to use. BRANCH for git branch, TAG for a git tag and COMMIT for a commit ID. +* 'value' - name of the branch (if 'source' is BRANCH), name of the GIT tag (if 'source' is TAG) or commint ID (if 'source' is COMMIT) + +### Build MaxScale + +Execute [build](http://max-tst-01.mariadb.com:8089/job/build/build) job. + +Parameter 'target' means a name of repository to put packages: +e.g. if 'target' is 'develop' packages are going to +[http://max-tst-01.mariadb.com/ci-repository/develop/](http://max-tst-01.mariadb.com/ci-repository/develop) + +NOTE: building is executed only for selected distribution ('box' parameter). Be careful with other distributions: if build is not executed for same distribution old version can be in the repository (from some old builds). Later tests have to be executed against the same distribution otherwise they can be run against old version of MaxScale. It is recommended to use unique name for 'target'. + +To debug failed build: +* set 'do_not_destroy_vm' parameter to 'yes' +* after the build: +
+ssh -i vagrant.pem vagrant@max-tst-01.mariadb.com
+cd ~/mdbci/build-<box>-<date><time>
+vagrant ssh
+
+ +For example: +
+ssh -i vagrant.pem vagrant@max-tst-01.mariadb.com
+cd ~/mdbci/build_centos6-20160119-0935
+vagrant ssh
+
+ +### Create set of Master/Slave and Galera nodes and setup build environment for Maxscale on one more node + +Execute [create_env](http://max-tst-01.mariadb.com:8089/view/env/job/create_env/) job. + +Login to Maxscale machine (see [environment documentation](ENV_SETUP.md#access-vms)). +MaxScale source code, binaries and packages can be found in the ~/workspace/ directory. +All build tools are installed. GIT can be used to go trough source code. +It is not recommended to commit anything from virtual machine to GitHub. + +Please use 'rpm' or 'dpkg' to properly install Maxscale package (/etc/init.d/maxscale script will not be +installed without execution of 'rpm' or 'dpkg') + +### Running test agains exiting version of Maxscale + +Execute [run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/) job. + +Be sure Maxscale binary repository is present on the +[http://max-tst-01.mariadb.com/ci-repository/](http://max-tst-01.mariadb.com/ci-repository/) +server. Please check: +* there is a directory with the name equal to 'target' parameter +* there is sub-directory for selected distribution ('box' parameter) + +e.g. if 'target' is 'develop' and distribution is CentOS7 (boxes 'centos7' or 'centos_7.0_libvirt') the directory [http://max-tst-01.mariadb.com/ci-repository/develop/mariadb-maxscale/centos/7/x86_64/](http://max-tst-01.mariadb.com/ci-repository/develop/mariadb-maxscale/centos/7/x86_64/) have to contain Maxscale RPM packages. + +If parameter 'do_not_destroy' set to 'yes' after the test virtual machine will not be destroyed and +can be used for debugging. See [environment documentation](ENV_SETUP.md#access-vms) to get know how to access virtual machines. + +### Maintenance operations + +If test run was executed with parameter 'do_not_destroy' set yo 'yes' please do not forget to execute +[destroy](http://max-tst-01.mariadb.com:8089/view/axilary/job/destroy/) against your 'target' + +This job also have to be executed if test run job crashed or it was interrupted. + +In case of build or test job crash, interruption, Jenkins crash during Vagrant operation it is possible that Vagrant lock +stays in locked state and no other job can progress (job can be started, but it is waiting for Vagrant lock - +'/home/vagrant/vagrant_lock' can be seen in the job log). In this case lock can be removed by [remove_lock](http://max-tst-01.mariadb.com:8089/view/axilary/job/remove_lock/) job. diff --git a/maxscale-system-test/LICENSE b/maxscale-system-test/LICENSE new file mode 100644 index 000000000..d6a93266f --- /dev/null +++ b/maxscale-system-test/LICENSE @@ -0,0 +1,340 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + {signature of Ty Coon}, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. + diff --git a/maxscale-system-test/README b/maxscale-system-test/README new file mode 100644 index 000000000..e69de29bb diff --git a/maxscale-system-test/README.md b/maxscale-system-test/README.md new file mode 100644 index 000000000..1e332b408 --- /dev/null +++ b/maxscale-system-test/README.md @@ -0,0 +1,67 @@ +# maxscale-system-test +System level tests for MaxScale + +## Basics +- every test is separate executable file +- backend for test: + - 1 machine for Maxscale + - >= 4 machines for Master/Slave + - >= 4 machines for Galera cluster +- environmental variables contains all information about backend: IPs, user names, passwords, paths to tools, etc +- backed can be created with help of [MDBCI tool](https://github.com/OSLL/mdbci) +- configuring of Master/Slave and Galera can be done with help of [build scripts package](https://github.com/mariadb-corporation/build-scripts-vagrant) + +## Manuals + +[How to run tests](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/RUN_TEST.md) + +[Hints: How to write a test](HOW_TO_WRITE_TEST.md) + +[Build and test environment setup (if you want to play with MDBCI and Vagrant on your local machine](ENV_SETUP.md) + +[Jenkins instructions](JENKINS.md) + +## Environmental variables +|variable|meaning| +|--------|-------| +|node_N|Number of machines for Master/Slave| +|node_XXX_network|IP address of Master/Slave machine number XXX| +|node_XXX_private_ip|private IP address of Master/Slave machine XXX for AWS machines (for everything else - same as node_XXX| +|node_XXX_port|MariaDB port of Master/Slave machine XXX| +|node_XXX_whoami|user name to access Master/Slave machine XXX via ssh| +|node_XXX_access_sudo|'sudo ' if node_access_user_XXX does not have root rights, empty string if node_access_user_XXX has root rights| +|node_XXX_keyfile|full name of secret key to access Master/Slave machine XXX via ssh| +|node_XXX_start_db_command|bash command to start DB server on Master/Slave machine XXX| +|node_XXX_stop_db_command|bash command to stop DB server on Master/Slave machine XXX| +|node_user|DB user name to access Master/Slave nodes (have to have all priveligies with GRANT option)| +|node_password|password for node_user| +|galera_N|Number of machines for Galera| +|galera_XXX_network|IP address of Galera machine number XXX| +|galera_XXX_private|private IP address of Galera machine XXX for AWS machines (for everything else - same as node_XXX| +|galera_XXX_port|MariaDB port of Galera machine XXX| +|galera_XXX_whoami|user name to access Galera machine XXX via ssh| +|galera_XXX_access|'sudo ' if node_access_user_XXX does not have root rights, empty string if node_access_user_XXX has root rights| +|galera_XXX_keyfile|full name of secret key to access Galera machine XXX via ssh| +|galera_XXX_start_db_command|bash command to start DB server on Galera machine XXX| +|galera_XXX_stop_db_command|bash command to stop DB server on Galera machine XXX| +|galera_user|DB user name to access Galera nodes (have to have all priveligies with GRANT option)| +|galera_password|password for node_user| +|maxscale_cnf|full name of Maxscale configuration file (maxscale.cnf)| +|maxscale_log_dir|directory for Maxscale log files| +|maxscale_IP|IP address of Maxscale machine| +|maxscale_sshkey|full name of secret key to access Maxscale machine via ssh| +|maxscale_access_user|user name to access Maxscale machine via ssh| +|maxscale_access_sudo|'sudo ' if maxscale_access_user does not have root rights, empty string if maxscale_access_user has root rights| +|maxscale_user|DB user to access via Maxscale| +|maxscale_password|password for maxscale_user| +|maxscale_hostname|hostname of Maxscale machine| +|sysbench_dir|directory where Sysbanch is installed| +|ssl|'yes' if tests should try to use ssl to connect to Maxscale and to backends (obsolete, now should be 'yes' in all cases)| +|smoke|if 'yes' all tests are executed in 'quick' mode (less iterations, skip heavy operations)| +|backend_ssl|if 'yes' ssl config will be added to all servers definition in maxscale.cnf| +|use_snapshots|if TRUE every test is trying to revert snapshot before running the test| +|take_snapshot_command|revert_snapshot_command| +|revert_snapshot_command|Command line to revert a snapshot of all VMs| +|no_nodes_check|if yes backend checks are not executed (needed in case of RDS or similar backend)| +|no_backend_log_copy|if yes logs from backend nodes are not copied (needed in case of RDS or similar backend)| +|no_maxscale_start|Do not start Maxscale automatically| diff --git a/maxscale-system-test/TEST_RUSULTS.md b/maxscale-system-test/TEST_RUSULTS.md new file mode 100644 index 000000000..b8750f54c --- /dev/null +++ b/maxscale-system-test/TEST_RUSULTS.md @@ -0,0 +1,32 @@ +# Results locations + +| Location | Description | +|----------|-------------| +|[run_test](http://max-tst-01.mariadb.com:8089/view/test/job/run_test/) Jenkins job log|Vagrant and test application outputs| +|[CDash](jenkins.engskysql.com/CDash/index.php?project=MaxScale)|CTest reports| +|[http://max-tst-01.mariadb.com/LOGS/](http://max-tst-01.mariadb.com/LOGS/)|MaxScale logs and core dumps| +|/home/vagrant/LOGS|Same as [http://max-tst-01.mariadb.com/LOGS/](http://max-tst-01.mariadb.com/LOGS/)| +|Maxscale VM /var/log/maxscale|MaxScale log from latest test case| +|Maxscale VM /tpm/core*|Core dump from latest test case| +|Maxscale VM home directory|QLA filter files (if enabled in MaxScale test configuration| +|nodeN, galeraN VMs|MariaDB/MySQL logs (see MariaDB/MySQL documentation for details)| + +For access to VMs see [environment documentation](ENV_SETUP.md#access-vms) + +Jenkins job log consists of following parts: +* Vagrant output: VMs creation priocess, MariaDB Master/Slave and MariaDB Galera stuff installation, MaxScale installation +* [set_env_vagrant.sh](https://github.com/mariadb-corporation/build-scripts-vagrant/blob/master/test/set_env_vagrant.sh) output: retrieval of all VM parameters +* setup scripts output: MariaDB initialisation on backend nodes, DB users setup, enabling core dump on MaxScale VM +* test application output for all tests: eevry line starts from test case number and ':' (can be grepped) +* CTest final printing: N of M tests passed, CTest warnings, email sending logs + +To check presence of core dumps: +
+find /home/vagrant/LOGS/<last_test_results_dir> | grep core
+
+ +where 'last_test_results_dir' - automatically generated name of logs directory (based on date and time of test run) + +To understand test case output please see test case description in Doxygen comments in every test case source file. + +VMs are alive after the test run only if test run is done with 'do_not_destroy' parameter. diff --git a/maxscale-system-test/astylerc b/maxscale-system-test/astylerc new file mode 100644 index 000000000..90562db93 --- /dev/null +++ b/maxscale-system-test/astylerc @@ -0,0 +1,14 @@ +--style=allman +--indent=spaces=4 +--indent-switches +--indent-labels +--min-conditional-indent=0 +--pad-oper +--pad-header +--add-brackets +--convert-tabs +--max-code-length=110 +--break-after-logical +--mode=c +--suffix=none +--max-instatement-indent=110 diff --git a/maxscale-system-test/auroramon.cpp b/maxscale-system-test/auroramon.cpp new file mode 100644 index 000000000..39140bbc0 --- /dev/null +++ b/maxscale-system-test/auroramon.cpp @@ -0,0 +1,157 @@ +/** + * @file auroramon.cpp test of Aurora RDS monitor + * - create RDS cluster + * - find 'writer' node and uses 'maxadmin' to check that this node is "Master, Running" + * - do forced failover + * - find 'writer' again and repeat check + * - destroy RDS cluster + */ + + +#include "testconnections.h" +#include "execute_cmd.h" +#include "rds_vpc.h" + +int set_endspoints(RDS * cluster) +{ + + json_t *endpoint; + long long int port; + const char * IP; + char p[64]; + size_t i; + char cmd[1024]; + + json_t * endpoints = cluster->get_endpoints(); + if (endpoints == NULL) + { + return -1; + } + + json_array_foreach(endpoints, i, endpoint) + { + port = json_integer_value(json_object_get(endpoint, "Port")); + IP = json_string_value(json_object_get(endpoint, "Address")); + printf("host: %s \t port: %lld\n", IP, port); + sprintf(cmd, "node_%03d_network", (int) i); + setenv(cmd, IP, 1); + sprintf(cmd, "node_%03d_port", (int) i); + sprintf(p, "%lld", port); + setenv(cmd, p, 1); + } + + setenv("node_password", "skysqlrds", 1); + setenv("maxscale_user", "skysql", 1); + setenv("maxscale_password", "skysqlrds", 1); + setenv("no_nodes_check", "yes", 1); + setenv("no_backend_log_copy", "yes", 1); + return 0; +} + + +void compare_masters(TestConnections* Test, RDS * cluster) +{ + const char * aurora_master; + cluster->get_writer(&aurora_master); + Test->tprintf("Aurora writer node: %s\n", aurora_master); + char maxadmin_status[1024]; + int i; + char cmd[1024]; + for (i = 0; i < Test->repl->N; i++) + { + sprintf(cmd, "show server server%d", i + 1); + Test->get_maxadmin_param(cmd, (char *) "Status:", &maxadmin_status[0]); + Test->tprintf("Server%d status %s\n", i + 1, maxadmin_status); + sprintf(cmd, "node%03d", i); + if (strcmp(aurora_master, cmd) == 0) + { + if (strcmp(maxadmin_status, "Master, Running")) + { + Test->tprintf("Maxadmin reports node%03d is a Master as expected", i); + } + else + { + Test->add_result(1, "Server node%03d status is not 'Master, Running'', it is '%s'", i, maxadmin_status); + } + } + else + { + if (strcmp(maxadmin_status, "Slave, Running")) + { + Test->tprintf("Maxadmin reports node%03d is a Slave as expected", i); + } + else + { + Test->add_result(1, "Server node%03d status is not 'Slave, Running'', it is '%s'", i, maxadmin_status); + } + } + + } +} + +int main(int argc, char *argv[]) +{ + RDS * cluster = new RDS((char *) "auroratest"); + + if (cluster->create_rds_db(4) != 0) + { + printf("Error RDS creation\n"); + return 1; + } + cluster->wait_for_nodes(4); + + + if (set_endspoints(cluster) != 0) + { + printf("Error getting RDS endpoints\n"); + return 1; + } + + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + compare_masters(Test, cluster); + + Test->set_timeout(30); + Test->tprintf("Executing a query through readwritesplit before failover"); + Test->connect_rwsplit(); + Test->try_query(Test->conn_rwsplit, "show processlist"); + char server_id[1024]; + Test->tprintf("Get aurora_server_id\n"); + find_field(Test->conn_rwsplit, "select @@aurora_server_id;", "server_id", &server_id[0]); + Test->close_rwsplit(); + Test->tprintf("server_id before failover: %s\n", server_id); + + Test->stop_timeout(); + Test->tprintf("Performing cluster failover\n"); + + Test->add_result(cluster->do_failover(), "Failover failed\n"); + + Test->tprintf("Failover done\n"); + + // Do the failover here and wait until it is over + //sleep(10); + + Test->set_timeout(30); + Test->tprintf("Executing a query through readwritesplit after failover"); + Test->connect_rwsplit(); + Test->try_query(Test->conn_rwsplit, "show processlist"); + Test->tprintf("Get aurora_server_id\n"); + find_field(Test->conn_rwsplit, "select @@aurora_server_id;", "server_id", &server_id[0]); + Test->close_rwsplit(); + Test->tprintf("server_id after failover: %s\n", server_id); + + compare_masters(Test, cluster); + + + //Test->check_maxscale_alive(); + + + Test->stop_timeout(); + cluster->delete_rds_cluster(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/avro.cpp b/maxscale-system-test/avro.cpp new file mode 100644 index 000000000..7dbd365b2 --- /dev/null +++ b/maxscale-system-test/avro.cpp @@ -0,0 +1,107 @@ +/** + * @file avro.cpp test of avro + * - setup binlog and avro + * - put some data to t1 + * - check avro file with "maxavrocheck -vv /var/lib/maxscale/avro/test.t1.000001.avro" + * - check that data in avro file is correct + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" +#include +#include "maxinfo_func.h" + +#include +#include + +using std::cout; +using std::endl; + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(600); + Test->stop_maxscale(); + Test->ssh_maxscale(true, (char *) "rm -rf /var/lib/maxscale/avro"); + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], "DROP TABLE IF EXISTS t1"); + Test->repl->close_connections(); + sleep(5); + + + Test->start_binlog(); + + Test->set_timeout(120); + + Test->stop_maxscale(); + + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/avro"); + + Test->set_timeout(120); + + Test->start_maxscale(); + + Test->set_timeout(60); + + Test->repl->connect(); + create_t1(Test->repl->nodes[0]); + insert_into_t1(Test->repl->nodes[0], 3); + execute_query(Test->repl->nodes[0], "FLUSH LOGS"); + + Test->repl->close_connections(); + + Test->set_timeout(120); + + sleep(10); + + char * avro_check = Test->ssh_maxscale_output(true, + "maxavrocheck -vv /var/lib/maxscale/avro/test.t1.000001.avro | grep \"{\""); + char * output = Test->ssh_maxscale_output(true, "maxavrocheck -d /var/lib/maxscale/avro/test.t1.000001.avro"); + + std::istringstream iss; + iss.str(output); + int x1_exp = 0; + int fl_exp = 0; + int x = 16; + + for (std::string line; std::getline(iss, line);) + { + long long int x1, fl; + Test->set_timeout(20); + get_x_fl_from_json((char*)line.c_str(), &x1, &fl); + + if (x1 != x1_exp || fl != fl_exp) + { + Test->add_result(1, "Output:x1 %lld, fl %lld, Expected: x1 %d, fl %d", + x1, fl, x1_exp, fl_exp); + break; + } + + if ((++x1_exp) >= x) + { + x1_exp = 0; + x = x * 16; + fl_exp++; + Test->tprintf("fl = %d", fl_exp); + } + } + + if (fl_exp != 3) + { + Test->add_result(1, "not enough lines in avrocheck output\n"); + } + + Test->set_timeout(120); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/avro_long.cpp b/maxscale-system-test/avro_long.cpp new file mode 100644 index 000000000..3221c9ceb --- /dev/null +++ b/maxscale-system-test/avro_long.cpp @@ -0,0 +1,60 @@ +/** + * @file avro_long.cpp test of avro + * - setup binlog and avro + * - put some data to t1 in the loop + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(600); + Test->stop_maxscale(); + Test->ssh_maxscale(true, (char *) "rm -rf /var/lib/maxscale/avro"); + + //Test->ssh_maxscale(true, (char *) "mkdir /var/lib/maxscale/avro; chown -R maxscale:maxscale /var/lib/maxscale/avro"); + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;"); + Test->repl->close_connections(); + sleep(5); + + + Test->start_binlog(); + + Test->set_timeout(120); + + Test->stop_maxscale(); + + Test->ssh_maxscale(true, (char *) "rm -rf /var/lib/maxscale/avro"); + + Test->set_timeout(120); + + Test->start_maxscale(); + + Test->set_timeout(60); + + Test->repl->connect(); + create_t1(Test->repl->nodes[0]); + + for (int i = 0; i < 1000000; i++) + { + Test->set_timeout(60); + insert_into_t1(Test->repl->nodes[0], 3); + Test->tprintf("i=%d\n", i); + } + + Test->repl->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/backend_auth_fail.cpp b/maxscale-system-test/backend_auth_fail.cpp new file mode 100644 index 000000000..adc0ed3b1 --- /dev/null +++ b/maxscale-system-test/backend_auth_fail.cpp @@ -0,0 +1,40 @@ +/** + * @backend_auth_fail.cpp Repeatedly connect to maxscale while the backends reject all connections + * + * MaxScale should not crash + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + MYSQL *mysql[1000]; + TestConnections * Test = new TestConnections(argc, argv); + Test->stop_timeout(); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 10;"); + + for (int x = 0; x < 3; x++) + { + Test->tprintf("Creating 100 connections...\n"); + for (int i = 0; i < 100; i++) + { + Test->set_timeout(30); + mysql[i] = Test->open_readconn_master_connection(); + execute_query_silent(mysql[i], "select 1"); + } + Test->stop_timeout(); + + for (int i = 0; i < 100; i++) + { + Test->set_timeout(30); + mysql_close(mysql[i]); + } + } + + Test->stop_timeout(); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; + +} diff --git a/maxscale-system-test/bad_pers.cpp b/maxscale-system-test/bad_pers.cpp new file mode 100644 index 000000000..993e7bee1 --- /dev/null +++ b/maxscale-system-test/bad_pers.cpp @@ -0,0 +1,27 @@ +/** + * @file bad_pres.cpp check that Maxscale prints warning if persistpoolmax=-1 for all backends (bug MXS-576) + * + * - Maxscale.cnf contains persistpoolmax=-1 for all servers + * - check log warning about it + */ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->connect_maxscale(); + Test->check_log_err((char *) "warning -1", true); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/big_load.cpp b/maxscale-system-test/big_load.cpp new file mode 100644 index 000000000..8c07daff2 --- /dev/null +++ b/maxscale-system-test/big_load.cpp @@ -0,0 +1,228 @@ +#include "big_load.h" +#include + +void load(long int *new_inserts, long int *new_selects, long int *selects, long int *inserts, int threads_num, + TestConnections * Test, long int *i1, long int *i2, int rwsplit_only, bool galera, bool report_errors) +{ + char sql[1000000]; + thread_data data; + Mariadb_nodes * nodes; + if (galera) + { + nodes = Test->galera; + } + else + { + nodes = Test->repl; + } + + int sql_l = 20000; + int run_time = 100; + if (Test->smoke) + { + sql_l = 500; + run_time = 10; + } + + nodes->connect(); + Test->connect_rwsplit(); + + data.i1 = 0; + data.i2 = 0; + data.exit_flag = 0; + data.Test = Test; + data.rwsplit_only = rwsplit_only; + // connect to the MaxScale server (rwsplit) + + if (Test->conn_rwsplit == NULL ) + { + if (report_errors) + { + Test->add_result(1, "Can't connect to MaxScale\n"); + } + //Test->copy_all_logs(); + exit(1); + } + else + { + create_t1(Test->conn_rwsplit); + create_insert_string(sql, sql_l, 1); + + if ((execute_query(Test->conn_rwsplit, sql) != 0) && (report_errors)) + { + Test->add_result(1, "Query %s failed\n", sql); + } + // close connections + Test->close_rwsplit(); + + Test->tprintf("Waiting for the table to replicate\n"); + Test->repl->sync_slaves(); + + pthread_t thread1[threads_num]; + pthread_t thread2[threads_num]; + int iret1[threads_num]; + int iret2[threads_num]; + + Test->tprintf("COM_INSERT and COM_SELECT before executing test\n"); + + Test->add_result(get_global_status_allnodes(&selects[0], &inserts[0], nodes, 0), + "get_global_status_allnodes failed\n"); + + data.exit_flag = 0; + /* Create independent threads each of them will execute function */ + for (int i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, query_thread1, &data); + iret2[i] = pthread_create(&thread2[i], NULL, query_thread2, &data); + } + Test->tprintf("Threads are running %d seconds \n", run_time); + sleep(run_time); + data.exit_flag = 1; + Test->tprintf("Waiting for all threads to exit\n"); + Test->set_timeout(100); + for (int i = 0; i < threads_num; i++) + { + pthread_join(thread1[i], NULL); + pthread_join(thread2[i], NULL); + } + sleep(1); + + Test->tprintf("COM_INSERT and COM_SELECT after executing test\n"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], nodes, 0); + print_delta(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], nodes->N); + Test->tprintf("First group of threads did %d queries, second - %d \n", data.i1, data.i2); + } + nodes->close_connections(); + *i1 = data.i1; + *i2 = data.i2; +} + +void *query_thread1( void *ptr ) +{ + MYSQL * conn1; + MYSQL * conn2; + MYSQL * conn3; + int conn_err = 0; + thread_data * data = (thread_data *) ptr; + conn1 = open_conn_db_timeout(data->Test->rwsplit_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + //conn1 = data->Test->open_rwsplit_connection(); + if (mysql_errno(conn1) != 0) + { + conn_err++; + } + if (data->rwsplit_only == 0) + { + //conn2 = data->Test->open_readconn_master_connection(); + conn2 = open_conn_db_timeout(data->Test->readconn_master_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + if (mysql_errno(conn2) != 0) + { + conn_err++; + } + //conn3 = data->Test->open_readconn_slave_connection(); + conn3 = open_conn_db_timeout(data->Test->readconn_slave_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + if (mysql_errno(conn3) != 0) + { + conn_err++; + } + } + if (conn_err == 0) + { + while (data->exit_flag == 0) + { + if (execute_query_silent(conn1, (char *) "SELECT * FROM t1;") == 0) + { + __sync_fetch_and_add(&data->i1, 1); + } + + if (data->rwsplit_only == 0) + { + execute_query_silent(conn2, (char *) "SELECT * FROM t1;"); + execute_query_silent(conn3, (char *) "SELECT * FROM t1;"); + } + + } + mysql_close(conn1); + if (data->rwsplit_only == 0) + { + mysql_close(conn2); + mysql_close(conn3); + } + } + return NULL; +} + +void *query_thread2(void *ptr ) +{ + MYSQL * conn1; + MYSQL * conn2; + MYSQL * conn3; + thread_data * data = (thread_data *) ptr; + //conn1 = data->Test->open_rwsplit_connection(); + conn1 = open_conn_db_timeout(data->Test->rwsplit_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + if (data->rwsplit_only == 0) + { + //conn2 = data->Test->open_readconn_master_connection(); + //conn3 = data->Test->open_readconn_slave_connection(); + + conn2 = open_conn_db_timeout(data->Test->readconn_master_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + //if (mysql_errno(conn2) != 0) { conn_err++; } + conn3 = open_conn_db_timeout(data->Test->readconn_slave_port, + data->Test->maxscale_IP, + (char *) "test", + data->Test->maxscale_user, + data->Test->maxscale_password, + 20, + data->Test->ssl); + //if (mysql_errno(conn3) != 0) { conn_err++; } + } + while (data->exit_flag == 0) + { + sleep(1); + if (execute_query_silent(conn1, (char *) "SELECT * FROM t1;") == 0) + { + __sync_fetch_and_add(&data->i2, 1); + } + if (data->rwsplit_only == 0) + { + execute_query_silent(conn2, (char *) "SELECT * FROM t1;"); + execute_query_silent(conn3, (char *) "SELECT * FROM t1;"); + } + } + mysql_close(conn1); + if (data->rwsplit_only == 0) + { + mysql_close(conn2); + mysql_close(conn3); + } + return NULL; +} diff --git a/maxscale-system-test/big_load.h b/maxscale-system-test/big_load.h new file mode 100644 index 000000000..f4e94bdb0 --- /dev/null +++ b/maxscale-system-test/big_load.h @@ -0,0 +1,39 @@ +#ifndef BIG_LOAD_H +#define BIG_LOAD_H + + +#include "testconnections.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +//pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +typedef struct +{ + int exit_flag; + long i1; + long i2; + int rwsplit_only; + TestConnections * Test; +} thread_data; + +void *query_thread1(void *ptr ); +void *query_thread2(void *ptr ); + +/** + * @brief load Creates load on Maxscale routers + * @param new_inserts COM_INSERT variable values array for all nodes after test + * @param new_selects COM_SELECT variable values array for all nodes after test + * @param selects COM_SELECT variable values array for all nodes before test + * @param inserts COM_INSERT variable values array for all nodes before test + * @param threads_num Number of load threads + * @param Test TestConnections object + * @param i1 Number of queries executed by "fast" threads (no wating between queries) + * @param i2 Number of queries executed by "slow" threads (sleep 1 second between queries) + * @param rwsplit_only if 1 create load only on RWSplit router, do not load ReadConn router + * @param galera if true use Galera backend (Test->galera instead of Test->repl) + * @param report_errors if true call add_result() in case of query failure + */ +void load(long *new_inserts, long *new_selects, long *selects, long *inserts, int threads_num, + TestConnections *Test, long *i1, long *i2, int rwsplit_only, bool galera, bool report_errors); + +#endif // BIG_LOAD_H diff --git a/maxscale-system-test/big_transaction.cpp b/maxscale-system-test/big_transaction.cpp new file mode 100644 index 000000000..2539aedb0 --- /dev/null +++ b/maxscale-system-test/big_transaction.cpp @@ -0,0 +1,23 @@ +#include "big_transaction.h" + +int big_transaction(MYSQL * conn, int N) +{ + int local_result = 0; + char sql[1000000]; + local_result += create_t1(conn); + local_result += execute_query(conn, (char *) "START TRANSACTION"); + local_result += execute_query(conn, (char *) "SET autocommit = 0"); + + for (int i = 0; i < N; i++) + { + create_insert_string(sql, 10000, i); + local_result += execute_query(conn, sql); + local_result += execute_query(conn, "CREATE TABLE t2(id int);"); + local_result += execute_query(conn, sql); + local_result += execute_query(conn, "DROP TABLE t2;"); + local_result += execute_query(conn, sql); + } + + local_result += execute_query(conn, (char *) "COMMIT"); + return local_result; +} diff --git a/maxscale-system-test/big_transaction.h b/maxscale-system-test/big_transaction.h new file mode 100644 index 000000000..d5be98e01 --- /dev/null +++ b/maxscale-system-test/big_transaction.h @@ -0,0 +1,17 @@ +#ifndef BIG_TRANSACTION_H +#define BIG_TRANSACTION_H + +#include +#include +#include +#include "sql_t1.h" + +/** + * @brief big_transaction Executes big transaction (includes N INSERTs of 10000 rows) + * @param conn MYSQL connection handler + * @param N Number of INSERTs + * @return 0 if success + */ +int big_transaction(MYSQL * conn, int N); + +#endif // BIG_TRANSACTION_H diff --git a/maxscale-system-test/binlog_big_transaction.cpp b/maxscale-system-test/binlog_big_transaction.cpp new file mode 100644 index 000000000..bb48a390d --- /dev/null +++ b/maxscale-system-test/binlog_big_transaction.cpp @@ -0,0 +1,72 @@ +/** + * @file binlog_big_transaction.cpp test of simple binlog router setup and execute a number of big transactions + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "test_binlog_fnc.h" +#include "big_transaction.h" + +void *disconnect_thread( void *ptr ); +TestConnections * Test ; +int exit_flag; +int main(int argc, char *argv[]) +{ + + Test = new TestConnections(argc, argv); + Test->set_timeout(3000); + Test->set_log_copy_interval(300); + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;"); + Test->repl->close_connections(); + sleep(5); + + Test->start_binlog(); + + pthread_t threads; + int iret; + exit_flag = 0; + iret = pthread_create( &threads, NULL, disconnect_thread, NULL); + + Test->repl->connect(); + for (int i = 0; i < 100000; i++) + { + Test->set_timeout(3000); + Test->tprintf("Trying transactions: %d\n", i); + Test->add_result(big_transaction(Test->repl->nodes[0], 7), "Transaction %d failed!\n", i); + } + Test->repl->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *disconnect_thread( void *ptr ) +{ + MYSQL * conn; + char cmd[256]; + int i; + conn = open_conn(Test->binlog_port, Test->maxscale_IP, Test->repl->user_name, Test->repl->password, + Test->repl->ssl); + Test->add_result(mysql_errno(conn), "Error connecting to Binlog router, error: %s\n", mysql_error(conn)); + i = 3; + while (exit_flag == 0) + { + sprintf(cmd, "DISCONNECT SERVER %d", i); + execute_query(conn, cmd); + i++; + if (i > Test->repl->N) + { + i = 3; + sleep(30); + execute_query(conn, (char *) "DISCONNECT SERVER ALL"); + } + sleep(5); + } + return NULL; +} diff --git a/maxscale-system-test/binlog_change_master.cpp b/maxscale-system-test/binlog_change_master.cpp new file mode 100644 index 000000000..ef59392a7 --- /dev/null +++ b/maxscale-system-test/binlog_change_master.cpp @@ -0,0 +1,322 @@ +/** + * @file binlog_change_master.cpp In the binlog router setup stop Master and promote one of the Slaves to be new Master + * - setup binlog + * - start thread wich executes transactions + * - block master + * - transaction thread tries to elect a new master a continue with new master + * - continue transaction with new master + * - stop transactions + * - wait + * - chack data on all nodes + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "test_binlog_fnc.h" +#include "big_transaction.h" + +void *disconnect_thread( void *ptr ); +void *transaction_thread( void *ptr ); + +TestConnections * Test ; +int exit_flag; +int master = 0; +int i_trans = 0; +int failed_transaction_num = 0; + +/** The amount of rows each transaction inserts */ +const int N_INSERTS = 100; + +int transaction(MYSQL * conn, int N) +{ + int local_result = 0; + char sql[1000000]; + + Test->tprintf("START TRANSACTION\n"); + local_result += execute_query(conn, (char *) "START TRANSACTION"); + if (local_result != 0) + { + Test->tprintf("START TRANSACTION Failed\n"); + return local_result; + } + Test->tprintf("SET autocommit = 0\n"); + local_result += execute_query(conn, (char *) "SET autocommit = 0"); + if (local_result != 0) + { + Test->tprintf("SET Failed\n"); + return local_result; + } + + create_insert_string(sql, N_INSERTS, N); + Test->tprintf("INSERT\n"); + local_result += execute_query(conn, sql); + if (local_result != 0) + { + Test->tprintf("Insert Failed\n"); + return local_result; + } + + Test->tprintf("COMMIT\n"); + local_result += execute_query(conn, (char *) "COMMIT"); + if (local_result != 0) + { + Test->tprintf("Commit Failed\n"); + return local_result; + } + return local_result; +} + + +int main(int argc, char *argv[]) +{ + int j; + + Test = new TestConnections(argc, argv); + Test->set_timeout(3000); + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;"); + Test->repl->close_connections(); + sleep(5); + + Test->repl->connect(); + Test->repl->execute_query_all_nodes((char *) "STOP SLAVE"); + Test->repl->execute_query_all_nodes((char *) "RESET SLAVE ALL"); + Test->repl->execute_query_all_nodes((char *) "RESET MASTER"); + + Test->tprintf("Starting binlog configuration\n"); + Test->start_binlog(); + + pthread_t disconnec_thread_t; + int disconnect_iret; + pthread_t transaction_thread_t; + int transaction_iret; + + exit_flag = 0; + Test->tprintf("Starting query thread\n"); + + transaction_iret = pthread_create(&transaction_thread_t, NULL, transaction_thread, NULL); + + Test->tprintf("Sleeping\n"); + Test->stop_timeout(); + + Test->repl->connect(); + int flushes = Test->smoke ? 2 : 5; + for (j = 0; j < flushes; j++) + { + Test->tprintf("Flush logs on master\n"); + execute_query(Test->repl->nodes[0], (char *) "flush logs"); + sleep(15); + } + + sleep(15); + + Test->tprintf("Blocking master\n"); + Test->repl->block_node(0); + Test->stop_timeout(); + + sleep(30); + + Test->tprintf("Done! Waiting for thread\n"); + exit_flag = 1; + pthread_join(transaction_thread_t, NULL ); + Test->tprintf("Done!\n"); + + Test->tprintf("Checking data on the node3 (slave)\n"); + char sql[256]; + char rep[256]; + int rep_d; + + Test->tprintf("Sleeping to let replication happen\n"); + sleep(30); + + Test->repl->connect(); + + for (int i_n = 3; i_n < Test->repl->N; i_n++) + { + for (j = 0; j < i_trans; j++) + { + sprintf(sql, "select count(*) from t1 where fl=%d;", j); + find_field(Test->repl->nodes[i_n], sql, (char *) "count(*)", rep); + Test->tprintf("Transaction %d put %s rows\n", j, rep); + sscanf(rep, "%d", &rep_d); + if ((rep_d != N_INSERTS) && (j != (failed_transaction_num - 1))) + { + Test->add_result(1, "Transaction %d did not put data into slave\n", j); + } + if ((j == (failed_transaction_num - 1)) && (rep_d != 0) && (rep_d != N_INSERTS)) + { + Test->add_result(1, "Incomplete transaction detected - %d\n", j); + } + if ((j == (failed_transaction_num - 1) && rep_d == 0)) + { + Test->tprintf("Transaction %d was rejected, OK\n", j); + } + } + } + Test->repl->close_connections(); + + + int rval = Test->global_result; + delete Test; + return rval; +} + + +const char * setup_slave1 = + "change master to MASTER_HOST='%s',\ + MASTER_USER='repl',\ + MASTER_PASSWORD='repl',\ + MASTER_LOG_FILE='%s',\ + MASTER_LOG_POS=%s,\ + MASTER_PORT=%d"; + + +int select_new_master(TestConnections * test) +{ + char log_file[256]; + char log_file_new[256]; + char log_pos[256]; + + char maxscale_log_file[256]; + char maxscale_log_file_new[256]; + char maxscale_log_pos[256]; + + // Stopping slave + test->tprintf("Connection to backend\n"); + test->repl->connect(); + test->tprintf("'stop slave' to node2\n"); + test->try_query(Test->repl->nodes[2], (char *) "stop slave;"); + test->tprintf("'reset slave all' to node2\n"); + test->try_query(Test->repl->nodes[2], (char *) "RESET slave all;"); + //execute_query(Test->repl->nodes[2], (char *) "reset master;"); + + // Get master status + test->tprintf("show master status\n"); + find_field(test->repl->nodes[2], (char *) "show master status", (char *) "File", &log_file[0]); + find_field(test->repl->nodes[2], (char *) "show master status", (char *) "Position", &log_pos[0]); + test->tprintf("Real master file: %s\n", log_file); + test->tprintf("Real master pos : %s\n", log_pos); + + test->tprintf("Connecting to MaxScale binlog router (with any DB)\n"); + MYSQL * binlog = open_conn_no_db(test->binlog_port, test->maxscale_IP, test->repl->user_name, + test->repl->password, test->ssl); + test->add_result(mysql_errno(binlog), "Error connection to binlog router %s\n", mysql_error(binlog)); + + test->tprintf("show master status on maxscale\n"); + find_field(binlog, (char *) "show master status", (char *) "File", &maxscale_log_file[0]); + find_field(binlog, (char *) "show master status", (char *) "Position", &maxscale_log_pos[0]); + + if (!maxscale_log_file[0] || !maxscale_log_pos[0]) + { + test->add_result(1, "Failed to query for master status"); + return 1; + } + + test->tprintf("Real master file: %s\n", maxscale_log_file); + test->tprintf("Real master pos : %s\n", maxscale_log_pos); + + char * p = strchr(maxscale_log_file, '.') + 1; + test->tprintf("log file num %s\n", p); + int pd; + sscanf(p, "%d", &pd); + test->tprintf("log file num (d) %d\n", pd); + p[0] = '\0'; + test->tprintf("log file name %s\n", maxscale_log_file); + sprintf(maxscale_log_file_new, "%s%06d", maxscale_log_file, pd + 1); + + test->try_query(test->repl->nodes[2], (char *) "reset master"); + test->tprintf("Flush logs %d times\n", pd + 1); + for (int k = 0; k < pd + 1; k++) + { + test->try_query(test->repl->nodes[2], (char *) "flush logs"); + } + + // Set Maxscale to new master + test->try_query(binlog, "stop slave"); + test->tprintf("configuring Maxscale binlog router\n"); + + + test->tprintf("reconnect to binlog\n"); + mysql_close(binlog); + binlog = open_conn_no_db(test->binlog_port, test->maxscale_IP, test->repl->user_name, test->repl->password, + test->ssl); + test->add_result(mysql_errno(binlog), "Error connection to binlog router %s\n", mysql_error(binlog)); + + char str[1024]; + //sprintf(str, setup_slave1, test->repl->IP[2], log_file_new, test->repl->port[2]); + sprintf(str, setup_slave1, test->repl->IP[2], maxscale_log_file_new, "4", test->repl->port[2]); + test->tprintf("change master query: %s\n", str); + test->try_query(binlog, str); + + test->try_query(binlog, "start slave"); + + test->repl->close_connections(); + +} + +void *disconnect_thread( void *ptr ) +{ + MYSQL * conn; + char cmd[256]; + int i; + conn = open_conn(Test->binlog_port, Test->maxscale_IP, Test->repl->user_name, Test->repl->password, + Test->repl->ssl); + Test->add_result(mysql_errno(conn), "Error connecting to Binlog router, error: %s\n", mysql_error(conn)); + i = 3; + while (exit_flag == 0) + { + sprintf(cmd, "DISCONNECT SERVER %d", i); + execute_query(conn, cmd); + i++; + if (i > Test->repl->N) + { + i = 3; + sleep(30); + execute_query(conn, (char *) "DISCONNECT SERVER ALL"); + } + sleep(5); + } + return NULL; +} + + +void *transaction_thread( void *ptr ) +{ + MYSQL * conn; + int trans_result = 0; + + conn = open_conn_db_timeout(Test->repl->port[master], Test->repl->IP[master], (char *) "test", + Test->repl->user_name, Test->repl->password, 20, Test->repl->ssl); + Test->add_result(mysql_errno(conn), "Error connecting to Binlog router, error: %s\n", mysql_error(conn)); + create_t1(conn); + + while ((exit_flag == 0)) + { + Test->tprintf("Transaction %d\n", i_trans); + trans_result = transaction(conn, i_trans); + if (trans_result != 0) + { + Test->tprintf("Transaction %d failed, doing master failover\n", i_trans); + failed_transaction_num = i_trans; + Test->tprintf("Closing connection\n"); + mysql_close(conn); + Test->tprintf("Calling select_new_master()\n"); + select_new_master(Test); + master = 2; + + conn = open_conn_db_timeout(Test->repl->port[master], Test->repl->IP[master], (char *) "test", + Test->repl->user_name, Test->repl->password, 20, Test->repl->ssl); + Test->add_result(mysql_errno(conn), "Error connecting to Binlog router, error: %s\n", mysql_error(conn)); + Test->tprintf("Retrying transaction %d\n", i_trans); + i_trans--; + } + i_trans++; + } + i_trans--; + + return NULL; +} diff --git a/maxscale-system-test/binlog_enc_aes_cbc.cnf b/maxscale-system-test/binlog_enc_aes_cbc.cnf new file mode 100644 index 000000000..02fb6e35d --- /dev/null +++ b/maxscale-system-test/binlog_enc_aes_cbc.cnf @@ -0,0 +1,12 @@ +[mysqld] +plugin-load-add=file_key_management.so +file_key_management_encryption_algorithm=aes_cbc +file_key_management_filename = /etc/mariadb_binlog_keys.txt +encrypt-binlog=1 + +# Enable checksum +binlog_checksum=CRC32 + +#Enable large packets handling +max_allowed_packet=1042M +innodb_log_file_size=142M diff --git a/maxscale-system-test/binlog_enc_aes_ctr.cnf b/maxscale-system-test/binlog_enc_aes_ctr.cnf new file mode 100644 index 000000000..42f082d8c --- /dev/null +++ b/maxscale-system-test/binlog_enc_aes_ctr.cnf @@ -0,0 +1,12 @@ +[mysqld] +plugin-load-add=file_key_management.so +file_key_management_encryption_algorithm=aes_ctr +file_key_management_filename = /etc/mariadb_binlog_keys.txt +encrypt-binlog=1 + +# Enable checksum +binlog_checksum=CRC32 + +#Enable large packets handling +max_allowed_packet=1042M +innodb_log_file_size=142M diff --git a/maxscale-system-test/binlog_failover.cpp b/maxscale-system-test/binlog_failover.cpp new file mode 100644 index 000000000..15b4139bb --- /dev/null +++ b/maxscale-system-test/binlog_failover.cpp @@ -0,0 +1,16 @@ +/** + * @file binlog_failover.cpp binlog_failover Test of failover scenarion for binlog router + * + * - setup following configuration: + * - one master + * - two maxscale binlog machines + * - two slaves connected to each maxscale binlog mashine + * - put some date via master + * - block master + * - stop all Maxscale machines with STOP SLAVE command + * - check which Maxscale machine contains most recent data (let's call this machine 'most_recent_maxscale') + * - use CHANGE MASETER on the second Maxscale machine to point it to the Maxscale machine from the previous step ('most_recent_maxscale') + * - wait until second Maxscale is in sync with 'most_recent_maxscale' (use SHOW MASTER STATUS) + * - select new master (HOW??) + * - set all Maxscale machines to be a slaves of new master + */ diff --git a/maxscale-system-test/binlog_incompl.cpp b/maxscale-system-test/binlog_incompl.cpp new file mode 100644 index 000000000..d0c9021e2 --- /dev/null +++ b/maxscale-system-test/binlog_incompl.cpp @@ -0,0 +1,26 @@ +/** + * @file setup_incompl trying to start binlog setup with incomplete Maxscale.cnf + * check for crash + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(60); + Test->connect_maxscale(); + Test->close_maxscale_connections(); + sleep(10); + Test->check_log_err("fatal signal 11", false); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/binlog_semisync.cpp b/maxscale-system-test/binlog_semisync.cpp new file mode 100644 index 000000000..143d9da11 --- /dev/null +++ b/maxscale-system-test/binlog_semisync.cpp @@ -0,0 +1,91 @@ +/** + * @file binlog_semisync.cpp Same test as setup_binlog, but with semisync enabled + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" + + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->tprintf("Test object initialized\n"); + Test->set_timeout(3000); + int options_set = 3; + if (Test->smoke) + { + options_set = 1; + } + Test->tprintf("Trying to connect to backend\n"); + if (Test->repl->connect() == 0) + { + Test->tprintf("DROP TABLE t1\n"); + execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;"); + + //Test->tprintf("SET GLOBAL rpl_semi_sync_master_enabled = 1;\n"); + //execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL rpl_semi_sync_master_enabled = 1;"); + Test->repl->close_connections(); + sleep(5); + + + for (int option = 0; option < options_set; option++) + { + Test->binlog_cmd_option = option; + Test->start_binlog(); + Test->repl->connect(); + Test->tprintf("install semisync plugin\n"); + execute_query(Test->repl->nodes[0], + (char *) "INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';"); + //sleep(10); + Test->tprintf("Reconnect\n"); + Test->repl->close_connections(); + Test->repl->connect(); + Test->tprintf("SET GLOBAL rpl_semi_sync_master_enabled = 1;\n"); + execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL rpl_semi_sync_master_enabled = 1;"); + //sleep(10); + Test->repl->close_connections(); + test_binlog(Test); + + Test->repl->connect(); + Test->tprintf("SET GLOBAL rpl_semi_sync_master_enabled = 0;\n"); + execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL rpl_semi_sync_master_enabled = 0;"); + //sleep(10); + Test->repl->close_connections(); + test_binlog(Test); + + Test->repl->connect(); + Test->tprintf("uninstall semisync plugin\n"); + execute_query(Test->repl->nodes[0], (char *) "UNINSTALL PLUGIN rpl_semi_sync_master;"); + Test->tprintf("Reconnect\n"); + Test->repl->close_connections(); + Test->repl->connect(); + Test->tprintf("SET GLOBAL rpl_semi_sync_master_enabled = 1;\n"); + execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL rpl_semi_sync_master_enabled = 1;"); + //sleep(10); + Test->repl->close_connections(); + test_binlog(Test); + + Test->repl->connect(); + Test->tprintf("SET GLOBAL rpl_semi_sync_master_enabled = 0;\n"); + execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL rpl_semi_sync_master_enabled = 0;"); + sleep(10); + Test->repl->close_connections(); + test_binlog(Test); + } + } + else + { + Test->add_result(1, "Can't connect to backend\n"); + } + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/blob_test.cpp b/maxscale-system-test/blob_test.cpp new file mode 100644 index 000000000..41358bafb --- /dev/null +++ b/maxscale-system-test/blob_test.cpp @@ -0,0 +1,193 @@ +#include "blob_test.h" + +int test_longblob(TestConnections* Test, MYSQL * conn, char * blob_name, unsigned long chunk_size, int chunks, + int rows) +{ + unsigned long size = chunk_size; + unsigned long * data; + unsigned long i, j; + MYSQL_BIND param[1]; + char sql[256]; + int global_res = Test->global_result; + //Test->tprintf("chunk size %lu chunks %d inserts %d\n", chunk_size, chunks, rows); + + char *insert_stmt = (char *) "INSERT INTO long_blob_table(x, b) VALUES(1, ?)"; + + Test->tprintf("Creating table with %s\n", blob_name); + Test->try_query(conn, (char *) "DROP TABLE IF EXISTS long_blob_table"); + sprintf(sql, "CREATE TABLE long_blob_table(id int NOT NULL AUTO_INCREMENT, x INT, b %s, PRIMARY KEY (id))", + blob_name); + Test->try_query(conn, sql); + + for (int k = 0; k < rows; k++) + { + Test->tprintf("Preparintg INSERT stmt\n"); + MYSQL_STMT * stmt = mysql_stmt_init(conn); + if (stmt == NULL) + { + Test->add_result(1, "stmt init error: %s\n", mysql_error(conn)); + } + + Test->add_result(mysql_stmt_prepare(stmt, insert_stmt, strlen(insert_stmt)), "Error preparing stmt: %s\n", + mysql_stmt_error(stmt)); + + param[0].buffer_type = MYSQL_TYPE_STRING; + param[0].is_null = 0; + + Test->tprintf("Binding parameter\n"); + Test->add_result(mysql_stmt_bind_param(stmt, param), "Error parameter binding: %s\n", mysql_stmt_error(stmt)); + + Test->tprintf("Filling buffer\n"); + data = (unsigned long *) malloc(size * sizeof(long int)); + + if (data == NULL) + { + Test->add_result(1, "Memory allocation error\n"); + } + + + + Test->tprintf("Sending data in %d bytes chunks, total size is %d\n", size * sizeof(unsigned long), + (size * sizeof(unsigned long)) * chunks); + for (i = 0; i < chunks; i++) + { + for (j = 0; j < size; j++) + { + data[j] = j + i * size; + } + Test->set_timeout(300); + Test->tprintf("Chunk #%d\n", i); + if (mysql_stmt_send_long_data(stmt, 0, (char *) data, size * sizeof(unsigned long)) != 0) + { + Test->add_result(1, "Error inserting data, iteration %d, error %s\n", i, mysql_stmt_error(stmt)); + return 1; + } + } + + //for (int k = 0; k < rows; k++) + //{ + Test->tprintf("Executing statement: %02d\n", k); + Test->set_timeout(3000); + Test->add_result(mysql_stmt_execute(stmt), "INSERT Statement with %s failed, error is %s\n", blob_name, + mysql_stmt_error(stmt)); + //} + Test->add_result(mysql_stmt_close(stmt), "Error closing stmt\n"); + } + + if (global_res == Test->global_result) + { + Test->tprintf("%s is OK\n", blob_name); + } + else + { + Test->tprintf("%s FAILED\n", blob_name); + } + + return 0; +} + +int check_longblob_data(TestConnections* Test, MYSQL * conn, unsigned long chunk_size, int chunks, + int rows) +{ + //char *select_stmt = (char *) "SELECT id, x, b FROM long_blob_table WHERE id = ?"; + char *select_stmt = (char *) "SELECT id, x, b FROM long_blob_table "; + MYSQL_STMT * stmt = mysql_stmt_init(Test->conn_rwsplit); + if (stmt == NULL) + { + Test->add_result(1, "stmt init error: %s\n", mysql_error(Test->conn_rwsplit)); + } + + Test->add_result(mysql_stmt_prepare(stmt, select_stmt, strlen(select_stmt)), "Error preparing stmt: %s\n", + mysql_stmt_error(stmt)); + + MYSQL_BIND param[1], result[3]; + int id = 1; + + memset(param, 0, sizeof(param)); + memset(result, 0, sizeof(result)); + + param[0].buffer_type = MYSQL_TYPE_LONG; + param[0].buffer = &id; + + unsigned long * data = (unsigned long *) malloc(chunk_size * chunks * sizeof(long int)); + + + int r_id; + int r_x; + unsigned long l_id; + unsigned long l_x; + my_bool b_id; + my_bool b_x; + my_bool e_id; + my_bool e_x; + + result[0].buffer_type = MYSQL_TYPE_LONG; + result[0].buffer = &r_id; + result[0].buffer_length = 0; + result[0].length = &l_id; + result[0].is_null = &b_id; + result[0].error = &e_id; + + result[1].buffer_type = MYSQL_TYPE_LONG; + result[1].buffer = &r_x; + result[1].buffer_length = 0; + result[1].length = &l_x; + result[1].is_null = &b_x; + result[1].error = &e_x; + + result[2].buffer_type = MYSQL_TYPE_LONG_BLOB; + result[2].buffer = data; + result[2].buffer_length = chunk_size * chunks * sizeof(long int); + + /* + if (mysql_stmt_bind_param(stmt, param) != 0) + { + printf("Could not bind parameters\n"); + return 1; + } + */ + if (mysql_stmt_bind_result(stmt, result) != 0) + { + printf("Could not bind results\n"); + return 1; + } + + if (mysql_stmt_execute(stmt) != 0) + { + Test->tprintf("Error executing stmt %s\n", mysql_error(Test->conn_rwsplit)); + } + + if (mysql_stmt_store_result(stmt) != 0) + { + printf("Could not buffer result set\n"); + return 1; + } + + int row = 0; + while (!mysql_stmt_fetch(stmt)) + { + Test->tprintf("id=%d\tx=%d\n", r_id, r_x); + if (r_id != row + 1) + { + Test->add_result(1, "id field is wrong! Expected %d, but it is %d\n", row + 1, r_id); + } + for (int y = 0; y < chunk_size * chunks; y++) + { + if (data[y] != y) + { + Test->add_result(1, "Data is wrong!\n"); + } + //printf("y = %d \t%lu\tid=%d\tx=%d\n", y, data[y], r_id, r_x); + } + row++; + } + if (row != rows) + { + Test->add_result(1, "Wrong number of rows in the table! Expected %d, but it is %d\n", rows, row); + } + mysql_stmt_free_result(stmt); + + mysql_stmt_close(stmt); +} + + diff --git a/maxscale-system-test/blob_test.h b/maxscale-system-test/blob_test.h new file mode 100644 index 000000000..a7f88b888 --- /dev/null +++ b/maxscale-system-test/blob_test.h @@ -0,0 +1,32 @@ +#ifndef BLOB_TEST_H +#define BLOB_TEST_H + +#include "testconnections.h" + +/** + * @brief test_longblob INSERT big amount of data into lobg_blob_table + * @param Test TestConnection object + * @param conn MYSQL connection handler + * @param blob_name blob type (LONGBLOB; MEDIUMBLOB or BLOB) + * @param chunk_size size of one data chunk (in sizeof(long usingned)) + * @param chunks number of chunks to INSERT + * @param rows number of rows to INSERT (executes INSERT stetament 'rows' times) + * @return 0 in case of success + */ +int test_longblob(TestConnections* Test, MYSQL * conn, char * blob_name, unsigned long chunk_size, int chunks, + int rows); + + +/** + * @brief check_longblob_data Does SELECT against table created by test_longblob() and cheks that data are correct + * @param Test TestConnection object + * @param conn MYSQL connection handler + * @param chunk_size size of one data chunk (in sizeof(long usingned)) + * @param chunks number of chunks in the table + * @param rows number of rows in the table + * @return 0 in case of success + */ +int check_longblob_data(TestConnections* Test, MYSQL * conn, unsigned long chunk_size, int chunks, + int rows); + +#endif // BLOB_TEST_H diff --git a/maxscale-system-test/bug143.cpp b/maxscale-system-test/bug143.cpp new file mode 100644 index 000000000..16df08866 --- /dev/null +++ b/maxscale-system-test/bug143.cpp @@ -0,0 +1,68 @@ +/** + * @file bug143.cpp bug143 regression case (MaxScale ignores host in user authentication) + * + * - create user@'non_existing_host1', user@'%', user@'non_existing_host2' identified by different passwords. + * - try to connect using RWSplit. First and third are expected to fail, second should succeed. + */ + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->tprintf("Creating user 'user' with 3 different passwords for different hosts\n"); + Test->connect_maxscale(); + execute_query(Test->conn_rwsplit, "CREATE USER 'user'@'non_existing_host1' IDENTIFIED BY 'pass1'"); + execute_query(Test->conn_rwsplit, "CREATE USER 'user'@'%%' IDENTIFIED BY 'pass2'"); + execute_query(Test->conn_rwsplit, "CREATE USER 'user'@'non_existing_host2' IDENTIFIED BY 'pass3'"); + execute_query(Test->conn_rwsplit, "GRANT ALL PRIVILEGES ON *.* TO 'user'@'non_existing_host1'"); + execute_query(Test->conn_rwsplit, "GRANT ALL PRIVILEGES ON *.* TO 'user'@'%%'"); + execute_query(Test->conn_rwsplit, "GRANT ALL PRIVILEGES ON *.* TO 'user'@'non_existing_host2'"); + + Test->tprintf("Synchronizing slaves"); + Test->set_timeout(50); + Test->repl->sync_slaves(); + + Test->tprintf("Trying first hostname, expecting failure"); + Test->set_timeout(15); + MYSQL * conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "user", (char *) "pass1", Test->ssl); + if (mysql_errno(conn) == 0) + { + Test->add_result(1, "MaxScale ignores host in authentication\n"); + } + if (conn != NULL) + { + mysql_close(conn); + } + + Test->tprintf("Trying second hostname, expecting success"); + Test->set_timeout(15); + conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "user", (char *) "pass2", Test->ssl); + Test->add_result(mysql_errno(conn), "MaxScale can't connect: %s\n", mysql_error(conn)); + if (conn != NULL) + { + mysql_close(conn); + } + + Test->tprintf("Trying third hostname, expecting failure"); + Test->set_timeout(15); + conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "user", (char *) "pass3", Test->ssl); + if (mysql_errno(conn) == 0) + { + Test->add_result(1, "MaxScale ignores host in authentication\n"); + } + if (conn != NULL) + { + mysql_close(conn); + } + + execute_query(Test->conn_rwsplit, "DROP USER 'user'@'non_existing_host1'"); + execute_query(Test->conn_rwsplit, "DROP USER 'user'@'%%'"); + execute_query(Test->conn_rwsplit, "DROP USER 'user'@'non_existing_host2'"); + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug359.cpp b/maxscale-system-test/bug359.cpp new file mode 100644 index 000000000..28c6a16a2 --- /dev/null +++ b/maxscale-system-test/bug359.cpp @@ -0,0 +1,51 @@ +/** + * @file bug359.cpp bug359 regression case (router_options in readwritesplit causes errors in error log) + * + * - Maxscale.cnf contains RWSplit router definition with router_option=slave. + * - error is expected in the log. Maxscale should not start. + */ + + +/* +Massimiliano 2013-11-22 09:45:13 UTC +Setting router_options=slave in readwritesplit causes: + +in the error log + +2013 11/22 10:35:43 Error : Couldn't find suitable Master from 3 candidates. +2013 11/22 10:35:43 Error : Failed to create router client session. Freeing allocated resources. + + +If no options are allowed here, it could be better to log this and/or unload the module + + +This is something could happen doing copy paste from readconnrouter as an example +Comment 1 Mark Riddoch 2014-02-05 11:35:57 UTC +I makes no sense for the read/write splitter to look at the slave and master router options. + +Vilho Raatikka 2014-05-22 07:02:50 UTC +Added check for router option 'synced' which accepts only that, and warns the user of other unsupported options ('master'|'slave' for example). If router option is specified for read write split router, only a node in 'joined' state will be accepted as eligible backend candidate. + +*/ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->check_log_err((char *) "Unsupported router option \"slave\"", true); + Test->check_log_err((char *) "Failed to start all MaxScale services. Exiting", true); + Test->check_log_err((char *) "Couldn't find suitable Master", false); + //Test->check_maxscale_alive(); + Test->check_maxscale_processes(0); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug422.cpp b/maxscale-system-test/bug422.cpp new file mode 100644 index 000000000..612568a50 --- /dev/null +++ b/maxscale-system-test/bug422.cpp @@ -0,0 +1,122 @@ +/** + * @file bug422.cpp bug422 regression case ( Executing '\s' doesn't always produce complete result set) + * + * Test executes "show status" 1000 times against all Maxscale services and checks Maxscale is alive after it. + */ + +/* +Vilho Raatikka 2014-04-15 07:03:03 UTC +Read/write split router +------------------------- + +Login to MaxScale & read/write split router, for example + +mysql --host=127.0.0.1 -P 4006 -u maxuser -pmaxpwd + +Complete result : + +MySQL [(none)]> \s +-------------- +mysql Ver 15.1 Distrib 5.5.33-MariaDB, for Linux (x86_64) using readline 5.1 + +Connection id: 4051 +Current database: +Current user: maxuser@localhost +SSL: Not in use +Current pager: less +Using outfile: '' +Using delimiter: ; +Server: MySQL +Server version: MaxScale 0.5.0 Source distribution +Protocol version: 10 +Connection: 127.0.0.1 via TCP/IP +Server characterset: latin1 +Db characterset: latin1 +Client characterset: latin1 +Conn. characterset: latin1 +TCP port: 4006 +Uptime: 34 min 23 sec + +Threads: 5 Questions: 206 Slow queries: 0 Opens: 0 Flush tables: 2 Open tables: 26 Queries per second avg: 0.099 +-------------- + + +By running same a few time in a row, an incomplete result set arrives, like the following: + +MySQL [(none)]> \s +-------------- +mysql Ver 15.1 Distrib 5.5.33-MariaDB, for Linux (x86_64) using readline 5.1 + +Connection id: 4051 +Current database: +Current user: maxuser@localhost +SSL: Not in use +Current pager: less +Using outfile: '' +Using delimiter: ; +Server: MySQL +Server version: MaxScale 0.5.0 Source distribution +Protocol version: 10 +Connection: 127.0.0.1 via TCP/IP +Server characterset: latin1 +Db characterset: latin1 +Client characterset: latin1 +Conn. characterset: latin1 +TCP port: 4006 +-------------- + +MySQL [(none)]> + +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int i; + int iterations = 1000; + if (Test->smoke) + { + iterations = 100; + } + + Test->set_timeout(10); + + Test->tprintf("Connecting to all MaxScale services\n"); + Test->add_result(Test->connect_maxscale(), "Can not connect to Maxscale\n"); + + Test->tprintf("executing show status %d times\n", iterations); + + + for (i = 0; i < iterations; i++) + { + Test->set_timeout(5); + Test->add_result(execute_query(Test->conn_rwsplit, (char *) "show status"), + "Query %d agains RWSplit failed\n", i); + } + for (i = 0; i < iterations; i++) + { + Test->set_timeout(5); + Test->add_result(execute_query(Test->conn_slave, (char *) "show status"), + "Query %d agains ReadConn Slave failed\n", i); + } + for (i = 0; i < iterations; i++) + { + Test->set_timeout(5); + Test->add_result(execute_query(Test->conn_master, (char *) "show status"), + "Query %d agains ReadConn Master failed\n", i); + } + Test->set_timeout(10); + + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug448.cpp b/maxscale-system-test/bug448.cpp new file mode 100644 index 000000000..72860e23b --- /dev/null +++ b/maxscale-system-test/bug448.cpp @@ -0,0 +1,71 @@ +/** + * @file bug448.cpp bug448 regression case ("Wildcard in host column of mysql.user table don't work properly") + * + * Test creates user1@xxx.%.%.% and tries to use it to connect + */ + +#include +#include "testconnections.h" +#include "get_my_ip.h" + +int main(int argc, char *argv[]) +{ + char my_ip[1024]; + char my_ip_db[1024]; + char sql[1024]; + char * first_dot; + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(20); + Test->repl->connect(); + + get_my_ip(Test->maxscale_IP, my_ip); + Test->tprintf("Test machine IP (got via network request) %s\n", my_ip); + + Test->add_result(Test->get_client_ip(my_ip_db), "Unable to get IP using connection to DB\n"); + + Test->tprintf("Test machine IP (got via Show processlist) %s\n", my_ip); + + first_dot = strstr(my_ip, "."); + strcpy(first_dot, ".%.%.%"); + + Test->tprintf("Test machine IP with %% %s\n", my_ip); + + Test->tprintf("Connecting to Maxscale\n"); + Test->add_result(Test->connect_maxscale(), "Error connecting to Maxscale\n"); + Test->tprintf("Creating user 'user1' for %s host\n", my_ip); + Test->set_timeout(30); + + Test->add_result(execute_query(Test->conn_rwsplit, "CREATE USER user1@'%s';", my_ip), + "Failed to create user"); + Test->add_result(execute_query(Test->conn_rwsplit, + "GRANT ALL PRIVILEGES ON *.* TO user1@'%s' identified by 'pass1'; FLUSH PRIVILEGES;", my_ip), + "Failed to grant privileges."); + + Test->tprintf("Trying to open connection using user1\n"); + + MYSQL * conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "user1", (char *) "pass1", + Test->ssl); + if (mysql_errno(conn) != 0) + { + Test->add_result(1, "TEST_FAILED! Authentification failed! error: %s\n", mysql_error(conn)); + } + else + { + Test->tprintf("Authentification for user@'%s' is ok", my_ip); + if (conn != NULL) + { + mysql_close(conn); + } + } + + Test->add_result(execute_query(Test->conn_rwsplit, "DROP USER user1@'%s'; FLUSH PRIVILEGES;", my_ip), + "Query Failed\n"); + + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug466.cpp b/maxscale-system-test/bug466.cpp new file mode 100644 index 000000000..fb7d0df78 --- /dev/null +++ b/maxscale-system-test/bug466.cpp @@ -0,0 +1,34 @@ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int global_result = 0; + + Test->ReadEnv(); + Test->PrintIP(); + + Test->ConnectMaxscale(); + + + execute_query(Test->conn_rwsplit, (char *) "select @@server_id; -- max_slave_replication_lag=120"); + + + + + Test->CloseMaxscaleConn(); + + + global_result += CheckMaxscaleAlive(); + + Test->Copy_all_logs(); + return global_result; +} + + diff --git a/maxscale-system-test/bug469.cpp b/maxscale-system-test/bug469.cpp new file mode 100644 index 000000000..642f4ef95 --- /dev/null +++ b/maxscale-system-test/bug469.cpp @@ -0,0 +1,89 @@ +/** + * @file bug469 bug469 regression test case ("rwsplit counts every connection twice in master - counnection counts leak") + * - use maxadmin command "show server server1" and check "Current no. of conns" and "Number of connections" - both should be 0 + * - execute simple query against RWSplit + * - use maxadmin command "show server server1" and check "Current no. of conns" (should be 0) and "Number of connections" (should be 1) + */ + + +/* +Vilho Raatikka 2014-08-05 12:28:21 UTC +Every connection is counted twice in master and decremented only once. As a result master seems always to have active connections after first connection is established. + +Server 0x21706e0 (server1) + Server: 127.0.0.1 + Status: Master, Running + Protocol: MySQLBackend + Port: 3000 + Server Version: 5.5.37-MariaDB-debug-log + Node Id: 3000 + Master Id: -1 + Slave Ids: 3001, 3002 , 3003 + Repl Depth: 0 + Number of connections: 6 + Current no. of conns: 3 + Current no. of operations: 0 +Server 0x21705e0 (server2) + Server: 127.0.0.1 + Status: Slave, Running + Protocol: MySQLBackend + Port: 3001 + Server Version: 5.5.37-MariaDB-debug-log + Node Id: 3001 + Master Id: 3000 + Slave Ids: + Repl Depth: 1 + Number of connections: 3 + Current no. of conns: 0 + Current no. of operations: 0 + +*/ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + char res[1024]; + int res_d; + Test->set_timeout(10); + + Test->get_maxadmin_param((char *) "show server server1", (char *) "Current no. of conns:", res); + sscanf(res, "%d", &res_d); + Test->tprintf("Before: Current num of conn %d\n", res_d); + Test->add_result(res_d, "curr num of conn is not 0\n"); + Test->get_maxadmin_param((char *) "show server server1", (char *) "Number of connections:", res); + sscanf(res, "%d", &res_d); + Test->tprintf("Before: num of conn %d\n", res_d); + Test->add_result(res_d, "num of conn is not 0"); + + Test->connect_rwsplit(); + Test->try_query(Test->conn_rwsplit, (char *) "select 1"); + Test->close_rwsplit(); + + Test->stop_timeout(); + sleep(10); + + Test->set_timeout(10); + + Test->get_maxadmin_param((char *) "show server server1", (char *) "Current no. of conns:", res); + sscanf(res, "%d", &res_d); + Test->tprintf("After: Current num of conn %d\n", res_d); + Test->add_result(res_d, "curr num of conn is not 0\n"); + Test->get_maxadmin_param((char *) "show server server1", (char *) "Number of connections:", res); + sscanf(res, "%d", &res_d); + Test->tprintf("After: num of conn %d\n", res_d); + if (res_d != 1) + { + Test->add_result(1, "num of conn is not 1"); + } + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug471.cpp b/maxscale-system-test/bug471.cpp new file mode 100644 index 000000000..da0185f00 --- /dev/null +++ b/maxscale-system-test/bug471.cpp @@ -0,0 +1,193 @@ +/** + * @file bug471.cpp bug471 regression case ( Routing Hints route to server sometimes doesn't work ) + * + * - try "select @@server_id; -- maxscale route to server server%d" (where %d - server number) and compares result + * with "select @@server_id;" sent directly to backend node. + * - do it 25 times. + */ + +/* +Massimiliano 2014-08-06 13:27:05 UTC +I found using basic routing hints such as: + + select @@server_id; -- maxscale route to server server4 + select @@server_id; -- maxscale route to server server3 + select @@server_id; -- maxscale route to server server2 + +server3 is the current master + +and server4 server_id is 4 +server3 server_id is 3 +server2 server_id is 2 + +sometimes I cannot get the expected results that are: + +4 +3 +2 + + +Sometimes I got: + +2 +3 +2 + +or + +4 +3 +4 + +In maxScale configuration 5 servers defined: + +server1 is not monitored but listed in [RW Split Service] +server5 is always stopped + + + +MaxScale configuration: + +[gateway] +threads=4 + +[RW Split Service] +#Please note server1 is not monitored n MySQL Monitor section +type=service +router=readwritesplit +servers=server1,server2,server3,server5,server4 +max_slave_connections=100% +max_slave_replication_lag=21 +user=massi +passwd=massi +enable_root_user=0 +filters=Hint + +# Definition of the servers +[server1] +#not monitored +type=server +address=127.0.0.1 +port=3306 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3307 +protocol=MySQLBackend + +[server3] +type=server +address=127.0.0.1 +port=3308 +protocol=MySQLBackend + +[server4] +type=server +address=127.0.0.1 +port=3309 +protocol=MySQLBackend + +[server5] +#always stopped +type=server +address=127.0.0.1 +port=3310 +protocol=MySQLBackend + + +[RW Split Listener] +type=listener +service=RW Split Service +protocol=MySQLClient +port=4606 + +[Hint] +type=filter +module=hintfilter + +[MySQL Monitor] +# Please note server1 is not monitored +type=monitor +module=mysqlmon +servers=server4,server2,server3,server5 +user=massi +passwd=massi +detect_replication_lag=1 +monitor_interval=10001 + + + +Removing server1 from the service section gives right results for server_id selection + + + +# mysql -c -h 127.0.0.1 -P 4606 -umassi -pmassi +MariaDB> select @@server_id; -- maxscale route to server server4 + + + +Please not -c option that allows comments to be sent + + +Vilho Raatikka 2014-08-08 08:13:42 UTC +After further consideration we decided that the behavior is invalid. Routing hints should be followed if they don't violate database consistency nor cluster setup. +Comment 11 Vilho Raatikka 2014-08-08 17:26:25 UTC +Pushed fix in commit d4de582e1622908cc95396f57878f8691289c35b to Z2. +Replication lag is not checked if routing hint is used. + +*/ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->repl->connect(); + Test->add_result(Test->connect_maxscale(), "Failed to connect to MaxScale\n"); + + char server_id[256]; + char server_id_d[256]; + + char hint_sql[64]; + + for (int i = 1; i < 25; i++) + { + for (int j = 0; j < Test->repl->N; j++) + { + if (j != 1 ) + { + Test->set_timeout(5); + sprintf(hint_sql, "select @@server_id; -- maxscale route to server server%d", j + 1); + + find_field(Test->conn_rwsplit, hint_sql, (char *) "@@server_id", &server_id[0]); + find_field(Test->repl->nodes[j], (char *) "select @@server_id;", (char *) "@@server_id", &server_id_d[0]); + + Test->tprintf("server%d ID from Maxscale: \t%s\n", j + 1, server_id); + Test->tprintf("server%d ID directly from node: \t%s\n", j + 1, server_id_d); + + Test->add_result(strcmp(server_id, server_id_d), "Hints does not work!\n"); + } + } + } + + Test->set_timeout(10); + + Test->close_maxscale_connections(); + Test->repl->close_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug473.cpp b/maxscale-system-test/bug473.cpp new file mode 100644 index 000000000..33278f0f9 --- /dev/null +++ b/maxscale-system-test/bug473.cpp @@ -0,0 +1,98 @@ +/** + * @file bug473.cpp bug470, 472, 473 regression cases ( malformed hints cause crash ) + * + * Test tries different hints with syntax errors (see source for details) + */ + +/* +Markus Mäkelä 2014-08-07 09:21:44 UTC +All of the following queries cause a segmentation fault: + +select @@server_id; -- maxscale route to server =( +select @@server_id; -- maxscale route to server =) +select @@server_id; -- maxscale route to server =: +select @@server_id; -- maxscale route to server =a +select @@server_id; -- maxscale route to server = a + +Most likely all variatios with the equals sign and a character after it cause the crash. + +Call stack: + +#0 __strncasecmp_l_sse2 () at ../sysdeps/x86_64/strcmp.S:209 +#1 0x00007fffdd0e9d0d in get_route_target (qtype=QUERY_TYPE_READ, trx_active=false, hint=0x74b830) at readwritesplit.c:1116 +#2 0x00007fffdd0ea494 in routeQuery (instance=0x72f960, router_session=0x73dbf0, querybuf=0x74b7a0) at readwritesplit.c:1346 +#3 0x00007fffd7191ed8 in routeQuery (instance=0x74b670, session=0x74b6b0, queue=0x74b7a0) at hintfilter.c:236 +#4 0x00007fffdc2a0b3d in route_by_statement (session=0x744ae0, readbuf=0x0) at mysql_client.c:1442 +#5 0x00007fffdc29f22c in gw_read_client_event (dcb=0x7446b0) at mysql_client.c:786 +#6 0x00000000004165da in poll_waitevents (arg=0x0) at poll.c:424 +#7 0x000000000040a72c in main (argc=4, argv=0x7fffffffe2e8) at gateway.c:1379 + +Failing point: + +1114 else if (hint->type == HINT_PARAMETER) +1115 { +1116 if (strncasecmp( +1117 (char *)hint->data, +1118 "max_slave_replication_lag", +1119 strlen("max_slave_replication_lag")) == 0) +1120 { + + +Value of hint: + +$6 = {type = HINT_PARAMETER, data = 0x0, value = 0x743a60, dsize = 0, next = 0x0} +*/ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->add_result(Test->connect_maxscale(), "Can not connect to Maxscale\n"); + + + Test->tprintf("Trying queries that caused crashes before fix: bug473\n"); + + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale route to server =("); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale route to server =)"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale route to server =:"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale route to server =a"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale route to server = a"); + Test->try_query(Test->conn_rwsplit, + (char *) "select @@server_id; -- maxscale route to server = кириллица åäö"); + + // bug472 + Test->tprintf("Trying queries that caused crashes before fix: bug472\n"); + Test->try_query(Test->conn_rwsplit, + (char *) "select @@server_id; -- maxscale s1 begin route to server server3"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale end"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale s1 begin"); + + // bug470 + Test->tprintf("Trying queries that caused crashes before fix: bug470\n"); + fflush(stdout); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale named begin route to master"); + Test->try_query(Test->conn_rwsplit, (char *) "select @@server_id;"); + Test->try_query(Test->conn_rwsplit, + (char *) "select @@server_id; -- maxscale named begin route to master; select @@server_id;"); + + + Test->close_maxscale_connections(); + + Test->tprintf("Checking if Maxscale is alive\n"); + fflush(stdout); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug475.cpp b/maxscale-system-test/bug475.cpp new file mode 100644 index 000000000..a1e03f214 --- /dev/null +++ b/maxscale-system-test/bug475.cpp @@ -0,0 +1,51 @@ +/** + * @file bug475.cpp regression case for bug 475 (The end comment tag in hints isn't properly detected) + * + * Test tries different hints with comments syntax and then checks log and checks if MaxScale is alive + */ + +// +//Markus Mäkelä 2014-08-08 10:09:48 UTC +//The closing tag isn't properly detected when using inline comments. +//Multiple commands cause this behaviour. The following commands cause these messages in the error log: + +//select /* maxscale hintname prepare route to master */ @@server_id; +//2014 08/08 13:01:09 Error : Syntax error in hint. Expected 'master', 'slave', or 'server' instead of '*/'. Hint ignored. + +//select /* maxscale hintname begin */ @@server_id; +//2014 08/08 13:02:45 Error : Syntax error in hint. Expected '=', 'prepare', or 'start' instead of '@@server_id'. Hint ignored. + +//The following only happens when no whitespace is used after 'master' and '*/': +//select /* maxscale route to master*/ @@server_id; +//2014 08/08 13:04:38 Error : Syntax error in hint. Expected 'master', 'slave', or 'server' instead of 'master*/'. Hint ignored. + +//All other forms of '/* maxscale route to [slave|server ]*/' work even without the whitespace before the closing tag. + + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + + Test->try_query(Test->conn_rwsplit, + (char *) "select /* maxscale hintname prepare route to master */ @@server_id;"); + Test->try_query(Test->conn_rwsplit, (char *) "select /* maxscale hintname begin */ @@server_id;"); + Test->try_query(Test->conn_rwsplit, (char *) "select /* maxscale route to master*/ @@server_id;"); + + Test->check_log_err((char *) "Syntax error in hint", false); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug479.cpp b/maxscale-system-test/bug479.cpp new file mode 100644 index 000000000..bb7c4a311 --- /dev/null +++ b/maxscale-system-test/bug479.cpp @@ -0,0 +1,35 @@ +/** + * @file bug479.cpp regression case for bug 479 ( Undefined filter reference in MaxScale.cnf causes a crash) + * + * - Maxscale.cnf with "filters=non existing filter | не существуюший фильтер", cheks error log for warnings and + * - check if Maxscale is alive + */ + + +/* +Markus Mäkelä 2014-08-15 17:38:06 UTC +Undefined filters in services cause a crash when the service is accessed. + +How to reproduce: +Define a service with a filter not defined in the MaxScale.cnf file, start MaxScale and access the service. +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->check_log_err((char *) "Unable to find filter 'non existing filter", true); + //global_result =Test->check_log_err((char *) "не существуюший фильтер", true); + //global_result +=Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug488.cpp b/maxscale-system-test/bug488.cpp new file mode 100644 index 000000000..4968698b7 --- /dev/null +++ b/maxscale-system-test/bug488.cpp @@ -0,0 +1,125 @@ +/** + * @file bug488.cpp regression case for bug 488 ("SHOW VARIABLES randomly failing with "Lost connection to MySQL server") + * + * - try "SHOW VARIABLES;" 100 times against all Maxscale services + * First round: 100 iterations for RWSplit, then ReadConn Master, then ReadConn Slave + * Second round: 100 iteration and in every iterations all three Maxscale services are in use. + * - check if Maxscale is alive. + */ + +/* +Kolbe Kegel 2014-08-27 18:37:14 UTC +Created attachment 138 [details] +good.txt and bad.txt + +Sending "SHOW VARIABLES" to MaxScale seems to sometimes result in "ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query". It appears to be random. It seems to be sending the query to the same backend server, so I'm not sure what is happening. I'm including the debug log for both the "good" and "bad" queries. +Comment 1 Vilho Raatikka 2014-08-27 18:41:25 UTC +Seems to happen exactly every second time with rwsplit router. Didn't experience it with read connection router. +Comment 2 Kolbe Kegel 2014-08-27 18:47:13 UTC +Not exactly every 2nd time. + +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query +$ mysql -h max1 -P 4006 -u maxuser -pmaxpwd -e 'show variables' >/dev/null +ERROR 2013 (HY000) at line 1: Lost connection to MySQL server during query +Comment 3 Vilho Raatikka 2014-08-28 10:48:39 UTC +SHOW VARIABLES is session write command - which is unnecessary because those could be read from any server - and what causes client to return 'lost connection' message to the user is duplicated response packet from MaxScale to the client. + +SHOW VARIABLES response should start like this: +T 127.0.0.1:59776 -> 127.0.0.1:4006 [AP] + 0f 00 00 00 03 73 68 6f 77 20 76 61 72 69 61 62 .....show variab + 6c 65 73 les + +T 127.0.0.1:4006 -> 127.0.0.1:59776 [AP] + 01 00 00 01 02 ..... + +T 127.0.0.1:4006 -> 127.0.0.1:59776 [AP] + 54 00 00 02 03 64 65 66 12 69 6e 66 6f 72 6d 61 T....def.informa + 74 69 6f 6e 5f 73 63 68 65 6d 61 09 56 41 52 49 tion_schema.VARI + 41 42 4c 45 53 09 56 41 52 49 41 42 4c 45 53 0d ABLES.VARIABLES. + 56 61 72 69 61 62 6c 65 5f 6e 61 6d 65 0d 56 41 Variable_name.VA + 52 49 41 42 4c 45 5f 4e 41 4d 45 0c 08 00 40 00 RIABLE_NAME...@. + 00 00 fd 01 00 00 00 00 ........ + +While in the failing case the initial packet is followed something like this: + +T 127.0.0.1:59776 -> 127.0.0.1:4006 [AP] + 0f 00 00 00 03 73 68 6f 77 20 76 61 72 69 61 62 .....show variab + 6c 65 73 les + +T 127.0.0.1:4006 -> 127.0.0.1:59776 [AP] + 1d 00 00 d5 18 69 6e 6e 6f 64 62 5f 75 73 65 5f .....innodb_use_ + 61 74 6f 6d 69 63 5f 77 72 69 74 65 73 03 4f 46 atomic_writes.OF + 46 F + +T 127.0.0.1:4006 -> 127.0.0.1:59776 [AP] + 19 00 00 d6 14 69 6e 6e 6f 64 62 5f 75 73 65 5f .....innodb_use_ + 66 61 6c 6c 6f 63 61 74 65 03 4f 46 46 fallocate.OFF + +- where those innodb related packets are duplicates from the previous response. + +*/ + + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + int i; + + Test->repl->connect(); + Test->connect_maxscale(); + + Test->tprintf("Trying SHOW VARIABLES to different Maxscale services\n"); + fflush(stdout); + Test->tprintf("RWSplit\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(5); + Test->try_query(Test->conn_rwsplit, (char *) "SHOW VARIABLES;"); + } + Test->tprintf("ReadConn master\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(5); + Test->try_query(Test->conn_master, (char *) "SHOW VARIABLES;"); + } + Test->tprintf("ReadConn slave\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(5); + Test->try_query(Test->conn_slave, (char *) "SHOW VARIABLES;"); + } + + Test->tprintf("All in one loop\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(5); + Test->try_query(Test->conn_rwsplit, (char *) "SHOW VARIABLES;"); + Test->try_query(Test->conn_master, (char *) "SHOW VARIABLES;"); + Test->try_query(Test->conn_slave, (char *) "SHOW VARIABLES;"); + } + + Test->set_timeout(10); + Test->close_maxscale_connections(); + Test->repl->close_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug493.cpp b/maxscale-system-test/bug493.cpp new file mode 100644 index 000000000..7ef712f6b --- /dev/null +++ b/maxscale-system-test/bug493.cpp @@ -0,0 +1,90 @@ +/** + * @file bug493.cpp regression case for bug 493 ( Can have same section name multiple times without warning) + * + * - Maxscale.cnf in which 'server2' is defined twice and tests checks error log for proper error message. + * - check if Maxscale is alive + */ + +/* +Hartmut Holzgraefe 2014-08-31 21:01:06 UTC +Due to a copy/paste error I ended up with two [server2] sections instead of having [server2] and [server3]. + +There were no error or warning messages about this in skygw_err1.log or skygw_msg1.log but only the second [server2] was actually used. + + +Configuration file: + +---8<------------------ +[maxscale] +threads=1 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +address=localhost +port=6603 + +[server1] +type=server +address=127.0.0.1 +port=3000 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3001 +protocol=MySQLBackend + +[server2] +type=server +address=127.0.0.1 +port=3002 +protocol=MySQLBackend +-------->8--- + +maxadmin results: + + +---8<-------------------- +MaxScale> list servers +Servers. +-------------------+-----------------+-------+----------------------+------------ +Server | Address | Port | Status | Connections +-------------------+-----------------+-------+----------------------+------------ +server1 | 127.0.0.1 | 3000 | Running | 0 +server2 | 127.0.0.1 | 3002 | Running | 0 +-------------------+-----------------+-------+----------------------+------------ +------------->8--- + +So no entry for the first (actually correct) [server2] on port 3001, +but only for the duplicate 2nd [server2] with port set to 3002 ... +Comment 1 Mark Riddoch 2014-09-01 16:17:51 UTC +The ini file parser we use allows multiple sections with the same name and will combine the section contains. Within this restriction we now have added code that will detect the same parameter being set twice and will warn the user. + +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->check_log_err((char *) "Duplicate section found: server2", true); + Test->check_log_err((char *) + "Failed to open, read or process the MaxScale configuration file /etc/maxscale.cnf. Exiting", true); + //Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug495.cpp b/maxscale-system-test/bug495.cpp new file mode 100644 index 000000000..cde63223b --- /dev/null +++ b/maxscale-system-test/bug495.cpp @@ -0,0 +1,43 @@ +/** + * @file bug495.cpp regression case for bug 495 ( Referring to a nonexisting server in servers=... doesn't even raise a warning ) + * + * - Maxscale.cnf with "servers= server1, server2,server3 ,server4,server5" + * but 'server5' is not defined. Test checks error log for proper error message. + * - check if Maxscale is alive + */ + +/* + +Description Hartmut Holzgraefe 2014-08-31 21:32:09 UTC +Only [server1] and [server2] are defined, +service [test_service] and monitor [MySQL monitor] +refer to a third server "server3" in their servers=... +list though ... + +Nothing in the err or msg log hints towards a problem ... +(which originally was caused by a copy/paste error that +also lead to the "duplicate section name" error reported +earlier ... and which would have been easy to track down +if either of these problems would at least have raised a +warning - took me almost an hour to track down the actual +problem ... :( +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->check_log_err((char *) "Unable to find server", true); + Test->check_log_err((char *) "errors were encountered while processing the configuration", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug507.cpp b/maxscale-system-test/bug507.cpp new file mode 100644 index 000000000..64568575b --- /dev/null +++ b/maxscale-system-test/bug507.cpp @@ -0,0 +1,126 @@ +/** + * @file bug507.cpp regression case for bug 507 ( "rw-split router does not send last_insert_id() to master" ) + * + * - "CREATE TABLE t2 (id INT(10) NOT NULL AUTO_INCREMENT, x int, PRIMARY KEY (id));", + * - do INSERT using RWsplit + * - do "select last_insert_id(), @@server_id" using RWSplit and directly to Master, compare @@server_id + * + */ + +/* +Kolbe Kegel 2014-09-01 14:45:56 UTC +After inserting a row via the rw-split router, a call to last_insert_id() can go to a slave, producing bad results. + +mariadb> select * from t1; ++----+ +| id | ++----+ +| 1 | +| 4 | ++----+ +2 rows in set (0.00 sec) + +mariadb> insert into t1 values (); +Query OK, 1 row affected (0.00 sec) + +mariadb> select * from t1; ++----+ +| id | ++----+ +| 1 | +| 4 | +| 7 | ++----+ +3 rows in set (0.00 sec) + +mariadb> select last_insert_id(); ++------------------+ +| last_insert_id() | ++------------------+ +| 0 | ++------------------+ +1 row in set (0.00 sec) + +mariadb> select @@wsrep_node_address, last_insert_id(); ++----------------------+------------------+ +| @@wsrep_node_address | last_insert_id() | ++----------------------+------------------+ +| 192.168.30.31 | 7 | ++----------------------+------------------+ +1 row in set (0.00 sec) +Comment 1 Vilho Raatikka 2014-09-01 17:51:45 UTC +last_inserted_id() belongs to UNKNOWN_FUNC class to which many read-only system functions belong too. Thus last_inserted_id() was routed to any slave. + +Unfortunately I can't confirm wrong behavior since running the same sequence generates same output when connected directly to MariaDB backend. Perhaps there is something required for the table t1 which is not included here? +Comment 2 Vilho Raatikka 2014-09-01 20:01:35 UTC +An autoincrement attribute was missing. +*/ + + +#include +#include "testconnections.h" + +const char * sel1 = "select last_insert_id(), @@server_id"; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->repl->connect(); + Test->connect_maxscale(); + + if (Test->repl->N < 3) + { + Test->tprintf("There is not enoght nodes for test\n"); + delete Test; + exit(1); + } + + Test->tprintf("Creating table\n"); + fflush(stdout); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t2"); + Test->try_query(Test->conn_rwsplit, + (char *) "CREATE TABLE t2 (id INT(10) NOT NULL AUTO_INCREMENT, x int, PRIMARY KEY (id));"); + Test->tprintf("Doing INSERTs\n"); + fflush(stdout); + Test->try_query(Test->conn_rwsplit, (char *) "insert into t2 (x) values (1);"); + + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->set_timeout(20); + Test->tprintf("Trying \n"); + char last_insert_id1[1024]; + char last_insert_id2[1024]; + if ( ( + find_field( + Test->conn_rwsplit, sel1, + "@@server_id", &last_insert_id1[0]) + != 0 ) || ( + find_field( + Test->repl->nodes[0], sel1, + "@@server_id", &last_insert_id2[0]) + != 0 )) + { + Test->tprintf("@@server_id fied not found!!\n"); + delete Test; + exit(1); + } + else + { + Test->tprintf("'%s' to RWSplit gave @@server_id %s\n", sel1, last_insert_id1); + Test->tprintf("'%s' directly to master gave @@server_id %s\n", sel1, last_insert_id2); + Test->add_result(strcmp(last_insert_id1, last_insert_id2), + "last_insert_id() are different depending in which order terms are in SELECT\n"); + } + + Test->close_maxscale_connections(); + Test->repl->close_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug509.cpp b/maxscale-system-test/bug509.cpp new file mode 100644 index 000000000..56bd204a7 --- /dev/null +++ b/maxscale-system-test/bug509.cpp @@ -0,0 +1,144 @@ +/** + * @file bug509.cpp regression case for bug 509 and 507 ( "Referring to a nonexisting server in servers=... doesn't even raise a warning" + * and "rw-split router does not send last_insert_id() to master" ) + * + * - "CREATE TABLE t2 (id INT(10) NOT NULL AUTO_INCREMENT, x int, PRIMARY KEY (id));", + * - do a number of INSERTs first using RWsplit, then directly Galera nodes. + * - do "select @@wsrep_node_address, last_insert_id();" and "select last_insert_id(), @@wsrep_node_address;" and compares results. + * - do "insert into t2 (x) values (i);" 1000 times and compares results of + * "select @@wsrep_node_address, last_insert_id();" and "select last_insert_id(), @@wsrep_node_address;" + * + * Test fails if results are different (after 5 seconds of waiting after last INSERT) + */ + +/* +Kolbe Kegel 2014-09-01 14:48:12 UTC +For some reason, the order of terms in the field list of a SELECT statement influences how the rw-split router decides where to send a statement. + +mariadb> select @@wsrep_node_address, last_insert_id(); ++----------------------+------------------+ +| @@wsrep_node_address | last_insert_id() | ++----------------------+------------------+ +| 192.168.30.31 | 7 | ++----------------------+------------------+ +1 row in set (0.00 sec) + +mariadb> select last_insert_id(), @@wsrep_node_address; ++------------------+----------------------+ +| last_insert_id() | @@wsrep_node_address | ++------------------+----------------------+ +| 0 | 192.168.30.33 | ++------------------+----------------------+ +1 row in set (0.00 sec) +Comment 1 Vilho Raatikka 2014-09-03 20:44:17 UTC +Added code to detect last_insert_id() function and now both types of elements are routed to master and their order of appearance doesn't matter. +*/ + + +#include +#include "testconnections.h" + +const char * sel1 = "select @@wsrep_node_address, last_insert_id();"; +const char * sel2 = "select last_insert_id(), @@wsrep_node_address;"; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + + Test->galera->connect(); + Test->connect_maxscale(); + + if (Test->galera->N < 3) + { + Test->tprintf("There is not enoght nodes for test\n"); + delete Test; + exit(1); + } + + Test->tprintf("Creating table\n"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t2;"); + Test->try_query(Test->conn_rwsplit, + (char *) "CREATE TABLE t2 (id INT(10) NOT NULL AUTO_INCREMENT, x int, PRIMARY KEY (id));"); + Test->tprintf("Doing INSERTs\n"); + Test->try_query(Test->conn_rwsplit, (char *) "insert into t2 (x) values (1);"); + + Test->try_query(Test->galera->nodes[0], (char *) "insert into t2 (x) values (2);"); + Test->try_query(Test->galera->nodes[0], (char *) "insert into t2 (x) values (3);"); + + Test->try_query(Test->galera->nodes[1], (char *) "insert into t2 (x) values (4);"); + Test->try_query(Test->galera->nodes[1], (char *) "insert into t2 (x) values (5);"); + Test->try_query(Test->galera->nodes[1], (char *) "insert into t2 (x) values (6);"); + + Test->try_query(Test->galera->nodes[2], (char *) "insert into t2 (x) values (7);"); + Test->try_query(Test->galera->nodes[2], (char *) "insert into t2 (x) values (8);"); + Test->try_query(Test->galera->nodes[2], (char *) "insert into t2 (x) values (9);"); + Test->try_query(Test->galera->nodes[2], (char *) "insert into t2 (x) values (10);"); + + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->tprintf("Trying \n"); + char last_insert_id1[1024]; + char last_insert_id2[1024]; + if ( ( + find_field( + Test->conn_rwsplit, sel1, + "last_insert_id()", &last_insert_id1[0]) + != 0 ) || ( + find_field( + Test->conn_rwsplit, sel2, + "last_insert_id()", &last_insert_id2[0]) + != 0 )) + { + Test->tprintf("last_insert_id() fied not found!!\n"); + delete Test; + exit(1); + } + else + { + Test->tprintf("'%s' gave last_insert_id() %s\n", sel1, last_insert_id1); + Test->tprintf("'%s' gave last_insert_id() %s\n", sel2, last_insert_id2); + Test->add_result(strcmp(last_insert_id1, last_insert_id2), + "last_insert_id() are different depending in which order terms are in SELECT\n"); + } + + char id_str[1024]; + char str1[1024]; + int iterations = 150; + + for (int i = 100; i < iterations; i++) + { + Test->set_timeout(50); + Test->add_result(execute_query(Test->conn_rwsplit, "insert into t2 (x) values (%d);", i), "Query failed"); + + sprintf(str1, "select * from t2 where x=%d;", i); + + find_field(Test->conn_rwsplit, sel1, "last_insert_id()", &last_insert_id1[0]); + find_field(Test->conn_rwsplit, str1, "id", &id_str[0]); + + int n = 0; + + while (strcmp(last_insert_id1, id_str) != 0 && n < 5) + { + Test->tprintf("Replication is lagging"); + sleep(1); + find_field(Test->conn_rwsplit, str1, "id", &id_str[0]); + n++; + } + + Test->add_result(strcmp(last_insert_id1, id_str), + "last_insert_id is not equal to id even after waiting 5 seconds"); + + if (i % 10 == 0) + { + Test->tprintf("last_insert_id is %s, id is %s", last_insert_id1, id_str); + } + } + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug519.cpp b/maxscale-system-test/bug519.cpp new file mode 100644 index 000000000..9cfb474bb --- /dev/null +++ b/maxscale-system-test/bug519.cpp @@ -0,0 +1,152 @@ +/** + * @file bug519.cpp - Jira task is MAX-345 + * - fill t1 with data + * - execute SELECT * INTO OUTFILE '/tmp/t1.csv' FROM t1; against all routers + * - DROP TABLE t1 + * - LOAD DATA LOCAL INFILE 't1.csv' INTO TABLE t1; using RWSplit + * - check if t1 contains right data + * - DROP t1 again and repeat LOAD DATA LOCAL INFILE 't1.csv' INTO TABLE t1; using ReadConn master + */ + +/* + +It seems that LOAD DATA LOCAL INFILE is not handled by readwritesplit? Maybe it's a bigger problem elsewhere in MaxScale? + +I can execute the command, it looks like it is getting sent to the master, but ... no data is actually loaded. Does/can MaxScale handle LOAD DATA LOCAL INFILE? +Comment 1 Kolbe Kegel 2014-09-03 02:39:47 UTC +The LOAD DATA LOCAL INFILE statement is stuck in "Reading from net" until some timeout is hit: + +| 22 | maxuser | 192.168.30.38:59996 | test | Query | 10 | Reading from net | load data local infile '/Users/kolbe/Devel/seattleparking/Street_Parking_Signs.csv' into table parki | 0.000 | + +The client never sees the statement end, though, even after the server has long ago killed its connection... + +When I start a *new* connection to MaxScale and I try to execute the LOAD DATA LOCAL INFILE statement again, I have some problems: + +mysql 5.5.38-MariaDB (maxuser) [test]> source /Users/kolbe/Devel/seattleparking/loaddata.sql +ERROR 2013 (HY000) at line 1 in file: '/Users/kolbe/Devel/seattleparking/loaddata.sql': Lost connection to MySQL server during query +mysql 5.5.38-MariaDB (maxuser) [test]> select @@wsrep_node_address; +ERROR 2006 (HY000): MySQL server has gone away +No connection. Trying to reconnect... +Connection id: 1709 +Current database: test + ++----------------------+ +| @@wsrep_node_address | ++----------------------+ +| 192.168.30.32 | ++----------------------+ +1 row in set (0.01 sec) + +mysql 5.5.38-MariaDB (maxuser) [test]> source /Users/kolbe/Devel/seattleparking/loaddata.sql +ERROR 2013 (HY000) at line 1 in file: '/Users/kolbe/Devel/seattleparking/loaddata.sql': Lost connection to MySQL server during query +mysql 5.5.38-MariaDB (maxuser) [test]> select @@wsrep_node_address; +ERROR 2006 (HY000): MySQL server has gone away +No connection. Trying to reconnect... +Connection id: 1709 +Current database: test + ++----------------------+ +| @@wsrep_node_address | ++----------------------+ +| 192.168.30.32 | ++----------------------+ +1 row in set (0.01 sec) + +mysql 5.5.38-MariaDB (maxuser) [test]> source /Users/kolbe/Devel/seattleparking/loaddata.sql +ERROR 2013 (HY000) at line 1 in file: '/Users/kolbe/Devel/seattleparking/loaddata.sql': Lost connection to MySQL server during query +mysql 5.5.38-MariaDB (maxuser) [test]> Bye +*/ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int N = 4; + int iterations = 2; + if (Test->smoke) + { + iterations = 1; + } + char str[1024]; + Test->set_timeout(10); + + Test->connect_maxscale(); + Test->repl->connect(); + + Test->tprintf("Create t1\n"); + create_t1(Test->conn_rwsplit); + Test->tprintf("Insert data into t1\n"); + Test->set_timeout(60); + insert_into_t1(Test->conn_rwsplit, N); + Test->stop_timeout(); + Test->repl->sync_slaves(); + Test->set_timeout(200); + + sprintf(str, "%s rm -f /tmp/t*.csv; %s chmod 777 /tmp", Test->repl->access_sudo[0], + Test->repl->access_sudo[0]); + Test->tprintf("%s\n", str); + for (int k = 0; k < Test->repl->N; k++) + { + Test->repl->ssh_node(k, str, false); + } + //system(str); + + Test->tprintf("Copying data from t1 to file...\n"); + Test->tprintf("using RWSplit: SELECT * INTO OUTFILE '/tmp/t1.csv' FROM t1;\n"); + Test->try_query(Test->conn_rwsplit, (char *) "SELECT * INTO OUTFILE '/tmp/t1.csv' FROM t1;"); + Test->tprintf("using ReadConn master: SELECT * INTO OUTFILE '/tmp/t2.csv' FROM t1;\n"); + Test->try_query(Test->conn_master, (char *) "SELECT * INTO OUTFILE '/tmp/t2.csv' FROM t1;"); + Test->tprintf("using ReadConn slave: SELECT * INTO OUTFILE '/tmp/t3.csv' FROM t1;\n"); + Test->try_query(Test->conn_slave, (char *) "SELECT * INTO OUTFILE '/tmp/t3.csv' FROM t1;"); + + Test->tprintf("Copying t1.cvs from Maxscale machine:\n"); + Test->copy_from_maxscale((char *) "/tmp/t1.csv", (char *) "./"); + /*sprintf(str, + "scp -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s:/tmp/t1.csv ./", + Test->repl->sshkey[0], Test->repl->access_user[0], Test->repl->IP[0]); + Test->tprintf("%s\n", str); + system(str);*/ + + MYSQL *srv[2]; + + srv[0] = Test->conn_rwsplit; + srv[1] = Test->conn_master; + for (int i = 0; i < iterations; i++) + { + Test->set_timeout(100); + Test->tprintf("Dropping t1 \n"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE t1;"); + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->set_timeout(100); + Test->tprintf("Create t1\n"); + create_t1(Test->conn_rwsplit); + Test->tprintf("Loading data to t1 from file\n"); + Test->try_query(srv[i], (char *) "LOAD DATA LOCAL INFILE 't1.csv' INTO TABLE t1;"); + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->set_timeout(100); + Test->tprintf("SELECT: rwsplitter\n"); + Test->add_result(select_from_t1(Test->conn_rwsplit, N), "Wrong data in 't1'"); + Test->tprintf("SELECT: master\n"); + Test->add_result(select_from_t1(Test->conn_master, N), "Wrong data in 't1'"); + Test->tprintf("SELECT: slave\n"); + Test->add_result(select_from_t1(Test->conn_slave, N), "Wrong data in 't1'"); + } + + Test->repl->close_connections(); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug526.cpp b/maxscale-system-test/bug526.cpp new file mode 100644 index 000000000..fa8042c4b --- /dev/null +++ b/maxscale-system-test/bug526.cpp @@ -0,0 +1,69 @@ +/** + * @file bug526.cpp regression case for bug 526 ( " Wrong module name crashes maxscale on connect" ) + * + * - Maxscale.cnf with "filters=QLA|testfilter" for RWSplit router service, 'testfilter' is not defined. + * - check error log for proper error messages and checks if ReadConn services are alive + */ + +/* +Hartmut Holzgraefe 2014-09-08 13:08:46 UTC +I mistyped a module name (for a filter in this case) + + [testfilter] + type=filter + module=foobar + +There were no warnings about this on startup at all, but at the first time trying to connect to a service this filter was used in maxscale crashed with a segmentation fault after writing the following error log entries: + + 2014 09/08 15:00:53 Error : Unable to find library for module: foobar. + 2014 09/08 15:00:53 Failed to create filter 'testfilter' for service 'testrouter'. + 2014 09/08 15:00:53 Error : Failed to create Read Connection Router session. + 2014 09/08 15:00:53 Error : Invalid authentication message from backend. Error : 28000, Msg : Access denied for user 'maxuser'@'localhost' (using password: YES) + 2014 09/08 15:00:53 Error : Backend server didn't accept authentication for user denied for user 'maxuser'@'localhost' (using password: YES). + +Two problems here: + +1) can't check up front that my configuration is valid / without errors without connecting to each defined service + +2) maxscale crashes instead of handling this situation gracefully (e.g. ignoring the misconfigured filter, or disabling the service that refers to it alltogether) +*/ + + + +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + if (Test->connect_rwsplit() == 0) + { + Test->add_result(1, "Filter config is broken, but service is started\n"); + } + if (Test->connect_readconn_master() == 0) + { + Test->add_result(1, "Filter config is broken, but Maxscale is started\n"); + } + if (Test->connect_readconn_slave() == 0) + { + Test->add_result(1, "Filter config is broken, but Maxscale is started\n"); + } + + //sleep(5); + Test->execute_maxadmin_command((char*) "sync logs"); + Test->check_log_err((char *) "Unable to find library for module: foobar", true); + Test->check_log_err((char *) "Failed to load filter module 'foobar'", true); + Test->check_log_err((char *) "Failed to load filter 'testfilter' for service 'RW Split Router'", true); + Test->check_log_err((char *) + "Failed to open, read or process the MaxScale configuration file /etc/maxscale.cnf. Exiting", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug529.cpp b/maxscale-system-test/bug529.cpp new file mode 100644 index 000000000..5f5de6d36 --- /dev/null +++ b/maxscale-system-test/bug529.cpp @@ -0,0 +1,315 @@ +/** + * @file bug529.cpp regression case for bug 529 ( "'Current no. of conns' not going down" ) + * + * - create table, opens 50 connections for every router, fill table with data using these connections. + * - check number of connections to Master - failure if there are more then 100 connections to master. + * - close RWSptit and ReadConn master connections and check connections to master again. + * - create 50 ReadConn slave connection in parallel threads, execute "SELECT * FROM t1" ones for every connections, then + * start using one of connections to create "SELECT" load. + * - check number of connections to Master again, wait 5 seconds and check number of connections to + * master ones more time: now expecting 0 connections to master (fail if there is a least one connection to master). + * - close and reopens all ReadConn slave connections in the main thread and check connections to master again + * - close all connection in all threads, close parallel thread + * - do final 'connections to master' check + */ + +/* +lisu87 2014-09-08 16:50:29 UTC +After starting maxscale and putting some traffic to it, the number of current connections to master server are still going up: + +Server 0x29e6330 (carlsberg) + Server: xxx.xxx.xxx.xxx + Status: Master, Running + Protocol: MySQLBackend + Port: 3306 + Node Id: -1 + Master Id: -1 + Repl Depth: -1 + Number of connections: 58 + Current no. of conns: 29 + Current no. of operations: 0 +Server 0x2948f60 (psy-carslave-1) + Server: xxx.xxx.xxx.xxx + Status: Slave, Running + Protocol: MySQLBackend + Port: 3306 + Node Id: -1 + Master Id: -1 + Repl Depth: -1 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 +Server 0x2948e60 (psy-carslave-2) + Server: xxx.xxx.xxx.xxx + Status: Slave, Running + Protocol: MySQLBackend + Port: 3306 + Node Id: -1 + Master Id: -1 + Repl Depth: -1 + Number of connections: 29 + Current no. of conns: 0 + Current no. of operations: 0 +Comment 1 Vilho Raatikka 2014-09-09 06:53:56 UTC +Is the version release-1.0beta? +Does any load cause this or does it require multiple parallel clients, for example? +Comment 2 lisu87 2014-09-09 07:53:18 UTC +The version is release-1.0beta. + +Even when just one short connection is made the counter of "Current no. of conns" goes up. + +Interesting thing is that the amount of current connections for my slave is always exactly two times smaller than "Number of connections": + +Server 0x1f51330 (carlsberg) + Server: 172.16.76.8 + Status: Master, Running + Protocol: MySQLBackend + Port: 3306 + Node Id: -1 + Master Id: -1 + Repl Depth: -1 + Number of connections: 3278 + Current no. of conns: 1639 + Current no. of operations: 0 +Comment 3 lisu87 2014-09-11 09:54:34 UTC +Any update on this one? +Comment 4 Vilho Raatikka 2014-09-11 10:34:20 UTC +The problem can't be reproduced with the code I'm working currently, and which will be the one where beta release will be refresed from. Thus, I'd wait till beta refresh is done and see if the problem still exists. +Comment 5 lisu87 2014-09-11 10:47:32 UTC +Thank you. + +And one more question: is it normal that even if SELECT query has been performed on skave the "Number of connections" counter for master increases too? +Comment 6 Vilho Raatikka 2014-09-11 11:02:08 UTC +(In reply to comment #5) +> Thank you. +> +> And one more question: is it normal that even if SELECT query has been +> performed on skave the "Number of connections" counter for master increases +> too? + +When rwsplit listens port 3333 and when a command like : + +mysql -h 127.0.0.1 -P 3333 -u maxscaleuser -ppwd -e "select count(user) from mysql.user" + +is executed client connects to MaxScale:3333, and MaxScale connects to master and slave(s). So connection count increases in each of those backends despite of query type. + +If you already have a rwsplit session, no new connections should be created when new queries are executed. +Comment 7 Vilho Raatikka 2014-09-11 12:34:26 UTC +I built MaxScale from releaes-1.0beta-refresh branch and tested by running 5000 prepared statements in one session to MaxScale/RWSplit and executing 'show servers' in another window. During the run the number of current connections was 1 in each server and after the run all 'current' counters show 0. + +If you want me to try with some other use case, describe it and I'll give it a try. +Comment 8 lisu87 2014-09-11 12:45:37 UTC +Thanks, Vilho. + +I'm building maxscale from that branch now and will retest shortly. +Comment 9 lisu87 2014-09-11 14:45:26 UTC +Confirmed. It works fine with 1.0beta-refresh. + +Thank you! +Comment 10 Vilho Raatikka 2014-09-22 10:11:06 UTC +The problem reappeared later and was eventually fixed in release-1.0beta-refresh commit a41a8d6060c7b60e09686bea8124803f047d85ad + +*/ + +// counting connection to all services + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; +int conn_N = 50; + +TestConnections * Test ; + +void *parall_traffic( void *ptr ); + + +int main(int argc, char *argv[]) +{ + + Test = new TestConnections(argc, argv); + Test->set_timeout(120); + int i; + int num_conn = 0; + char sql[100]; + + pthread_t parall_traffic1; + int check_iret; + + MYSQL * conn; + MYSQL * rwsplit_conn[conn_N]; + MYSQL * master_conn[conn_N]; + MYSQL * slave_conn[conn_N]; + + Test->repl->connect(); + + conn = Test->open_rwsplit_connection(); + execute_query(conn, (char *) "DROP DATABASE IF EXISTS test"); + execute_query(conn, (char *) "CREATE DATABASE test"); + execute_query(conn, (char *) "USE test;"); + + create_t1(conn); + mysql_close(conn); + Test->tprintf("Table t1 is created\n"); + + for (i = 0; i < conn_N; i++) + { + Test->set_timeout(60); + rwsplit_conn[i] = Test->open_rwsplit_connection(); + master_conn[i] = Test->open_readconn_master_connection(); + slave_conn[i] = Test->open_readconn_slave_connection(); + sprintf(sql, "INSERT INTO t1 (x1, fl) VALUES(%d, 1);", i); + execute_query(rwsplit_conn[i], sql); + sprintf(sql, "INSERT INTO t1 (x1, fl) VALUES(%d, 2);", i); + execute_query(master_conn[i], sql); + fflush(stdout); + } + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + Test->tprintf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn > 2 * conn_N)) + { + Test->add_result(1, "too many connections to master\n"); + } + } + + Test->tprintf("Closing RWSptit and ReadConn master connections\n"); + for (i = 0; i < conn_N; i++) + { + mysql_close(rwsplit_conn[i]); + mysql_close(master_conn[i]); + } + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + printf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn > 2 * conn_N)) + { + Test->add_result(1, "too many connections to master\n"); + } + } + + Test->tprintf("Opening more connection to ReadConn slave in parallel thread\n"); + + check_iret = pthread_create( ¶ll_traffic1, NULL, parall_traffic, NULL); + + for (i = 0; i < Test->repl->N; i++) + { + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + printf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn > 2 * conn_N)) + { + Test->add_result(1, "too many connections to master\n"); + } + } + + Test->stop_timeout(); + Test->tprintf("Sleeping 15 seconds\n"); + sleep(15); + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + printf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn != 0)) + { + Test->add_result(1, "there are still connections to master\n"); + } + } + + printf("Closing ReadConn slave connections\n"); + for (i = 0; i < conn_N; i++) + { + mysql_close(slave_conn[i]); + } + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + Test->tprintf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn != 0)) + { + Test->add_result(1, "there are still connections to master\n"); + } + } + + Test->tprintf("Opening ReadConn slave connections again\n"); + for (i = 0; i < conn_N; i++) + { + Test->set_timeout(60); + slave_conn[i] = Test->open_readconn_slave_connection(); + sprintf(sql, "SELECT * FROM t1"); + execute_query(slave_conn[i], sql); + fflush(stdout); + } + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + Test->tprintf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn != 0)) + { + Test->add_result(1, "there are still connections to master\n"); + } + } + + Test->tprintf("Closing ReadConn slave connections\n"); + for (i = 0; i < conn_N; i++) + { + Test->set_timeout(20); + mysql_close(slave_conn[i]); + } + exit_flag = 1; + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(60); + num_conn = get_conn_num(Test->repl->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + Test->tprintf("Connections to node %d (%s): %d\n", i, Test->repl->IP[i], num_conn); + if ((i == 0) && (num_conn != 0)) + { + Test->add_result(1, "there are still connections to master\n"); + } + } + + int rval = Test->global_result; + delete Test; + return rval; +} + + +void *parall_traffic( void *ptr ) +{ + MYSQL * slave_conn1[conn_N]; + int i; + for (i = 0; i < conn_N; i++) + { + slave_conn1[i] = Test->open_readconn_slave_connection(); + execute_query(slave_conn1[i], "SELECT * FROM t1"); + } + + while (exit_flag == 0) + { + execute_query(slave_conn1[0], "SELECT * FROM t1"); + } + for (i = 0; i < conn_N; i++) + { + mysql_close(slave_conn1[i]); + } + + return NULL; +} diff --git a/maxscale-system-test/bug539.cpp b/maxscale-system-test/bug539.cpp new file mode 100644 index 000000000..25ce79914 --- /dev/null +++ b/maxscale-system-test/bug539.cpp @@ -0,0 +1,56 @@ +/** + * @file bug539.cpp regression case for bug539 ("MaxScale crashes in session_setup_filters") + * using maxadmin execute "fail backendfd" + * try quries against all services + * using maxadmin execute "fail clientfd" + * try quries against all services + * check if MaxScale alive + */ + + +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + int i, j; + MYSQL * conn; + + int N_cmd = 2; + char * fail_cmd[N_cmd - 1]; + + int N_ports = 3; + int ports[N_ports]; + + fail_cmd[0] = (char *) "fail backendfd"; + fail_cmd[1] = (char *) "fail clientfd"; + + ports[0] = Test->rwsplit_port; + ports[1] = Test->readconn_master_port; + ports[2] = Test->readconn_slave_port; + + for (i = 0; i < N_cmd; i++) + { + for (j = 0; j < N_ports; j++) + { + Test->tprintf("Executing MaxAdmin command '%s'\n", fail_cmd[i]); + if (execute_maxadmin_command(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, fail_cmd[i]) != 0) + { + Test->add_result(1, "MaxAdmin command failed\n"); + } + else + { + printf("Trying query against %d\n", ports[j]); + conn = open_conn(ports[j], Test->maxscale_IP, Test->maxscale_user, Test->maxscale_user, Test->ssl); + Test->try_query(conn, (char *) "show processlist;"); + } + } + } + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug547.cpp b/maxscale-system-test/bug547.cpp new file mode 100644 index 000000000..8649f14a2 --- /dev/null +++ b/maxscale-system-test/bug547.cpp @@ -0,0 +1,72 @@ +/** + * @file bug547.cpp regression case for bug 547 and bug 594 ( "get_dcb fails if slaves are not available" and "Maxscale fails to start without anything in the logs if there is no slave available" ) + * Behaviour has been changed and this test check only for crash + * - block all slaves + * - try some queries (create table, do INSERT using RWSplit router) + * - check there is no crash + */ + +/* +Vilho Raatikka 2014-09-16 07:43:54 UTC +get_dcb function returns the backend descriptor for router. Some merge has broken the logic and in case of non-existent slave the router simply fails to find a backend server although master would be available. +Comment 1 Vilho Raatikka 2014-09-16 09:40:14 UTC +get_dcb now searches master if slaves are not available. +*/ + +// also relates to bug594 +// all slaves in MaxScale config have wrong IP + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int i; + + for (i = 1; i < Test->repl->N; i++) + { + Test->set_timeout(20); + Test->repl->block_node(i); + } + + Test->set_timeout(30); + sleep(15); + + Test->set_timeout(30); + Test->tprintf("Connecting to all MaxScale services, expecting error\n"); + Test->connect_maxscale(); + + Test->set_timeout(30); + Test->tprintf("Trying some queries, expecting failure, but not a crash\n"); + execute_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t1"); + execute_query(Test->conn_rwsplit, (char *) "CREATE TABLE t1 (x INT)"); + execute_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 (x) VALUES (1)"); + execute_query(Test->conn_rwsplit, (char *) "select * from t1"); + execute_query(Test->conn_master, (char *) "select * from t1"); + execute_query(Test->conn_slave, (char *) "select * from t1"); + + Test->set_timeout(10); + Test->close_maxscale_connections(); + + Test->set_timeout(30); + Test->repl->unblock_all_nodes(); + + Test->stop_timeout(); + sleep(15); + Test->check_log_err((char *) "fatal signal 11", false); + Test->check_log_err((char *) "Failed to create new router session for service 'RW-Split-Router'", true); + Test->check_log_err((char *) + "Failed to create new router session for service 'Read-Connection-Router-Master'", true); + Test->check_log_err((char *) "Failed to create new router session for service 'Read-Connection-Router-Slave'", + true); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug561.sh b/maxscale-system-test/bug561.sh new file mode 100755 index 000000000..9ceb8c16a --- /dev/null +++ b/maxscale-system-test/bug561.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +### +## @file bug561.sh Regression case for the bug "Different error messages from MariaDB and Maxscale" +## - try to connect to non existing DB directly to MariaDB server and via Maxscale +## - compare error messages +## - repeat for RWSplit, ReadConn + + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` + +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +#echo "Waiting for 15 seconds" +#sleep 15 + +mariadb_err=`mysql -u$node_user -p$node_password -h $node_000_network $ssl_options --socket=$node_000_socket -P $node_000_port non_existing_db 2>&1` +maxscale_err=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options non_existing_db 2>&1` + +maxscale_err1=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4008 $ssl_options non_existing_db 2>&1` +maxscale_err2=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4009 $ssl_options non_existing_db 2>&1` + +echo "MariaDB message" +echo "$mariadb_err" +echo " " +echo "Maxscale message from RWSplit" +echo "$maxscale_err" +echo "Maxscale message from ReadConn master" +echo "$maxscale_err1" +echo "Maxscale message from ReadConn slave" +echo "$maxscale_err2" + +res=0 + +#echo "$maxscale_err" | grep "$mariadb_err" +if [ "$maxscale_err" != "$mariadb_err" ] ; then + echo "Messages are different!" + echo "MaxScale: $maxscale_err" + echo "Server: $mariadb_err" + res=1 +else + echo "Messages are same" +fi + +if [ "$maxscale_err1" != "$mariadb_err" ] ; then + echo "Messages are different!" + echo "MaxScale: $maxscale_err1" + echo "Server: $mariadb_err" + res=1 +else + echo "Messages are same" +fi + +if [ "$maxscale_err2" != "$mariadb_err" ] ; then + echo "Messages are different!" + echo "MaxScale: $maxscale_err2" + echo "Server: $mariadb_err" + + res=1 +else + echo "Messages are same" +fi + +$test_dir/copy_logs.sh bug561 +exit $res diff --git a/maxscale-system-test/bug562.sh b/maxscale-system-test/bug562.sh new file mode 100755 index 000000000..a19907a6f --- /dev/null +++ b/maxscale-system-test/bug562.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +### +## @file bug562.sh Regression case for the bug "Wrong error message for Access denied error" +## - try to connect with bad credestials directly to MariaDB server and via Maxscale +## - compare error messages + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` + +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +mariadb_err=`mysql -u no_such_user -psome_pwd -h $node_001_network $ssl_option --socket=$node_000_socket test 2>&1` +maxscale_err=`mysql -u no_such_user -psome_pwd -h $maxscale_IP -P 4006 $ssl_options test 2>&1` + +echo "MariaDB message" +echo "$mariadb_err" +echo " " +echo "Maxscale message" +echo "$maxscale_err" + +res=0 +#echo "$maxscale_err" | grep "$mariadb_err" +echo "$maxscale_err" |grep "ERROR 1045 (28000): Access denied for user 'no_such_user'@'" +if [ "$?" != 0 ]; then + echo "Maxscale message is not ok!" + echo "Message: $maxscale_err" + res=1 +else + echo "Messages are same" + res=0 +fi + +$test_dir/copy_logs.sh bug562 +exit $res diff --git a/maxscale-system-test/bug564.sh b/maxscale-system-test/bug564.sh new file mode 100755 index 000000000..1113d74dc --- /dev/null +++ b/maxscale-system-test/bug564.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +### +## @file bug564.sh Regression case for the bug "Wrong charset settings" +## - call MariaDB client with different --default-character-set= settings +## - check output of SHOW VARIABLES LIKE 'char%' + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +for char_set in "latin1" "latin2" +do + + line1=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options --default-character-set="$char_set" -e "SHOW VARIABLES LIKE 'char%'" | grep "character_set_client"` + line2=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options --default-character-set="$char_set" -e "SHOW VARIABLES LIKE 'char%'" | grep "character_set_connection"` + line3=`mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options --default-character-set="$char_set" -e "SHOW VARIABLES LIKE 'char%'" | grep "character_set_results"` + + echo $line1 | grep "$char_set" + res1=$? + echo $line2 | grep "$char_set" + res2=$? + echo $line3 | grep "$char_set" + res3=$? + + + if [[ $res1 != 0 ]] || [[ $res2 != 0 ]] || [[ $res3 != 0 ]] ; then + echo "charset is ignored" + mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options --default-character-set="latin2" -e "SHOW VARIABLES LIKE 'char%'" + $test_dir/copy_logs.sh bug564 + exit 1 + fi +done +$test_dir/copy_logs.sh bug564 +exit 0 + + diff --git a/maxscale-system-test/bug565.cpp b/maxscale-system-test/bug565.cpp new file mode 100644 index 000000000..deaa749ff --- /dev/null +++ b/maxscale-system-test/bug565.cpp @@ -0,0 +1,106 @@ +/** + * @file bug565.cpp regression case for bug 565 ( "Clients CLIENT_FOUND_ROWS setting is ignored by maxscale" ) MAX-311 + * + * - open connection with CLIENT_FOUND_ROWS flag + * - CREATE TABLE t1(id INT PRIMARY KEY, val INT, msg VARCHAR(100)) + * - INSERT INTO t1 VALUES (1, 1, 'foo'), (2, 1, 'bar'), (3, 2, 'baz'), (4, 2, 'abc')" + * - check 'affected_rows' for folloing UPDATES: + * + UPDATE t1 SET msg='xyz' WHERE val=2" (expect 2) + * + UPDATE t1 SET msg='xyz' WHERE val=2 (expect 0) + * + UPDATE t1 SET msg='xyz' WHERE val=2 (expect 2) + */ + +/* +Hartmut Holzgraefe 2014-10-02 14:27:18 UTC +Created attachment 155 [details] +test for mysql_affected_rows() with/without CLIENT_FOUND_ROWS connection flag + +Even worse: connections via maxscale always behave as if CLIENT_FOUND_ROWS is set even though the default is NOT having it set. + +When doing the same update two times in a row without CLIENT_FOUND_ROWS +mysql_affected_rows() should return the number of rows actually changed +by the last query, while with CLIENT_FOUND_ROWS connection flag set the +number of matching rows is returned, even if the UPDATE didn't change +any column values. + +With a direct connection to mysqld this works as expected, +through readconnroute(master) I'm always getting the number of matching +rows (as if CLIENT_FOUND_ROWS was set), and not the number of actually +changed rows when CLIENT_FOUND_ROWS is not set (which is the default +behaviour when not setting connection options) + +Attaching PHP mysqli test script, result with direct mysqld connection is + + update #1: 2 + update #2: 0 + update #3: 2 + +while through maxscale it is + + update #1: 2 + update #2: 2 + update #3: 2 + +I also verified this using the C API directly to rule out that this is +a PHP specific problem +Comment 1 Vilho Raatikka 2014-10-08 14:11:38 UTC +Client flags are not passed to backend server properly. +Comment 2 Vilho Raatikka 2014-10-08 19:35:58 UTC +Pushed initial fix to MAX-311. Waiting for validation for the fix. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + MYSQL * conn_found_rows; + my_ulonglong rows; + + + Test->repl->connect(); + Test->connect_maxscale(); + + conn_found_rows = open_conn_db_flags(Test->rwsplit_port, Test->maxscale_IP, (char *) "test", + Test->maxscale_user, Test->maxscale_password, CLIENT_FOUND_ROWS, Test->ssl); + + Test->set_timeout(30); + execute_query(Test->conn_rwsplit, "DROP TABLE IF EXISTS t1"); + execute_query(Test->conn_rwsplit, "CREATE TABLE t1(id INT PRIMARY KEY, val INT, msg VARCHAR(100))"); + execute_query(Test->conn_rwsplit, + "INSERT INTO t1 VALUES (1, 1, 'foo'), (2, 1, 'bar'), (3, 2, 'baz'), (4, 2, 'abc')"); + + Test->set_timeout(30); + execute_query_affected_rows(Test->conn_rwsplit, "UPDATE t1 SET msg='xyz' WHERE val=2", &rows); + Test->tprintf("update #1: %ld (expeced value is 2)\n", (long) rows); + if (rows != 2) + { + Test->add_result(1, "Affected rows is not 2\n"); + } + + Test->set_timeout(30); + execute_query_affected_rows(Test->conn_rwsplit, "UPDATE t1 SET msg='xyz' WHERE val=2", &rows); + Test->tprintf("update #2: %ld (expeced value is 0)\n", (long) rows); + if (rows != 0) + { + Test->add_result(1, "Affected rows is not 0\n"); + } + + Test->set_timeout(30); + execute_query_affected_rows(conn_found_rows, "UPDATE t1 SET msg='xyz' WHERE val=2", &rows); + Test->tprintf("update #3: %ld (expeced value is 2)\n", (long) rows); + if (rows != 2) + { + Test->add_result(1, "Affected rows is not 2\n"); + } + + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; + +} diff --git a/maxscale-system-test/bug567.sh b/maxscale-system-test/bug567.sh new file mode 100755 index 000000000..06de54fd7 --- /dev/null +++ b/maxscale-system-test/bug567.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +### +## @file bug567.sh Regression case for the bug "Crash if files from /dev/shm/ removed" +## - try to remove everythign from /dev/shm/$maxscale_pid +## check if Maxscale is alive + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +#pid=`ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "pgrep maxscale"` +#echo "Maxscale pid is $pid" +echo "removing log directory from /dev/shm/" +if [ $maxscale_IP != "127.0.0.1" ] ; then + ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "sudo rm -rf /dev/shm/maxscale/*" +else + sudo rm -rf /dev/shm/maxscale/* +fi +sleep 1 +echo "checking if Maxscale is alive" +echo "show databases;" | mysql -u$node_user -p$node_password -h $maxscale_IP -P 4006 $ssl_options +res=$? + +$test_dir/copy_logs.sh bug567 +exit $res + diff --git a/maxscale-system-test/bug571.cpp b/maxscale-system-test/bug571.cpp new file mode 100644 index 000000000..d9e7ab492 --- /dev/null +++ b/maxscale-system-test/bug571.cpp @@ -0,0 +1,141 @@ +/** + * @file bug571.cpp regression case for bug 571 and bug 585 ( "Using regex filter hangs MaxScale" and "modutil_extract_SQL doesn't work with multiple GWBUF buffers" ) + * + * - Maxscale.cnf + * @verbatim + [regex] + type=filter + module=regexfilter + match=[Ff][Oo0][rR][mM] + replace=FROM + + [r2] + type=filter + module=regexfilter + match=fetch + replace=select + + [hints] + type=filter + module=hintfilter + + [RW Split Router] + type=service + router= readwritesplit + servers=server1, server2, server3,server4 + user=skysql + passwd=skysql + max_slave_connections=100% + use_sql_variables_in=all + router_options=slave_selection_criteria=LEAST_BEHIND_MASTER + filters=hints|regex|r2 + @endverbatim + * for bug585: + * @verbatim +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=regex|typo + @endverbatim + * - fetch * from mysql.user; + * - fetch count(*) form mysql.user; + * - check if Maxscale is alive + */ + +/* +Vilho Raatikka 2014-10-10 11:09:19 UTC +Branch:release-1.0.1beta +Executing : + +fetch * from mysql.user + +with this config hangs MaxScale + +[regex] +type=filter +module=regexfilter +match=[Ff][Oo0][rR][mM] +replace=FROM + +[r2] +type=filter +module=regexfilter +match=fetch +replace=select + + +[hints] +type=filter +module=hintfilter + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +user=maxuser +passwd=maxpwd +filters=hints|regex|r2 +Comment 1 Vilho Raatikka 2014-10-11 18:55:23 UTC +If we look at the rewrite function we see that query lacks one character. + +T 127.0.0.1:37858 -> 127.0.0.1:4006 [AP] + 16 00 00 00 03 66 65 74 63 68 20 69 64 20 66 72 .....fetch id fr + 6f 6d 20 74 65 73 74 2e 74 31 om test.t1 + +T 127.0.0.1:44591 -> 127.0.0.1:3000 [AP] + 17 00 00 00 03 73 65 6c 65 63 74 20 69 64 20 66 .....select id f + 72 6f 6d 20 74 65 73 74 2e 74 rom test.t +*/ + +// the same code is used for bug585 + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->tprintf("Connecting to all MaxScale services\n"); + Test->set_timeout(10); + Test->add_result(Test->connect_maxscale(), "Error connectiong to Maxscale\n"); + + Test->tprintf("executing fetch * from mysql.user \n"); + Test->set_timeout(10); + Test->try_query(Test->conn_rwsplit, (char *) "fetch * from mysql.user;"); + Test->set_timeout(10); + Test->try_query(Test->conn_rwsplit, (char *) "fetch count(*) form mysql.user;"); + + Test->set_timeout(10); + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug572.cpp b/maxscale-system-test/bug572.cpp new file mode 100644 index 000000000..e6e91c97b --- /dev/null +++ b/maxscale-system-test/bug572.cpp @@ -0,0 +1,46 @@ +/** + * @file bug572.cpp regression case for bug 572 ( " If reading a user from users table fails, MaxScale fails" ) + * + * - try GRANT with wrong IP using all Maxscale services: + * + GRANT ALL PRIVILEGES ON *.* TO 'foo'@'*.foo.notexists' IDENTIFIED BY 'foo'; + * + GRANT ALL PRIVILEGES ON *.* TO 'bar'@'127.0.0.*' IDENTIFIED BY 'bar' + * + DROP USER 'foo'@'*.foo.notexists' + * + DROP USER 'bar'@'127.0.0.*' + * - do "select * from mysql.user" using RWSplit to check if Maxsclae crashed + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +void create_drop_bad_user(MYSQL * conn, TestConnections * Test) +{ + + Test->try_query(conn, (char *) + "GRANT ALL PRIVILEGES ON *.* TO 'foo'@'*.foo.notexists' IDENTIFIED BY 'foo';"); + Test->try_query(conn, (char *) "GRANT ALL PRIVILEGES ON *.* TO 'bar'@'127.0.0.*' IDENTIFIED BY 'bar'"); + Test->try_query(conn, (char *) "DROP USER 'foo'@'*.foo.notexists'"); + Test->try_query(conn, (char *) "DROP USER 'bar'@'127.0.0.*'"); +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->repl->connect(); + Test->connect_maxscale(); + + Test->tprintf("Trying GRANT for with bad IP: RWSplit\n"); + create_drop_bad_user(Test->conn_rwsplit, Test); + + Test->tprintf("Trying SELECT to check if Maxscale hangs\n"); + Test->try_query(Test->conn_rwsplit, (char *) "select * from mysql.user"); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug587.cpp b/maxscale-system-test/bug587.cpp new file mode 100644 index 000000000..1a9a96670 --- /dev/null +++ b/maxscale-system-test/bug587.cpp @@ -0,0 +1,110 @@ +/** + * @file bug587.cpp regression case for the bug 587 ( "Hint filter don't work if listed before regex filter in configuration file" ) + * + * - Maxscale.cnf + * @verbatim +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints|regex +@endverbatim + * - second test (bug587_1) is executed with "filters=regex|hints" (dffeent order of filters) + * - check if hints filter working by executing and comparing results: + * + via RWSPLIT: "select @@server_id; -- maxscale route to server server%d" (%d - node number) + * + directly to backend node "select @@server_id;" + * - do the same test with "filters=regex|hints" "filters=hints|regex" + */ + +/* +Vilho Raatikka 2014-10-21 19:12:33 UTC +If filters and rwsplit are configured as follows, hints don't work. + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +user=maxuser +passwd=maxpwd +filters=hints|regex + +Changing filters=regex|hints makes it work. This is due to processing order. Regex filter drops hint off. +Comment 1 Vilho Raatikka 2014-10-23 18:08:07 UTC +buffer.c:gwbuf_make_contiguous: hint wasn't duplicated to new GWBUF struct. As a result hints were lost if query rewriting resulted in longer query than the original. +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->repl->connect(); + Test->connect_maxscale(); + + char server_id[256]; + char server_id_d[256]; + + char hint_sql[64]; + + for (int i = 1; i < 25; i++) + { + for (int j = 0; j < Test->repl->N; j++) + { + Test->set_timeout(10); + sprintf(hint_sql, "select @@server_id; -- maxscale route to server server%d", j + 1); + Test->tprintf("%s\n", hint_sql); + + find_field(Test->conn_rwsplit, hint_sql, (char *) "@@server_id", &server_id[0]); + find_field(Test->repl->nodes[j], (char *) "select @@server_id;", (char *) "@@server_id", &server_id_d[0]); + + Test->tprintf("server%d ID from Maxscale: \t%s\n", j + 1, server_id); + Test->tprintf("server%d ID directly from node: \t%s\n", j + 1, server_id_d); + + Test->add_result(strcmp(server_id, server_id_d), "Hints does not work!\n"); + } + } + + Test->close_maxscale_connections(); + Test->repl->close_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/bug592.cpp b/maxscale-system-test/bug592.cpp new file mode 100644 index 000000000..5c0488b3c --- /dev/null +++ b/maxscale-system-test/bug592.cpp @@ -0,0 +1,129 @@ +/** + * @file bug592.cpp regression case for bug 592 ( "slave in "Running" state breaks authorization" ) MXS-326 + * + * - stop all slaves: "stop slave;" directly to every node (now they are in "Running" state, not in "Russning, Slave") + * - via RWSplit "CREATE USER 'test_user'@'%' IDENTIFIED BY 'pass'" + * - try to connect using 'test_user' (expecting success) + * - start all slaves: "start slave;" directly to every node + * - via RWSplit: "DROP USER 'test_user'@'%'" + */ + +/* +Timofey Turenko 2014-10-24 09:35:35 UTC +1. setup: Master/Slave replication +2. reboot slaves +3. create user usinf connection to RWSplit +4. try to use this user to connect to Maxscale + +expected result: +Authentication is ok + +actual result: + Access denied for user 'user'@'192.168.122.1' (using password: YES) + +Th issue was discovered with following setup state: + +MaxScale> show servers +Server 0x3428260 (server1) + Server: 192.168.122.106 + Status: Master, Running + Protocol: MySQLBackend + Port: 3306 + Server Version: 5.5.40-MariaDB-log + Node Id: 106 + Master Id: -1 + Slave Ids: 107, 108 , 109 + Repl Depth: 0 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 +Server 0x3428160 (server2) + Server: 192.168.122.107 + Status: Slave, Running + Protocol: MySQLBackend + Port: 3306 + Server Version: 5.5.40-MariaDB-log + Node Id: 107 + Master Id: 106 + Slave Ids: + Repl Depth: 1 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 +Server 0x3428060 (server3) + Server: 192.168.122.108 + Status: Running + Protocol: MySQLBackend + Port: 3306 + Server Version: 5.5.40-MariaDB-log + Node Id: 108 + Master Id: 106 + Slave Ids: + Repl Depth: 1 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 +Server 0x338c3f0 (server4) + Server: 192.168.122.109 + Status: Running + Protocol: MySQLBackend + Port: 3306 + Server Version: 5.5.40-MariaDB-log + Node Id: 109 + Master Id: 106 + Slave Ids: + Repl Depth: 1 + Number of connections: 0 + Current no. of conns: 0 + Current no. of operations: 0 + + +Maxscale read mysql.user table from server4 which was not properly replicated +Comment 1 Mark Riddoch 2014-11-05 09:55:07 UTC +In the reload users routine, if there is a master available then use that rather than the first. +*/ + + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + int i; + + Test->repl->connect(); + Test->connect_maxscale(); + + for (i = 1; i < Test->repl->N; i++) + { + execute_query(Test->repl->nodes[i], (char *) "stop slave;"); + } + + execute_query(Test->conn_rwsplit, (char *) "CREATE USER 'test_user'@'%%' IDENTIFIED BY 'pass'"); + + MYSQL * conn = open_conn_no_db(Test->rwsplit_port, Test->maxscale_IP, (char *) "test_user", (char *) "pass", + Test->ssl); + + if (conn == NULL) + { + Test->add_result(1, "Connections error\n"); + } + + for (i = 1; i < Test->repl->N; i++) + { + execute_query(Test->repl->nodes[i], (char *) "start slave;"); + } + + execute_query(Test->conn_rwsplit, (char *) "DROP USER 'test_user'@'%%'"); + + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; + +} diff --git a/maxscale-system-test/bug601.cpp b/maxscale-system-test/bug601.cpp new file mode 100644 index 000000000..4b1205fe7 --- /dev/null +++ b/maxscale-system-test/bug601.cpp @@ -0,0 +1,128 @@ +/** + * @file bug601.cpp regression case for bug 601 ("COM_CHANGE_USER fails with correct user/pwd if executed during authentication") + * - configure Maxscale.cnf to use only one thread + * - in 100 parallel threads start to open/close session + * - do change_user 2000 times + * - check all change_user are ok + * - check Mascale is alive + */ + +/* +Vilho Raatikka 2014-10-30 14:30:57 UTC +If COM_CHANGE_USER is executed while backend protocol's state is not yet MYSQL_AUTH_RECV it will fail in the backend. + +If MaxScale uses multiple worked threads this occurs rarely and it would be possible to easily suspend execution of COM_CHANGE_USER. + +If MaxScale uses one worker thread then there's currently no way to suspend execution. It would require thread to put current task on hold, complete authentication task and return to COM_CHANGE_USER execution. + +In theory it is possible to add an event to client's DCB and let it become notified in the same way than events that occur in sockets. It would have to be added first (not last) and ensure that no other command is executed before it. + +Since this is the only case known where this would be necessary, it could be enough to add a "pending auth change" pointer in client's protocol object which would be checked before thread returns to epoll_wait after completing the authentication. +Comment 1 Massimiliano 2014-11-07 17:01:29 UTC +Current code in develop branch let COM_CHANGE_USER work fine. + +I noticed sometime a failed authentication issue using only. +This because backend protocol's state is not yet MYSQL_AUTH_RECV and necessary data for succesfull backend change user (such as scramble data from handshake) may be not available. + + +I put a query before change_user and the issue doesn't appear: that's another proof. +Comment 2 Vilho Raatikka 2014-11-13 15:54:15 UTC +In gw_change_user->gw_send_change_user_to_backend authentication message was sent to backend server before backend had its scramble data. That caused authentication to fail. +Comment 3 Vilho Raatikka 2014-11-13 15:58:34 UTC +if (func.auth ==)gw_change_user->gw_send_change_user_to_backend is called before backend has its scramble, auth packet is set to backend's delauqueue instead of writing it to backend. When backend_write_delayqueue is called COM_CHANGE_USER packets are rewritten with backend's current data. +*/ + + +#include +#include "testconnections.h" + +using namespace std; + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; + +TestConnections * Test ; + +void *parall_traffic( void *ptr ); + + +int main(int argc, char *argv[]) +{ + int iterations = 1000; + Test = new TestConnections(argc, argv); + if (Test->smoke) + { + iterations = 100; + } + + + pthread_t parall_traffic1[100]; + int check_iret[100]; + + Test->set_timeout(60); + Test->repl->connect(); + Test->repl->execute_query_all_nodes((char *) "set global max_connect_errors=1000;"); + Test->repl->execute_query_all_nodes((char *) "set global max_connections=1000;"); + + Test->connect_maxscale(); + Test->tprintf("Creating one user 'user@%%'"); + execute_query_silent(Test->conn_rwsplit, (char *) "DROP USER user@'%'"); + Test->try_query(Test->conn_rwsplit, (char *) "CREATE USER user@'%%' identified by 'pass2'"); + Test->try_query(Test->conn_rwsplit, (char *) "GRANT SELECT ON test.* TO user@'%%';"); + Test->try_query(Test->conn_rwsplit, (char *) "FLUSH PRIVILEGES;"); + + Test->tprintf("Starting parallel thread which opens/closes session in the loop"); + + for (int j = 0; j < 25; j++) + { + check_iret[j] = pthread_create(¶ll_traffic1[j], NULL, parall_traffic, NULL); + } + + Test->tprintf("Doing change_user in the loop"); + for (int i = 0; i < iterations; i++) + { + Test->set_timeout(15); + Test->add_result(mysql_change_user(Test->conn_rwsplit, "user", "pass2", (char *) "test"), + "change_user failed! %", mysql_error(Test->conn_rwsplit)); + Test->add_result(mysql_change_user(Test->conn_rwsplit, Test->maxscale_user, Test->maxscale_password, + (char *) "test"), "change_user failed! %s", mysql_error(Test->conn_rwsplit)); + } + + Test->tprintf("Waiting for all threads to finish"); + exit_flag = 1; + for (int j = 0; j < 25; j++) + { + Test->set_timeout(30); + pthread_join(parall_traffic1[j], NULL); + } + Test->tprintf("All threads are finished"); + Test->repl->flush_hosts(); + + Test->tprintf("Change user to '%s' in order to be able to DROP user", Test->maxscale_user); + Test->set_timeout(30); + mysql_change_user(Test->conn_rwsplit, Test->maxscale_user, Test->maxscale_password, NULL); + + Test->tprintf("Dropping user", Test->maxscale_user); + Test->try_query(Test->conn_rwsplit, (char *) "DROP USER user@'%%';"); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *parall_traffic( void *ptr ) +{ + MYSQL * conn; + while (exit_flag == 0) + { + conn = Test->open_rwsplit_connection(); + mysql_close(conn); + if (Test->backend_ssl) + { + sleep(1); + } + } + return NULL; +} + diff --git a/maxscale-system-test/bug620.cpp b/maxscale-system-test/bug620.cpp new file mode 100644 index 000000000..e321504f5 --- /dev/null +++ b/maxscale-system-test/bug620.cpp @@ -0,0 +1,268 @@ +/** + * @file bug620.cpp bug620 regression case ("enable_root_user=true generates errors to error log") + * + * - Maxscale.cnf contains RWSplit router definition with enable_root_user=true + * - GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'skysqlroot'; + * - try to connect using 'root' user and execute some query + * - errors are not expected in the log. All Maxscale services should be alive. + */ + +/* +Vilho Raatikka 2014-11-14 09:03:59 UTC +Enabling use of root user in MaxScale causes the following being printed to error log. Disabling the setting enable_root_user prevents these errors. + +2014-11-14 11:02:47 Error : getaddrinfo failed for [linux-yxkl.site] due [Name or service not known] +2014-11-14 11:02:47 140635119954176 [mysql_users_add()] Failed adding user root@linux-yxkl.site for service [RW Split Router] +2014-11-14 11:02:47 Error : getaddrinfo failed for [::1] due [Address family for hostname not supported] +2014-11-14 11:02:47 140635119954176 [mysql_users_add()] Failed adding user root@::1 for service [RW Split Router] +2014-11-14 11:02:47 140635119954176 [mysql_users_add()] Failed adding user root@127.0.0.1 for service [RW Split Router] +Comment 1 Vilho Raatikka 2014-11-14 09:04:40 UTC +This appears with binary built from develop branch 14.11.14 +Comment 2 Massimiliano 2014-11-14 09:15:27 UTC +The messages appear in the log because root user has an invalid hostname: linux-yxkl.site + + +The second message root@127.0.0.1 may be related to a previous root@localhost entry. + + +Names are resolved to IPs and added into maxscale hashtable: localhost and 127.0.0.1 result in a duplicated entry + + + +A standard root@localhost only entry doesn't cause any logged message +Comment 3 Vilho Raatikka 2014-11-14 09:24:56 UTC +Problem is that they seem critical errors but MaxScale still works like nothing had happened. If the default hostname of the server host is not good, what does it mean for MaxScale? Doest it still accept root user or not? Why it only causes trouble for root user but not for others? + +If the error has no effect in practice, then log entries could be better in debug log. + +Thread ids are suitable in debug log but not anywhere else. +Comment 4 Massimiliano 2014-11-14 09:32:27 UTC +The 'enable_root_user' option only allows selecting 'root' user from backend databases. + + +The error messages are printed for all users and report that + + specific_user@specific_host is not loaded but + + +Example: + +2014-11-14 11:02:47 Error : getaddrinfo failed for [linux-yxkl.site] due [Name or service not known] +2014-11-14 11:02:47 140635119954176 [mysql_users_add()] Failed adding user root@linux-yxkl.site for service [RW Split Router] + +2014-11-14 04:19:23 Error : getaddrinfo failed for [ftp.*.fi] due [Name or service not known] +2014-11-14 04:19:23 67322400 [mysql_users_add()] Failed adding user foo@ftp.*.fi for service [Master Service] + + + +In the examples foo@%.funet.fi and root@linux-yxkl.site are not loaded. + + +foo@localhost and root@localhost are loaded +Comment 5 Vilho Raatikka 2014-11-14 10:55:35 UTC +(In reply to comment #4) +> The 'enable_root_user' option only allows selecting 'root' user from backend +> databases. + +I think that enable_root_user means : MaxScale user can use her 'root' account also with MaxScale. + +Technically your explanation may be correct and I'm not against that. What I mean is that the user may not want to worry about what is 'loaded' or 'selected' under the cover. +She simply wants to use root user. If it is not possible then that should be written to error log clearly. For example, "Use of 'root' disabled due to unreachable hostname" or something equally clear. + +Reporting several lines of errors may confuse the user especially if the root account still works perfectly. + +> +> +> The error messages are printed for all users and report that +> +> specific_user@specific_host is not loaded but +> +> +> Example: +> +> 2014-11-14 11:02:47 Error : getaddrinfo failed for [linux-yxkl.site] due +> [Name or service not known] +> 2014-11-14 11:02:47 140635119954176 [mysql_users_add()] Failed adding user +> root@linux-yxkl.site for service [RW Split Router] +> +> 2014-11-14 04:19:23 Error : getaddrinfo failed for [ftp.*.fi] due [Name or +> service not known] +> 2014-11-14 04:19:23 67322400 [mysql_users_add()] Failed adding user +> foo@ftp.*.fi for service [Master Service] +> +> +> +> In the examples foo@%.funet.fi and root@linux-yxkl.site are not loaded. +> +> +> foo@localhost and root@localhost are loaded +Comment 6 Massimiliano 2014-11-14 11:00:04 UTC +MaxScale MySQL authentication is based on user@host + + +You may have such situation: + +foo@localhost [OK] +foo@x-y-z.notexists [KO] + +user 'foo@localhost' is loaded the latter isn't + + +For root user is the same. + +Allowing selection of root user means selecting all the rows from mysql.user table where user='root' + + +if there is any row (root@xxxx) that cannot be loaded the message appears. + +In a standard setup we don't expect any log messages +Comment 7 Timofey Turenko 2014-12-10 16:02:36 UTC +I tried following: + +via RWSplit: + +GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'skysqlroot'; + +and try to connect to RWSplit using 'root' and 'skysqlroot' and try some simple query: + +2014-12-10 17:35:43 Error : getaddrinfo failed for [::1] due [Address family for hostname not supported] +2014-12-10 17:35:43 Warning: Failed to add user root@::1 for service [RW Split Router]. This user will be unavailable via MaxScale. +2014-12-10 17:35:43 Warning: Failed to add user root@127.0.0.1 for service [RW Split Router]. This user will be unavailable via MaxScale. +2014-12-10 17:35:43 Error : Failed to start router for service 'HTTPD Router'. +2014-12-10 17:35:43 Error : Failed to start service 'HTTPD Router'. +2014-12-10 17:36:08 Error : getaddrinfo failed for [::1] due [Address family for hostname not supported] +2014-12-10 17:36:08 Warning: Failed to add user root@::1 for service [RW Split Router]. This user will be unavailable via MaxScale. +2014-12-10 17:36:08 Warning: Failed to add user root@127.0.0.1 for service [RW Split Router]. This user will be unavailable via MaxScale. + + +Is it expected? +Comment 8 Massimiliano 2014-12-10 16:09:23 UTC +root@::1 could not be loaded because it's for IPv6 + +root@127.0.0.1 may be not loaded if root@localhost was found before + +As names are translated to IPv4 addresses localhost->127.0.0.1 and that'a duplicate + + +2014-12-10 17:35:43 Error : Failed to start router for service 'HTTPD Router'. +2014-12-10 17:35:43 Error : Failed to start service 'HTTPD Router'. + +Those messages are not part of mysql users load phase. + +when you have auth errors users are reload (in the allowed time window) and you see the messages again + + +With admin interface you can check: + + +show dbusers RW Split Router + +and you should see root@% you added with the grant +Comment 9 Timofey Turenko 2014-12-12 21:59:30 UTC +Following is present in the error log just after MaxScale start: + + +2014-12-12 23:49:07 Error : getaddrinfo failed for [::1] due [Address family for hostname not supported] +2014-12-12 23:49:07 Warning: Failed to add user root@::1 for service [RW Split Router]. This user will be unavailable via MaxScale. +2014-12-12 23:49:07 Warning: Failed to add user root@127.0.0.1 for service [RW Split Router]. This user will be unavailable via MaxScale. + + +first two line are clear: no support for IPv6, but would it be better to print 'warning' instead of 'error'? + +"Failed to add user root@127.0.0.1" - is it correct? + +direct connection to backend gives: +MariaDB [(none)]> select User, host from mysql.user; ++---------+-----------+ +| User | host | ++---------+-----------+ +| maxuser | % | +| repl | % | +| skysql | % | +| root | 127.0.0.1 | +| root | ::1 | +| | localhost | +| maxuser | localhost | +| root | localhost | +| skysql | localhost | +| | node1 | +| root | node1 | ++---------+-----------+ + +admin interface gives: + +MaxScale> show dbusers "RW Split Router" +Users table data +Hashtable: 0x7f6b64000c30, size 52 + No. of entries: 7 + Average chain length: 0.1 + Longest chain length: 1 +User names: root@192.168.122.106, repl@%, skysql@%, maxuser@127.0.0.1, skysql@127.0.0.1, root@127.0.0.1, maxuser@% + + +So, root@127.0.0.1 is present in the list. +Comment 10 Mark Riddoch 2015-01-05 13:03:34 UTC +The message "Failed to add user root@127.0.0.1" is because the two entries root@localhsot and root@127.0.0.1 are seen as duplicates in MaxScale. This is a result of MaxScale resolving hostnames at the time it reads the database rather than at connect time. So a duplicate is detected and the second one causes the error to be displayed. +Comment 11 Timofey Turenko 2015-01-09 19:26:35 UTC +works as expected, closing. +Check for lack of "Error : getaddrinfo failed" added (just in case) and for warning about 'skysql' +*/ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->connect_maxscale(); + + Test->tprintf("Creating 'root'@'%%'\n"); + //global_result += execute_query(Test->conn_rwsplit, (char *) "CREATE USER 'root'@'%'; SET PASSWORD FOR 'root'@'%' = PASSWORD('skysqlroot');"); + + Test->try_query(Test->conn_rwsplit, + (char *) "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%%' IDENTIFIED BY 'skysqlroot';"); + Test->try_query(Test->conn_rwsplit, + (char *) "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY 'skysqlroot';"); + sleep(10); + + MYSQL * conn; + + Test->tprintf("Connecting using 'root'@'%%'\n"); + conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "root", (char *) "skysqlroot", Test->ssl); + if (mysql_errno(conn) != 0) + { + Test->add_result(1, "Connection using 'root' user failed, error: %s\n", mysql_error(conn)); + } + else + { + Test->tprintf("Simple query...\n"); + Test->try_query(conn, (char *) "SELECT * from mysql.user"); + Test->try_query(conn, + (char *) "set password for 'root'@'localhost' = PASSWORD('');"); + } + if (conn != NULL) + { + mysql_close(conn); + } + + Test->tprintf("Dropping 'root'@'%%'\n"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP USER 'root'@'%%';"); + + Test->close_maxscale_connections(); + + Test->check_log_err((char *) "Failed to add user skysql", false); + Test->check_log_err((char *) "getaddrinfo failed", false); + Test->check_log_err((char *) "Couldn't find suitable Master", false); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug626.cpp b/maxscale-system-test/bug626.cpp new file mode 100644 index 000000000..a7bd23fa7 --- /dev/null +++ b/maxscale-system-test/bug626.cpp @@ -0,0 +1,181 @@ +/** + * @file bug626.cpp regression case for bug 626 ("Crash when user define with old password style (before 4.1 protocol)"), also checks error message in the log for bug428 ("Pre MySQL 4.1 encrypted passwords cause authorization failure") + * + * - CREATE USER 'old'@'%' IDENTIFIED BY 'old'; + * - SET PASSWORD FOR 'old'@'%' = OLD_PASSWORD('old'); + * - try to connect using user 'old' + * - check log for "MaxScale does not support these old passwords" warning + * - DROP USER 'old'@'%' + * - check MaxScale is alive + */ + +/* +Stephane VAROQUI 2014-11-25 17:37:58 UTC +2014-11-21 16:24:03 Error : Invalid authentication message from backend. Error code: 1129, Msg : Host '192.168.42.172' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' +2014-11-21 16:24:03 Error : access for secrets file [/usr/local/maxscale/maxscale-1.0.1-beta/etc/.secrets] failed. Error 2, No such file or directory. +2014-11-21 16:24:03 Error : Unable to get user data from backend database for service RW Split Router. Missing server information. +2014-11-21 16:24:03 Error : Unable to write to backend due to authentication failure. +2014-11-21 16:24:03 Error : Invalid authentication message from backend. Error code: 1129, Msg : Host '192.168.42.172' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts' +2014-11-21 16:24:03 Error : access for secrets file [/usr/local/maxscale/maxscale-1.0.1-beta/etc/.secrets] failed. Error 2, No such file or directory. +2014-11-21 16:24:03 Error : Unable to get user data from backend database for service RW Split Router. Missing server information. +2014-11-21 16:24:03 Error : Unable to write to backend due to authentication failure. +2014-11-21 16:24:03 Fatal: MaxScale received fatal signal 11. Attempting backtrace. +2014-11-21 16:24:03 ./maxscale() [0x53ad1c] + +2014-11-21 16:24:03 /usr/lib64/libpthread.so.0(+0xf6d0) [0x7fd8039756d0] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libreadwritesplit.so(is_read_tmp_table+0x64) [0x7fd7ec0f9d25] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libreadwritesplit.so(+0x5577) [0x7fd7ec0fa577] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libMySQLClient.so(+0x5821) [0x7fd7ea1a1821] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libMySQLClient.so(+0x49df) [0x7fd7ea1a09df] + +2014-11-21 16:24:03 ./maxscale() [0x547093] + +2014-11-21 16:24:03 ./maxscale(poll_waitevents+0xbd) [0x546858] + +2014-11-21 16:24:03 ./maxscale(main+0x12b2) [0x53cfd2] + +2014-11-21 16:24:03 /usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7fd8035c9d65] + +2014-11-21 16:24:03 ./maxscale() [0x539fdd] +Comment 1 Mark Riddoch 2014-11-25 17:57:20 UTC +Vilho, + +looks like multiple issues here, the missing authentication data is one problem, but the SEGFAULT appears to occur in the Read/Write Splitter + +2014-11-21 16:24:03 Fatal: MaxScale received fatal signal 11. Attempting backtrace. +2014-11-21 16:24:03 ./maxscale() [0x53ad1c] + +2014-11-21 16:24:03 /usr/lib64/libpthread.so.0(+0xf6d0) [0x7fd8039756d0] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libreadwritesplit.so(is_read_tmp_table+0x64) [0x7fd7ec0f9d25] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libreadwritesplit.so(+0x5577) [0x7fd7ec0fa577] + +2014-11-21 16:24:03 /usr/local/maxscale/maxscale-1.0.1-beta/modules/libMySQLClient.so(+0x5821) [0x7fd7ea1a1821] + +Massimiliano 2014-12-01 17:29:26 UTC +I have found an easy way to produce the "Host xxxx is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'" error + + +Connect directly to mysql backend(s): + +# mysql -h 127.0.0.1 -P 3310 +MariaDB [(none)]> set global max_connect_errors=1; +Query OK, 0 rows affected (0.00 sec) + +... + +# nc 127.0.0.1 3310 +] +5.5.5-10.0.11-MariaDB-log??A[(SHQ>$???6$PEI"ilc+L{mysql_native_password + +Ctrl-C + +Next attempt results in: + + # nc 127.0.0.1 3310 +j?iHost '151.20.6.153' is blocked because of many connection errors; unblock with 'mysqladmin flush-hosts'[root@mcentos62 ~] + + +Then I restored error count: + +# mysqladmin -h 127.0.0.1 -P 3310 flush-hosts + + + +I launched a mysqlslap test against MaxScale, and after a few seconds I caused the error as described above and ... + +[root@mcentos62 ~]# mysqlslap -h 127.0.0.1 -P 4008 -umassi -pmassi --query="select 1" --concurrency=16 --iterations=200 +mysqlslap: Cannot run query select 1 ERROR : Authentication with backend failed. Session will be closed. + +But no crash at all, this with GA branch and maxscale-1.0.1-beta RPMs. + + +Some details about the error itself. + +http://dev.mysql.com/doc/refman/5.0/en/blocked-host.html + +B.5.2.6 Host 'host_name' is blocked +If the following error occurs, it means that mysqld has received many connection requests from the given host that were interrupted in the middle: + +... + +By default, mysqld blocks a host after 10 connection errors. + + +------------------------------------ + +I also tried with with gdb and MaxScale binary, with a breakpoint set to the gw_create_backend_connection routine. + +Once the breakpoint is reached I did Ctrl-C int the mysql client (connected to MaxScale) and this caused the error too in the next connection. + +As the client stopped running, MaxScale cannot continue with async backend connection and this may increase the error counter: this may be a good example looking for any possible incomplete backend authentication due to a potential bug. + + +Note, using a value as high as 10000 for max_connect_errors doesn't result in any issue of course. + + +BTW, in my today setup even having backends with max_connect_errors=1 doesn't result in any issue at all. + +I run a test with MaxScale on a Virtual CentOS 6.2 on my laptop and backends in a Digital Ocean server, so with the Internet in the middle. +Comment 9 Massimiliano 2014-12-03 10:04:49 UTC +Reported segfault is related to is_read_tmp_table() routine. + + +"many connection errors" not spotted yet during MaxScale tests + + +A new setup is highly desiderable, it should happen in a few days. +Comment 10 Massimiliano 2014-12-03 15:48:55 UTC +No issues/crea found with user and old_password style. + +Message is logged into the error log where there is such case. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + printf("Creating user with old style password\n"); + Test->repl->connect(); + execute_query(Test->repl->nodes[0], "CREATE USER 'old'@'%%' IDENTIFIED BY 'old';"); + execute_query(Test->repl->nodes[0], "SET PASSWORD FOR 'old'@'%%' = OLD_PASSWORD('old');"); + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->set_timeout(20); + printf("Trying to connect using user with old style password\n"); + MYSQL * conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "old", (char *) "old", Test->ssl); + + if ( mysql_errno(conn) != 0) + { + Test->tprintf("Connections is not open as expected\n"); + } + else + { + Test->add_result(1, "Connections is open for the user with old style password.\n"); + } + if (conn != NULL) + { + mysql_close(conn); + } + + execute_query(Test->repl->nodes[0], "DROP USER 'old'@'%%'"); + + Test->check_log_err((char *) "MaxScale does not support these old passwords", true); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug634.cpp b/maxscale-system-test/bug634.cpp new file mode 100644 index 000000000..877f6f8ef --- /dev/null +++ b/maxscale-system-test/bug634.cpp @@ -0,0 +1,42 @@ +/** + * @file bug634.cpp regression case for bug 634 ("SHOW SLAVE STATUS in RW SPLITTER is send to master") + * + * - execute SHOW SLAVE STATUS and check resut + */ + +/* + +Description Stephane VAROQUI 2014-12-03 10:41:30 UTC +SHOW SLAVE STATUS in RW SPLITTER is send to master ? That could break some monitoring scripts for generic proxy abstraction . +Comment 1 Vilho Raatikka 2014-12-03 11:10:12 UTC +COM_SHOW_SLAVE_STAT was unknown to query classifier. Being fixed. +Comment 2 Vilho Raatikka 2014-12-03 11:26:17 UTC +COM_SHOW_SLAVE_STAT wasn't classified but it was treated as 'unknown' and thus routed to master. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(5); + + char master_ip[100]; + + Test->connect_maxscale(); + + for (int i = 0; i < 100; i++) + { + Test->set_timeout(5); + Test->add_result(find_field(Test->conn_rwsplit, (char *) "SHOW SLAVE STATUS", (char *) "Master_Host", + master_ip), "Master_host files is not found in the SHOW SLAVE STATUS reply, probably query went to master\n"); + Test->add_result(strcmp(master_ip, Test->repl->IP_private[0]), "Master IP is wrong\n"); + } + + Test->close_maxscale_connections(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug643.cpp b/maxscale-system-test/bug643.cpp new file mode 100644 index 000000000..6a3974882 --- /dev/null +++ b/maxscale-system-test/bug643.cpp @@ -0,0 +1,64 @@ +/** + * @file bug643.cpp regression case for bugs 643 ("Hints, RWSplit: MaxScale goes into infinite loop and crashes") and bug645 + * - setup RWSplit in the following way for bug643 + * @verbatim +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +user=skysql +passwd=skysql +filters=duplicate + +[duplicate] +type=filter +module=tee +service=RW Split Router + @endverbatim + * - try to connect + * - try simple query using ReadConn router (both, master and slave) + * - check warnig in the log "RW Split Router: Recursive use of tee filter in service" + */ + +/* +Mark Riddoch 2014-12-11 11:59:19 UTC +There is a recursive use of the tee filter in the configuration. + +The "RW Split Router" uses the"duplicate" filter that will then duplicate all traffic to the original destination and another copy of the "RW Split Router", which again will duplicate all traffic to the original destination and another copy of the "RW Split Router"... + +Really this needs to be trapped as a configuration error. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->tprintf("Trying to connect to all Maxscale services\n"); + fflush(stdout); + Test->connect_maxscale(); + Test->tprintf("Trying to send query to ReadConn master\n"); + fflush(stdout); + Test->try_query(Test->conn_master, (char *) "show processlist"); + Test->tprintf("Trying to send query to ReadConn slave\n"); + fflush(stdout); + Test->try_query(Test->conn_slave, (char *) "show processlist"); + Test->tprintf("Trying to send query to RWSplit, expecting failure\n"); + fflush(stdout); + if (execute_query(Test->conn_rwsplit, (char *) "show processlist") == 0) + { + Test->add_result(1, "FAIL: Query to broken service succeeded!\n"); + } + Test->close_maxscale_connections(); + Test->check_log_err((char *) "RW-Split-Router: Recursive use of tee filter in service", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug643_1.cpp b/maxscale-system-test/bug643_1.cpp new file mode 100644 index 000000000..0cc2aa7d5 --- /dev/null +++ b/maxscale-system-test/bug643_1.cpp @@ -0,0 +1,96 @@ +/** + * @file bug643.cpp regression case for bugs 643 ("Hints, RWSplit: MaxScale goes into infinite loop and crashes") and bug645 + * - setup RWSplit in the following way for bug643 + * @verbatim + [hints] +type=filter +module=hintfilter + + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[qla] +type=filter +module=qlafilter +options=/tmp/QueryLog + +[duplicate] +type=filter +module=tee +service=RW Split2 + +[testfilter] +type=filter +module=foobar + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +#servers=server1,server2 +max_slave_connections=100% +use_sql_variables_in=all +#use_sql_variables_in=master +user=skysql +passwd=skysql +#filters=typo|qla|regex|hints|regex|hints +#enable_root_user=1 +filters=duplicate + +[RW Split2] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +user=skysql +passwd=skysql +filters=qla|tests|hints + + @endverbatim + * - try to connect + * - try simple query using all services + * - check warnig in the log "Failed to start service 'RW Split2" + * - check if Maxscale still alive + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->tprintf("Trying to connect to all Maxscale services\n"); + fflush(stdout); + Test->connect_maxscale(); + Test->tprintf("Trying to send query to RWSplit\n"); + fflush(stdout); + execute_query(Test->conn_rwsplit, (char *) "show processlist"); + Test->tprintf("Trying to send query to ReadConn master\n"); + fflush(stdout); + execute_query(Test->conn_master, (char *) "show processlist"); + Test->tprintf("Trying to send query to ReadConn slave\n"); + fflush(stdout); + execute_query(Test->conn_slave, (char *) "show processlist"); + Test->close_maxscale_connections(); + + Test->check_log_err((char *) "Unable to find filter 'tests' for service 'RW Split2'", true); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug645.cpp b/maxscale-system-test/bug645.cpp new file mode 100644 index 000000000..9d5062c10 --- /dev/null +++ b/maxscale-system-test/bug645.cpp @@ -0,0 +1,213 @@ +/** + * @file bug643.cpp regression case for bugs 645 ("Tee filter with readwritesplit service hangs MaxScale") + * - setup RWSplit in the following way + * @verbatim +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=skysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server1, server3,server2 +user=skysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + + @endverbatim + * - try to connect + * - try simple query + * - check MaxScale is alive + */ + +/* +Massimiliano 2014-12-11 14:19:51 UTC +When tee filter is used with a readwritesplit service MaxScale hangs (each service including admin interface)or there is a failed assetion in Debug mode: + +debug assert /source/GA/server/modules/routing/readwritesplit/readwritesplit.c:1825 +maxscale: /source/GA/server/modules/routing/readwritesplit/readwritesplit.c:1825: routeQuery: Assertion `!(querybuf->gwbuf_type == 0)' failed. + + +Configuration: + + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=massi +passwd=massi +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=massi +passwd=massi + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4606 + + +Accessing the RW_listener: + +mysql -h 127.0.0.1 -P 4606 -umassi -pmassi + + + +Debug version: + +2014-12-11 08:48:48 Fatal: MaxScale received fatal signal 6. Attempting backtrace. +2014-12-11 08:48:48 ./maxscale() [0x53c80e] +2014-12-11 08:48:48 /lib64/libpthread.so.0(+0xf710) [0x7fd418a62710] +2014-12-11 08:48:48 /lib64/libc.so.6(gsignal+0x35) [0x7fd417318925] +2014-12-11 08:48:48 /lib64/libc.so.6(abort+0x175) [0x7fd41731a105] +2014-12-11 08:48:48 /lib64/libc.so.6(+0x2ba4e) [0x7fd417311a4e] +2014-12-11 08:48:48 /lib64/libc.so.6(__assert_perror_fail+0) [0x7fd417311b10] +2014-12-11 08:48:48 /usr/local/skysql/maxscale/modules/libreadwritesplit.so(+0x69ca) [0x7fd4142789ca] +2014-12-11 08:48:48 /usr/local/skysql/maxscale/modules/libtee.so(+0x3707) [0x7fd3fc2db707] +2014-12-11 08:48:48 /usr/local/skysql/maxscale/modules/libMySQLClient.so(+0x595d) [0x7fd3fe34b95d] +2014-12-11 08:48:48 ./maxscale() [0x54d3ec] +2014-12-11 08:48:48 ./maxscale(poll_waitevents+0x63d) [0x54ca8a] +2014-12-11 08:48:48 ./maxscale(main+0x1acc) [0x53f616] +2014-12-11 08:48:48 /lib64/libc.so.6(__libc_start_main+0xfd) [0x7fd417304d1d] +2014-12-11 08:48:48 ./maxscale() [0x53a92d] + + +Without debug: + +we got mysql prompt but then maxscale is stucked +or +when don't have the prompt, it hangs after few welcome messages +Comment 1 Vilho Raatikka 2014-12-11 15:14:50 UTC +The assertion occurs because query is is not statement - but packet type. That is, it was sent to read connection router which doesn't examine MySQL packets except the header. Thus, the type of query is not set in mysql_client.c:gw_read_client_event: +>>> + if (cap == 0 || (cap == RCAP_TYPE_PACKET_INPUT)) + { + stmt_input = false; + } + else if (cap == RCAP_TYPE_STMT_INPUT) + { + stmt_input = true; + + gwbuf_set_type(read_buffer, GWBUF_TYPE_MYSQL); + } +>>> +Comment 2 Massimiliano 2014-12-11 16:00:52 UTC +Using readconnroute (with router_options=master) instead seems fine. + +I found that "USE dbname" is not passed via tee filter: + + +4606 is the listener to a service with tee filter + +root@maxscale-02 build]# mysql -h 127.0.0.1 -P 4606 -u massi -pmassi + + + +USE test; SELECT DATABASE() +client to maxscale: + +T 127.0.0.1:40440 -> 127.0.0.1:4606 [AP] + 05 00 00 00 02 74 65 73 74 .....test + +T 127.0.0.1:4606 -> 127.0.0.1:40440 [AP] + 07 00 00 01 00 00 00 02 00 00 00 ........... + +T 127.0.0.1:40440 -> 127.0.0.1:4606 [AP] + 12 00 00 00 03 53 45 4c 45 43 54 20 44 41 54 41 .....SELECT DATA + 42 41 53 45 28 29 BASE() + +T 127.0.0.1:4606 -> 127.0.0.1:40440 [AP] + 01 00 00 01 01 20 00 00 02 03 64 65 66 00 00 00 ..... ....def... + 0a 44 41 54 41 42 41 53 45 28 29 00 0c 08 00 22 .DATABASE()...." + 00 00 00 fd 00 00 1f 00 00 05 00 00 03 fe 00 00 ................ + 02 00 05 00 00 04 04 74 65 73 74 05 00 00 05 fe .......test..... + 00 00 02 00 .... + +maxscale to backend: + + +T 127.0.0.1:56578 -> 127.0.0.1:3308 [AP] + 12 00 00 00 03 53 45 4c 45 43 54 20 44 41 54 41 .....SELECT DATA + 42 41 53 45 28 29 BASE() + +T 127.0.0.1:3308 -> 127.0.0.1:56578 [AP] + 01 00 00 01 01 20 00 00 02 03 64 65 66 00 00 00 ..... ....def... + 0a 44 41 54 41 42 41 53 45 28 29 00 0c 08 00 22 .DATABASE()...." + 00 00 00 fd 00 00 1f 00 00 05 00 00 03 fe 00 00 ................ + 02 00 01 00 00 04 fb 05 00 00 05 fe 00 00 02 00 ................ + + +USE test was not sent + + + +May be a similar issue is present with readwritesplit but I cannot test it +Comment 3 Vilho Raatikka 2014-12-11 16:35:46 UTC +(In reply to comment #2) +> Using readconnroute (with router_options=master) instead seems fine. + +Using readconnroute _where_? in tee? +Comment 4 Vilho Raatikka 2014-12-12 08:27:41 UTC +gwbuf_type is not set and that is the immediate cause for assertion with Debug version. +Reason why the type is not set is in the way the packets are first processed in mysql_client.c client protocol module and then passed optionally to filters and router. There is a bug because it is assumed that when client protocol module reads incoming packet it can resolve which router will handle the packet processing. The code doesn't take into account that same packet can be processed by many different routers, like in the case of readconnrouter->tee->readwritesplit. +Another problem is in readwritesplit where it is assumed that it is the first and the only router that will process tha data. So it includes checks that the buffer has correct type. + +Required changes are: +- readwritesplit should check if buffer has no type and in that case, insted of asserting, merge incoming MySQL packet fragments into a single contiguous buffer. +- remove checks which enforce rules which are based on false assumption. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + Test->try_query(Test->conn_master, (char *) "show processlist"); + Test->try_query(Test->conn_slave, (char *) "show processlist"); + Test->try_query(Test->conn_rwsplit, (char *) "show processlist"); + Test->close_maxscale_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug645_1.cpp b/maxscale-system-test/bug645_1.cpp new file mode 100644 index 000000000..26d1a56bc --- /dev/null +++ b/maxscale-system-test/bug645_1.cpp @@ -0,0 +1,104 @@ +/** + * @file bug643.cpp regression case for bugs 645 ("Tee filter with readwritesplit service hangs MaxScale") + * - setup RWSplit in the following way + * @verbatim +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=skysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=skysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=QLA + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=QLA + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + + + @endverbatim + * - try to connect to all services except 4016 + * - try simple query + * - check ReadConn is ok + * - check log for presens of "Couldn't find suitable Master from 2 candidates" errors + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + Test->tprintf("trying query to RWSplit, expecting failure\n"); + if (execute_query(Test->conn_rwsplit, (char *) "show processlist") == 0) + { + Test->add_result(1, "Query is ok, but failue is expected\n"); + } + Test->tprintf("Trying query to ReadConn router master\n"); + Test->try_query(Test->conn_master, (char *) "show processlist"); + Test->tprintf("Trying query to ReadConn router slave\n"); + Test->try_query(Test->conn_slave, (char *) "show processlist"); + + Test->close_maxscale_connections(); + + Test->check_log_err((char *) "Couldn't find suitable Master from 2 candidates", true); + Test->check_log_err((char *) "Creating client session for Tee filter failed. Terminating session.", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug649.cpp b/maxscale-system-test/bug649.cpp new file mode 100644 index 000000000..bfd9bca3f --- /dev/null +++ b/maxscale-system-test/bug649.cpp @@ -0,0 +1,156 @@ +/** + * @file bug649.cpp regression case for bug 649 ("Segfault using RW Splitter") + * @verbatim + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=skysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server1,server3,server2 +user=skysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + + @endverbatim + * - Connect to RWSplit + * - create load on RWSplit (25 threads doing long INSERTs in the loop) + * - block Mariadb server on Master node by Firewall + * - unblock Mariadb server + * - check if Maxscale is alive + * - reconnect and check if query execution is ok + */ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; + +TestConnections * Test ; + +char sql[1000000]; + +void *parall_traffic( void *ptr ); + +int main(int argc, char *argv[]) +{ + int threads_num = 20; + pthread_t parall_traffic1[threads_num]; + int check_iret[threads_num]; + + Test = new TestConnections(argc, argv); + int time_to_run = (Test->smoke) ? 10 : 30; + Test->set_timeout(10); + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + Test->connect_rwsplit(); + + Test->repl->connect(); + Test->tprintf("Drop t1 if exists\n"); + execute_query(Test->repl->nodes[0], "DROP TABLE IF EXISTS t1;"); + Test->tprintf("Create t1\n"); + Test->add_result(create_t1(Test->repl->nodes[0]), "t1 creation Failed\n"); + Test->repl->close_connections(); + + Test->stop_timeout(); + sleep(5); + + create_insert_string(sql, 65000, 1); + Test->tprintf("Creating query threads\n", time_to_run); + for (int j = 0; j < threads_num; j++) + { + Test->set_timeout(20); + check_iret[j] = pthread_create(¶ll_traffic1[j], NULL, parall_traffic, NULL); + } + + Test->stop_timeout(); + Test->tprintf("Waiting %d seconds\n", time_to_run); + sleep(time_to_run); + + Test->tprintf("Setup firewall to block mysql on master\n"); + Test->repl->block_node(0); + fflush(stdout); + + Test->tprintf("Waiting %d seconds\n", time_to_run); + sleep(time_to_run); + + Test->set_timeout(30); + Test->tprintf("Trying query to RWSplit, expecting failure, but not a crash\n"); + if (execute_query_silent(Test->conn_rwsplit, (char *) "show processlist;") == 0) + { + Test->add_result(1, "Failure is expected, but query is ok\n"); + } + + Test->stop_timeout(); + sleep(time_to_run); + + Test->tprintf("Setup firewall back to allow mysql\n"); + Test->repl->unblock_node(0); + fflush(stdout); + Test->stop_timeout(); + sleep(time_to_run); + exit_flag = 1; + for (int i = 0; i < threads_num; i++) + { + Test->set_timeout(30); + pthread_join(parall_traffic1[i], NULL); + Test->tprintf("exit %d\n", i); + } + Test->stop_timeout(); + sleep(5); + + Test->set_timeout(20); + Test->tprintf("Checking Maxscale is alive\n"); + Test->check_maxscale_alive(); + + Test->set_timeout(20); + Test->tprintf("Reconnecting to RWSplit ...\n"); + Test->connect_rwsplit(); + Test->tprintf(" ... and trying query\n"); + Test->try_query(Test->conn_rwsplit, (char *) "show processlist;"); + Test->close_rwsplit(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + +void *parall_traffic( void *ptr ) +{ + MYSQL * conn; + mysql_thread_init(); + conn = Test->open_rwsplit_connection(); + if ((conn != NULL) && (mysql_errno(conn) == 0)) + { + while (exit_flag == 0) + { + execute_query_silent(conn, sql); + fflush(stdout); + } + } + else + { + Test->tprintf("Error opening connection"); + } + + if (conn != NULL ) + { + mysql_close(conn); + } + return NULL; +} diff --git a/maxscale-system-test/bug650.cpp b/maxscale-system-test/bug650.cpp new file mode 100644 index 000000000..94262277b --- /dev/null +++ b/maxscale-system-test/bug650.cpp @@ -0,0 +1,84 @@ +/** + * @file bug650.cpp regression case for bug 650 ("Hints, RWSplit: MaxScale goes into infinite loop and crashes") and bug645 + * - setup RWSplit in the following way + * @verbatim +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=skysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=skysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + + @endverbatim + * - try to connect + * - try simple query using ReadConn router (both, master and slave) + * - check errors in the log + @verbatim + Couldn't find suitable Master from 2 candidates + Failed to create RW_Split session. + Creating client session for Tee filter failed. Terminating session. + Failed to create filter 'DuplicaFilter' for service 'RW_Router' + Setting up filters failed. Terminating session RW_Router + @endverbatim + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->connect_maxscale(); + Test->tprintf("Trying query to ReadConn master\n"); + Test->try_query(Test->conn_master, (char *) "show processlist"); + Test->tprintf("Trying query to ReadConn slave\n"); + Test->try_query(Test->conn_slave, (char *) "show processlist"); + Test->tprintf("Trying query to RWSplit, expecting failure\n"); + if (execute_query(Test->conn_rwsplit, (char *) "show processlist") == 0) + { + Test->add_result(1, "Query is ok, but failure is expected\n"); + } + Test->close_maxscale_connections(); + + Test->tprintf("Checking logs\n"); + + Test->check_log_err((char *) "Couldn't find suitable Master from 2 candidates", true); + Test->check_log_err((char *) "Failed to create new router session for service 'RW_Split'", true); + Test->check_log_err((char *) "Creating client session for Tee filter failed. Terminating session.", true); + Test->check_log_err((char *) "Failed to create filter 'DuplicaFilter' for service 'RW_Router'", true); + Test->check_log_err((char *) "Setting up filters failed. Terminating session RW_Router", true); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug653.cpp b/maxscale-system-test/bug653.cpp new file mode 100644 index 000000000..261eb579a --- /dev/null +++ b/maxscale-system-test/bug653.cpp @@ -0,0 +1,80 @@ +/** + * @file bug653.cpp regression case for bug 653 ("Memory corruption when users with long hostnames that can no the resolved are loaded into MaxScale") + * + * - CREATE USER 'user_with_very_long_hostname'@'very_long_hostname_that_can_not_be_resolved_and_it_probably_caused_crash.com.net.org' IDENTIFIED BY 'old'; + * - try to connect using user 'user_with_very_long_hostname' + * - DROP USER 'user_with_very_long_hostname'@'very_long_hostname_that_can_not_be_resolved_and_it_probably_caused_crash.com.net.org' + * - check MaxScale is alive + */ + +/* +Mark Riddoch 2014-12-16 13:17:25 UTC +Program received signal SIGSEGV, Segmentation fault. +0x00007ffff49385ac in free () from /lib64/libc.so.6 +Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.47.el6_2.12.x86_64 keyutils-libs-1.4-4.el6.x86_64 krb5-libs-1.10.3-10.el6_4.2.x86_64 libaio-0.3.107-10.el6.x86_64 libcom_err-1.41.12-14.el6.x86_64 libgcc-4.4.7-4.el6.x86_64 libselinux-2.0.94-5.3.el6_4.1.x86_64 libstdc++-4.4.7-4.el6.x86_64 nss-pam-ldapd-0.7.5-14.el6_2.1.x86_64 nss-softokn-freebl-3.14.3-10.el6_5.x86_64 openssl-1.0.1e-16.el6_5.15.x86_64 zlib-1.2.3-29.el6.x86_64 +(gdb) where +#0 0x00007ffff49385ac in free () from /lib64/libc.so.6 +#1 0x000000000041d421 in add_mysql_users_with_host_ipv4 (users=0x72c4c0, + user=0x739030 "u3", host=0x739033 "aver.log.hostname.to.overflow.the.buffer", + passwd=0x73905c "", anydb=0x739089 "Y", db=0x0) at dbusers.c:291 +#2 0x000000000041e302 in getUsers (service=0x728ef0, users=0x72c4c0) + at dbusers.c:742 +#3 0x000000000041cf97 in load_mysql_users (service=0x728ef0) at dbusers.c:99 +#4 0x00000000004128c7 in serviceStartPort (service=0x728ef0, port=0x729b70) + at service.c:227 +#5 0x0000000000412e27 in serviceStart (service=0x728ef0) at service.c:365 +#6 0x0000000000412f00 in serviceStartAll () at service.c:413 +#7 0x000000000040b592 in main (argc=2, argv=0x7fffffffe108) at gateway.c:1750 +Comment 1 Mark Riddoch 2014-12-16 13:18:09 UTC +The problem is a buffer overrun in normalise_hostname. Fix underway. +Comment 2 Mark Riddoch 2014-12-16 15:45:59 UTC +Increased buffer size to prevent overrun issue +Comment 3 Timofey Turenko 2014-12-22 15:39:32 UTC +I'm not sure I understand the bug correctly. +But 60-chars long host name does not cause problem (longer is not possible "String 'very_long_hostname_that_can_not_be_resolved_and_it_probably_caused_cra' is too long for host name (should be no longer than 60)" +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(50); + Test->connect_maxscale(); + + Test->tprintf("Creating user with old style password\n"); + Test->try_query(Test->conn_rwsplit, + (char *) "CREATE USER 'user_long_host11'@'very_long_hostname_that_probably_caused_crashhh.com.net.org' IDENTIFIED BY 'old'"); + Test->try_query(Test->conn_rwsplit, + (char *) "GRANT ALL PRIVILEGES ON *.* TO 'user_long_host11'@'very_long_hostname_that_probably_caused_crashhh.com.net.org' WITH GRANT OPTION"); + sleep(10); + + Test->tprintf("Trying to connect using user with old style password\n"); + MYSQL * conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, (char *) "user_long_host11", (char *) "old", + Test->ssl); + + if ( mysql_errno(conn) != 0 ) + { + Test->tprintf("Connections is not open as expected\n"); + } + else + { + Test->add_result(1, "Connections is open for the user with bad host\n"); + } + if (conn != NULL) + { + mysql_close(conn); + } + + Test->try_query(Test->conn_rwsplit, + (char *) "DROP USER 'user_long_host11'@'very_long_hostname_that_probably_caused_crashhh.com.net.org'"); + Test->close_maxscale_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug654.cpp b/maxscale-system-test/bug654.cpp new file mode 100644 index 000000000..4fd192446 --- /dev/null +++ b/maxscale-system-test/bug654.cpp @@ -0,0 +1,202 @@ +/** + * @file bug654.cpp regression case for bug654 abd 698 ("maxadm: show dbusers causes SEGFAULT", "Using invalid parameter in many maxadmin commands causes MaxScale to fail") + * + * - execute maxadmin command show dbusers RW Split Router and show dbusers "RW Split Router" + * . execute different maxadmin commands with wrong parameters + * - check MaxScale is alive + */ + +/* +Vilho Raatikka 2014-12-16 13:54:36 UTC +MaxScale> show services +Service 0x1af7eb0 + Service: RW Split Router + Router: readwritesplit (0x7fffdf501440) + Number of router sessions: 0 + Current no. of router sessions: 0 + Number of queries forwarded: 0 + Number of queries forwarded to master: 0 + Number of queries forwarded to slave: 0 + Number of queries forwarded to all: 0 + Started: Tue Dec 16 15:51:54 2014 + Root user access: Disabled + Filter chain: duplicate + Backend databases + 127.0.0.1:3003 Protocol: MySQLBackend + 127.0.0.1:3002 Protocol: MySQLBackend + 127.0.0.1:3001 Protocol: MySQLBackend + 127.0.0.1:3000 Protocol: MySQLBackend + Users data: 0x1aea000 + Total connections: 1 + Currently connected: 1 + +... + +MaxScale> show dbusers RW Split Router + +(gdb) bt +#0 0x00007fffdfb4950a in execute_cmd (cli=0x7fffc0000c70) at /home/raatikka/src/git/MaxScale/server/modules/routing/debugcmd.c:805 +#1 0x00007fffdfb48ef8 in execute (instance=0x1b0f7b0, router_session=0x7fffc0000c70, queue=0x0) at /home/raatikka/src/git/MaxScale/server/modules/routing/cli.c:279 +#2 0x00007ffff46ae934 in maxscaled_read_event (dcb=0x7fffc00009c0) at /home/raatikka/src/git/MaxScale/server/modules/protocol/maxscaled.c:177 +#3 0x000000000058b145 in process_pollq (thread_id=2) at /home/raatikka/src/git/MaxScale/server/core/poll.c:858 +#4 0x000000000058a7df in poll_waitevents (arg=0x2) at /home/raatikka/src/git/MaxScale/server/core/poll.c:608 +#5 0x00007ffff7527e0f in start_thread () from /lib64/libpthread.so.0 +#6 0x00007ffff5e0e0dd in clone () from /lib64/libc.so.6 +(gdb) +Comment 1 Vilho Raatikka 2014-12-16 13:58:37 UTC +805 for (i = 0; args[i] && *args[i]; i++) + +Off-by-one if there are more arguments than expected. +Comment 2 Vilho Raatikka 2014-12-23 16:11:12 UTC +NULL-terminated argument list in case where there are given more arguments than expected. +*/ + + +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + char result[1024]; + + Test->get_maxadmin_param((char *) "show dbusers RW Split Router", (char *) "Incorrect number of arguments:", + result); + Test->tprintf("result %s\n", result); + + if (strstr(result, "show dbusers expects 1 argument") == NULL) + { + Test->add_result(1, "there is NO \"show dbusers expects 1 argument\" message"); + } + Test->set_timeout(30); + Test->get_maxadmin_param((char *) "show dbusers \"RW Split Router\"", (char *) "User names:", result); + Test->tprintf("result %s\n", result); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "reload dbusers 0x232fed0"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "reload dbusers Хрен"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "reload dbusers Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show Хрен"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show dcb Хрен"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show dcb Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show dcb khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show server Хрен"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show server Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show server khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show service Хрен"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show service Хрен моржовый"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show service khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "show service khren morzhovyj"); + + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "list listeners"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "restart monitor"); + Test->set_timeout(30); + Test->execute_maxadmin_command((char *) "restart service"); + + if (!Test->smoke) + { + int N = 28; + const char * cmd[N]; + + int Ng = 6; + const char * garbage[Ng]; + + garbage[0] = "qwerty"; + garbage[1] = "khren morzhovyj"; + garbage[2] = "Хрен"; + garbage[3] = "Хрен моржовый"; + garbage[4] = + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + garbage[5] = + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Хрен моржовый Хрен моржовый "; + + cmd[0] = "enable disable heartbeat "; + cmd[1] = "disable heartbeat "; + cmd[2] = "reload dbusers "; + + cmd[3] = "set server server1 master "; + + cmd[4] = "set pollsleep "; + cmd[5] = "set nbpolls "; + + cmd[6] = "show dcb "; + cmd[7] = "show eventq "; + cmd[8] = "show eventstats "; + cmd[9] = "show filter "; + cmd[10] = "show monitor "; + cmd[11] = "show server "; + cmd[12] = "show service "; + cmd[13] = "show session "; + + cmd[14] = "show filters "; + cmd[15] = "show modules "; + cmd[16] = "show monitors "; + cmd[17] = "show servers "; + cmd[18] = "show services "; + cmd[19] = "show sessions "; + cmd[20] = "show tasks "; + cmd[21] = "show threads "; + cmd[22] = "show users "; + + cmd[23] = "shutdown monitor "; + cmd[24] = "shutdown service "; + + cmd[25] = "shutdown maxscale "; + + cmd[26] = "enable root "; + cmd[27] = "disable root "; + + char str1[4096]; + int i1, i2; + + for (i1 = 0; i1 < N; i1++) + { + for (i2 = 0; i2 < Ng; i2++) + { + Test->set_timeout(30); + sprintf(str1, "%s %s", cmd[i1], garbage[i2]); + Test->tprintf("Trying '%s'\n", str1); + Test->execute_maxadmin_command(str1); + + sprintf(str1, "%s %s%s%s%s %s ", cmd[i1], garbage[i2], garbage[i2], garbage[i2], garbage[i2], garbage[i2]); + Test->tprintf("Trying '%s'\n", str1); + Test->execute_maxadmin_command(str1); + } + } + } + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug656.cpp b/maxscale-system-test/bug656.cpp new file mode 100644 index 000000000..d27a45aa7 --- /dev/null +++ b/maxscale-system-test/bug656.cpp @@ -0,0 +1,42 @@ +/** + * @file bug656.cpp Checks Maxscale behaviour in case if Master node is blocked - NOT NEEDED BECAUSE IT IS ALREADY CHECKED BY OTHER TESTS!!!! + * + * - ConnecT to RWSplit + * - block Mariadb server on Master node by Firewall + * - try simple query *show servers" via Maxadmin + */ + + +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + Test->connect_rwsplit(); + + Test->tprintf("Setup firewall to block mysql on master\n"); + Test->repl->block_node(0); + + //printf("Trying query to RWSplit, expecting failure, but not a crash\n"); fflush(stdout); + //execute_query(Test->conn_rwsplit, (char *) "show processlist;"); + execute_maxadmin_command_print(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, + (char *) "show servers"); + + Test->tprintf("Setup firewall back to allow mysql and wait\n"); + Test->repl->unblock_node(0); + sleep(10); + + Test->close_rwsplit(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/bug657.cpp b/maxscale-system-test/bug657.cpp new file mode 100644 index 000000000..e6c9ec72f --- /dev/null +++ b/maxscale-system-test/bug657.cpp @@ -0,0 +1,119 @@ +/** + * @file bug657.cpp regression case for bug 657 ("Tee filter: closing child session causes MaxScale to fail") + * + * - Configure readconnrouter with tee filter and tee filter with a readwritesplit as a child service. + * @verbatim +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +#filters=QLA + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=TEE + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=TEE + +[TEE] +type=filter +module=tee +service=RW Split Router +@endverbatim + * - Start MaxScale + * - Connect readconnrouter + * - Fail the master node + * - Reconnect readconnrouter + */ + +/* +Vilho Raatikka 2014-12-22 08:35:52 UTC +How to reproduce: +1. Configure readconnrouter with tee filter and tee filter with a readwritesplit as a child service. +2. Start MaxScale +3. Connect readconnrouter +4. Fail the master node +5. Reconnect readconnrouter + +As a consequence, next routeQuery will be duplicated to closed readwritesplit router and eventually fred memory will be accessed which causes SEGFAULT. + +Reason for this is that situation where child (=branch -) session is closed as a consequence of node failure, is not handled in tee filter. Tee filter handles the case where client closes the session. +Comment 1 Vilho Raatikka 2014-12-22 09:14:13 UTC +Background: client session may be closed for different reasons. If client actively closes it by sending COM_QUIT packet, it happens from top to bottom: packet is identified and client DCB is closed. Client's DCB close routine also closes the client router session. + +If backend fails and monitor detects it, then every DCB that isn't running or isn't master, slave, joined (Galera) nor ndb calls its hangup function. If the failed node was master then client session gets state SESSION_STATE_STOPPING which triggers first closing the client DCB and as a part of it, the whole session. + +In tee filter, the first issue is the client DCB's close routine which doesn't trigger closing the session. The other issue is that if child session gets closed there's no mechanism that would prevent future queries being routed to tee's child service. As a consequence, future calls to routeQuery will access closed child session including freed memory etc. +Comment 2 Vilho Raatikka 2014-12-22 22:32:25 UTC +session.c:session_free:if session is child of another service (tee in this case), it is the parent which releases child's allocated memory back to the system. This now also includes the child router session. + dcb.h: Added DCB_IS_CLONE macro + tee.c:freeSession:if parent session triggered closing of tee, then child session may not be closed yet. In that case free the child session first and only then free child router session and release child session's memory back to system. + tee.c:routeQuery: only route if child session is ready for routing. Log if session is not ready for routing and set tee session inactive + mysql_client.c:gw_client_close:if DCB is cloned one don't close the protocol because they it is shared with the original DCB. +Comment 3 Vilho Raatikka 2014-12-23 10:04:11 UTC +If monitor haven't yet changed the status for failed backend, even the fixed won't notice the failure, and the client is left waiting for reply until some lower level timeout exceeds and closes the socket. + +The solution is to register a callback function to readconnrouter's backend DCB in the same way that it is done in readwritesplit. Callback needs to be implemented and tests added. +By using this mechanism the client must wait at most one monitor interval before the session is closed. + +Vilho Raatikka 2014-12-31 23:19:41 UTC +filter.c:filter_free:if filter parameter is NULL, return. + tee.c:freeSession: if my_session->dummy_filterdef is NULL, don't try to release the memory +*/ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + TestConnections *Test = new TestConnections(argc, argv); + Test->set_timeout(200); + + Test->tprintf("Connecting to ReadConn Master %s\n", Test->maxscale_IP); + Test->connect_readconn_master(); + + sleep(1); + + Test->tprintf("Setup firewall to block mysql on master\n"); + Test->repl->block_node(0); + + sleep(10); + + Test->tprintf("Reconnecting to ReadConnMaster\n"); + Test->close_readconn_master(); + Test->connect_readconn_master(); + + sleep(5); + + Test->repl->unblock_node(0); + sleep(10); + + Test->tprintf("Closing connection\n"); + + Test->close_readconn_master(); + fflush(stdout); + + Test->tprintf("Checking Maxscale is alive\n"); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug658.cpp b/maxscale-system-test/bug658.cpp new file mode 100644 index 000000000..b8c6cced4 --- /dev/null +++ b/maxscale-system-test/bug658.cpp @@ -0,0 +1,111 @@ +/** + * @file bug658.cpp regression case for bug 658 ("readconnroute: client is not closed if backend fails") + * + * - Connect all MaxScale + * - block Mariadb server on Master node by Firewall + * - execute query + * - unblock Mariadb server + * - do same test, but block all backend nodes + * - check if Maxscale is alive + */ + +/* +ilho Raatikka 2014-12-22 22:38:42 UTC +Reproduce: +1. connect readconnroute with mysql client +2. fail the backend server +3. execute query by using mysql client + +>> client hangs if write to backend socket doesn't return error (which doesn't happen in many cases) +Comment 1 Markus Mäkelä 2014-12-23 09:19:17 UTC +Added a check for server status before routing the query. Now if the server is down it returns an error. +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(40); + int i; + + Test->tprintf("Connecting to Maxscale %s\n", Test->maxscale_IP); + Test->connect_maxscale(); + + printf("Setup firewall to block mysql on master\n"); + fflush(stdout); + Test->repl->block_node(0); + + sleep(1); + + Test->tprintf("Trying query to RWSplit, expecting failure, but not a crash\n"); + execute_query(Test->conn_rwsplit, (char *) "show processlist;"); + fflush(stdout); + Test->tprintf("Trying query to ReadConn master, expecting failure, but not a crash\n"); + execute_query(Test->conn_master, (char *) "show processlist;"); + fflush(stdout); + Test->tprintf("Trying query to ReadConn slave, expecting failure, but not a crash\n"); + execute_query(Test->conn_slave, (char *) "show processlist;"); + fflush(stdout); + + sleep(1); + + Test->repl->unblock_node(0); + sleep(10); + + Test->close_maxscale_connections(); + + Test->tprintf("Checking Maxscale is alive\n"); + Test->check_maxscale_alive(); + + Test->set_timeout(20); + + Test->tprintf("Connecting to Maxscale %s to check its behaviour in case of blocking all bacxkends\n", + Test->maxscale_IP); + Test->connect_maxscale(); + + if (!Test->smoke) + { + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Setup firewall to block mysql on node %d\n", i); + Test->repl->block_node(i); + fflush(stdout); + } + sleep(1); + + Test->tprintf("Trying query to RWSplit, expecting failure, but not a crash\n"); + execute_query(Test->conn_rwsplit, (char *) "show processlist;"); + fflush(stdout); + Test->tprintf("Trying query to ReadConn master, expecting failure, but not a crash\n"); + execute_query(Test->conn_master, (char *) "show processlist;"); + fflush(stdout); + Test->tprintf("Trying query to ReadConn slave, expecting failure, but not a crash\n"); + execute_query(Test->conn_slave, (char *) "show processlist;"); + fflush(stdout); + + sleep(1); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Setup firewall back to allow mysql on node %d\n", i); + Test->repl->unblock_node(i); + fflush(stdout); + } + } + Test->stop_timeout(); + Test->tprintf("Sleeping 20 seconds\n"); + sleep(20); + + Test->set_timeout(20); + + Test->close_maxscale_connections(); + Test->tprintf("Checking Maxscale is alive\n"); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug662.cpp b/maxscale-system-test/bug662.cpp new file mode 100644 index 000000000..af7e514f3 --- /dev/null +++ b/maxscale-system-test/bug662.cpp @@ -0,0 +1,79 @@ +/** + * @file bug662.cpp regression case for bug 662 ("MaxScale hangs in startup if backend server is not responsive"), covers also bug680 ("RWSplit can't load DB user if backend is not available at MaxScale start") + * + * - block all Mariadb servers Firewall + * - restart MaxScale + * - check it took no more then 20 seconds + * - unblock Mariadb servers + * - sleep one minute + * - check if Maxscale is alive + */ + +/* +Vilho Raatikka 2014-12-29 08:38:28 UTC +During startup, load_mysql_users tries to read the contents of the mysql.user table. If the chosen backend is not responsive, connection hangs for a long time. +Comment 1 Vilho Raatikka 2014-12-29 11:41:32 UTC +The issue causes long stalls for the executing thread whenever getUsers function is called and one or more backends are not responsive. +Comment 2 Vilho Raatikka 2014-12-29 11:50:10 UTC +dbusers.c: Added function for setting read, write and connection timeout values. Set default timeouts for getUsers. Defaults are listed in service.c + gateway.c:shutdown_server is called whenever MaxScale is to be shut down. Added call for service_shutdown to shutdown_server. + service.c:service_alloc: replaced malloc with calloc and removed unnecessary zero/NULL initialization statements as a consequence. + serviceStart: Exit serviceStartPort loop if shutdown flag is set for the service. + serviceStartAll: Exit serviceStart loop if shutdown flag is set for the service. + service.c: Added service_shutdown which sets shutdown flag for each service found in allServices list. + service.h: Added prototype for service_shutdown +*/ + + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + int i; + + Test->tprintf("Connecting to Maxscale %s\n", Test->maxscale_IP); + + Test->tprintf("Connecting to Maxscale %s to check its behaviour in case of blocking all backends\n", + Test->maxscale_IP); + Test->connect_maxscale(); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Setup firewall to block mysql on node %d\n", i); + Test->repl->block_node(i); + fflush(stdout); + } + + Test->set_timeout(100); + Test->restart_maxscale(); + + Test->set_timeout(20); + Test->tprintf("Checking if MaxScale is alive by connecting to MaxAdmin\n"); + Test->add_result(Test->execute_maxadmin_command((char* ) "show servers"), "Maxadmin execution failed.\n"); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Setup firewall back to allow mysql on node %d\n", i); + Test->repl->unblock_node(i); + fflush(stdout); + } + + Test->stop_timeout(); + Test->tprintf("Sleeping 30 seconds\n"); + sleep(30); + + Test->set_timeout(20); + + Test->tprintf("Checking Maxscale is alive\n"); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; + //} +} diff --git a/maxscale-system-test/bug664.cpp b/maxscale-system-test/bug664.cpp new file mode 100644 index 000000000..ef7c22801 --- /dev/null +++ b/maxscale-system-test/bug664.cpp @@ -0,0 +1,316 @@ +/** + * @file bug664.cpp bug664 regression case ("Core: Access of freed memory in gw_send_authentication_to_backend") + * + * - Maxscale.cnf contains: + * @verbatim +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=maxuser +passwd=maxpwd +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=maxuser +passwd=maxpwd + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd +filters=QLA + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd +filters=QLA + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + + @endverbatim + * - warning is expected in the log, but not an error. All Maxscale services should be alive. + * - Check MaxScale is alive + */ + +/* +Vilho Raatikka 2014-12-29 18:12:23 UTC +All these cases are due to accessing freed dcb->data (MYSQL_session *): + +==12419== Invalid read of size 1 +==12419== at 0x1B1434BA: gw_send_authentication_to_backend (mysql_common.c:544) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690285 is 149 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x1B1434D6: gw_send_authentication_to_backend (mysql_common.c:547) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x186901f0 is 0 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 8 +==12419== at 0x1B1435FC: gw_send_authentication_to_backend (mysql_common.c:572) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x186901f0 is 0 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 8 +==12419== at 0x1B143606: gw_send_authentication_to_backend (mysql_common.c:572) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x186901f8 is 8 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 4 +==12419== at 0x1B143611: gw_send_authentication_to_backend (mysql_common.c:572) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690200 is 16 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCA2: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B143719: gw_send_authentication_to_backend (mysql_common.c:604) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690204 is 20 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCB4: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B143719: gw_send_authentication_to_backend (mysql_common.c:604) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690205 is 21 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCA2: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B143893: gw_send_authentication_to_backend (mysql_common.c:660) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690204 is 20 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCB4: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B143893: gw_send_authentication_to_backend (mysql_common.c:660) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690205 is 21 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2DE21: memcpy@@GLIBC_2.14 (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B1438AF: gw_send_authentication_to_backend (mysql_common.c:660) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x1869020a is 26 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 2 +==12419== at 0x4C2DEA0: memcpy@@GLIBC_2.14 (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B1438AF: gw_send_authentication_to_backend (mysql_common.c:660) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690206 is 22 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCA2: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B1438BE: gw_send_authentication_to_backend (mysql_common.c:661) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690204 is 20 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== +==12419== Invalid read of size 1 +==12419== at 0x4C2CCB4: strlen (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x1B1438BE: gw_send_authentication_to_backend (mysql_common.c:661) +==12419== by 0x1B13F90E: gw_read_backend_event (mysql_backend.c:228) +==12419== by 0x588CA2: process_pollq (poll.c:858) +==12419== by 0x58854B: poll_waitevents (poll.c:608) +==12419== by 0x57C11B: main (gateway.c:1792) +==12419== Address 0x18690205 is 21 bytes inside a block of size 278 free'd +==12419== at 0x4C2AF6C: free (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so) +==12419== by 0x57D806: dcb_final_free (dcb.c:406) +==12419== by 0x57DDE6: dcb_process_zombies (dcb.c:603) +==12419== by 0x588598: poll_waitevents (poll.c:613) +==12419== by 0x57C11B: main (gateway.c:1792) +Comment 1 Vilho Raatikka 2014-12-29 18:29:11 UTC + dcb_final_free:don't free dcb->data, it is either freed in session_alloc if session creation fails or in session_free only. + mysql_client.c:gw_mysql_do_authentication:if anything fails, and session_alloc won't be called, free dcb->data. + mysql_common.c:gw_send_authentication_to_backend:if session is already closing then return with error. +Comment 2 Markus Mäkelä 2014-12-30 08:58:59 UTC +Created attachment 170 [details] +failing configuration + +The attached configuration currently crashes into a debug assert in handleError in readconnroute.c when connecting to port 4006. If this is removed, the next point of failure is in dcb_final_free when the session->data object is freed. +Comment 3 Vilho Raatikka 2014-12-30 10:14:43 UTC +(In reply to comment #2) +> Created attachment 170 [details] +> failing configuration +> +> The attached configuration currently crashes into a debug assert in +> handleError in readconnroute.c when connecting to port 4006. If this is +> removed, the next point of failure is in dcb_final_free when the +> session->data object is freed. + +Should this be open or closed based on the information provided? +Comment 4 Markus Mäkelä 2014-12-30 10:17:55 UTC +My apologies, I thought I did reopen it. +Comment 5 Vilho Raatikka 2014-12-30 10:27:32 UTC +Fixed double freeing dcb->data if authentication phase fails. +Comment 6 Vilho Raatikka 2014-12-30 10:30:12 UTC +Reopen due to crash. Another double free somewhere. +Comment 7 Vilho Raatikka 2014-12-30 11:36:38 UTC +Cloned session was freeing the shared 'data' dcb->data/session->data. Now only session_free for the non-clone session is allowed to free the data. + +*/ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->connect_maxscale(); + + Test->tprintf("Trying query to ReadConn master\n"); + fflush(stdout); + Test->try_query(Test->conn_master, "show processlist;"); + Test->tprintf("Trying query to ReadConn slave\n"); + Test->try_query(Test->conn_slave, "show processlist;"); + + Test->close_maxscale_connections(); + + Test->check_log_err((char *) "Creating client session for Tee filter failed. Terminating session.", true); + Test->check_log_err((char *) "Failed to create filter 'DuplicaFilter' for service 'RW_Router'", true); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bug670.cpp b/maxscale-system-test/bug670.cpp new file mode 100644 index 000000000..dcd68d8a0 --- /dev/null +++ b/maxscale-system-test/bug670.cpp @@ -0,0 +1,267 @@ +/** + * @file bug670.cpp bug670 regression case + * configuration + * @verbatim +[MySQL Monitor] +type=monitor +module=mysqlmon +monitor_interval=10000 +servers=server1,server2,server3,server4 +detect_replication_lag=1 +detect_stale_master=1 +user=maxuser +passwd=maxpwd + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[qla] +type=filter +module=qlafilter +options=/tmp/QueryLog + +[duplicate] +type=filter +module=tee +service=RW Split2 + +[testfilter] +type=filter +module=foobar + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +#servers=server1 +max_slave_connections=100% +use_sql_variables_in=all +#use_sql_variables_in=master +user=maxuser +passwd=maxpwd +filters=typo|qla|regex|hints|regex|hints +enable_root_user=1 + +[RW Split2] +type=service +router=readwritesplit +servers=server1,server2 +max_slave_connections=100% +use_sql_variables_in=all +user=maxuser +passwd=maxpwd +#filters=qla|tests|hints + +[Read Connection Router] +type=service +router=readconnroute +router_options=slave +servers=server1,server2 +user=maxuser +passwd=maxpwd +filters=duplicate + +[HTTPD Router] +type=service +router=testroute +servers=server1,server2,server3 + +[Debug Interface] +type=service +router=debugcli + +[CLI] +type=service +router=cli + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[RW Split Listener2] +type=listener +service=RW Split2 +protocol=MySQLClient +port=4012 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + + @endverbatim + * execute following SQLs against all services in the loop (100 times) + * @verbatim +set autocommit=0; +use mysql; +set autocommit=1; +use test; +set autocommit=0; +use mysql; +set autocommit=1; +select user,host from user; +set autocommit=0; +use fakedb; +use test; +use mysql; +use dontuse; +use mysql; +drop table if exists t1; +commit; +use test; +use mysql; +set autocommit=1; +create table t1(id integer primary key); +insert into t1 values(5); +use test; +use mysql; +select user from user; +set autocommit=0; +set autocommit=1; +set autocommit=0; +insert into mysql.t1 values(7); +use mysql; +rollback work; +commit; +delete from mysql.t1 where id=7; +insert into mysql.t1 values(7); +select host,user from mysql.user; +set autocommit=1; +delete from mysql.t1 where id = 7; +select 1 as "endof cycle" from dual; + @endverbatim + * + * check that Maxscale is alive, no crash + */ + + +/* + +Description Vilho Raatikka 2014-12-30 11:54:52 UTC +Statement router (readwritesplit) loses pending statement if the other router executes statements faster than it. Statement router assumes that client doesn't send next statement before previous has replied. The only supported exception is session command which doesn't need to reply before client may send the next statement. + +What happens in practice, is, that when 'next' statement arrives router's routeQuery it finds previous, still pending statement. In Debug build process traps. In Release build the pending command is overwritten. + +(gdb) bt +#0 0x00007f050fa56065 in raise () from /lib64/libc.so.6 +#1 0x00007f050fa574e8 in abort () from /lib64/libc.so.6 +#2 0x00007f050fa4ef72 in __assert_fail_base () from /lib64/libc.so.6 +#3 0x00007f050fa4f022 in __assert_fail () from /lib64/libc.so.6 +#4 0x00007f050c06ddbb in route_single_stmt (inst=0x25ea750, rses=0x7f04d4002350, querybuf=0x7f04d400f130) + at /home/raatikka/src/git/MaxScale/server/modules/routing/readwritesplit/readwritesplit.c:2372 +#5 0x00007f050c06c2ef in routeQuery (instance=0x25ea750, router_session=0x7f04d4002350, querybuf=0x7f04d400f130) + at /home/raatikka/src/git/MaxScale/server/modules/routing/readwritesplit/readwritesplit.c:1895 +#6 0x00007f04f03ca573 in routeQuery (instance=0x7f04d4001f20, session=0x7f04d4002050, queue=0x7f04d400f130) + at /home/raatikka/src/git/MaxScale/server/modules/filter/tee.c:597 +#7 0x00007f04f8df700a in gw_read_client_event (dcb=0x7f04cc0009c0) + at /home/raatikka/src/git/MaxScale/server/modules/protocol/mysql_client.c:867 +#8 0x000000000058b351 in process_pollq (thread_id=4) at /home/raatikka/src/git/MaxScale/server/core/poll.c:858 +#9 0x000000000058a9eb in poll_waitevents (arg=0x4) at /home/raatikka/src/git/MaxScale/server/core/poll.c:608 +#10 0x00007f0511223e0f in start_thread () from /lib64/libpthread.so.0 +#11 0x00007f050fb0a0dd in clone () from /lib64/libc.so.6 +(gdb) +Comment 1 Vilho Raatikka 2014-12-30 12:04:06 UTC +Created attachment 171 [details] +Configuration. + +1. Start MaxScale +2. feed session command/statement load to port 4008 which belongs to readconnrouter. Statements are then duplicated to rwsplit which starts to lag behind immediately. +Comment 2 Vilho Raatikka 2014-12-30 12:05:26 UTC +Created attachment 172 [details] +Simple script to run session command/statement load + +Requires setmix.sql +Comment 3 Vilho Raatikka 2014-12-30 12:05:49 UTC +Created attachment 173 [details] +List of statements used by run_setmix.sh +Comment 4 Vilho Raatikka 2014-12-31 19:13:21 UTC +tee filter doesn't send reply to client before both routers have replied. This required adding upstream processing to tee filter. First reply is routed to client. By this tee ensures that new query is never sent to either router before they have replied to previous one. +Comment 5 Timofey Turenko 2015-01-08 12:40:34 UTC +test added, closing +Comment 6 Timofey Turenko 2015-02-28 18:11:16 UTC +Reopen: starting from 1.0.5 test for bug670 start to fail with debug assert: + +(gdb) bt +#0 0x00007f542de05625 in raise () from /lib64/libc.so.6 +#1 0x00007f542de06e05 in abort () from /lib64/libc.so.6 +#2 0x00007f542ddfe74e in __assert_fail_base () from /lib64/libc.so.6 +#3 0x00007f542ddfe810 in __assert_fail () from /lib64/libc.so.6 +#4 0x00007f53fe7a2daf in clientReply (instance=0x7f53ec001970, session=0x7f53ec001aa0, reply=0x7f5404000b70) + at /usr/local/skysql/maxscale/server/modules/filter/tee.c:973 +#5 0x00007f54292c1b55 in clientReply (instance=0x335ae20, router_session=0x7f53ec000f90, queue=0x7f5404000b70, + backend_dcb=0x7f53ec000fd0) at /usr/local/skysql/maxscale/server/modules/routing/readconnroute.c:814 +#6 0x00007f54143f7fb7 in gw_read_backend_event (dcb=0x7f53ec000fd0) + at /usr/local/skysql/maxscale/server/modules/protocol/mysql_backend.c:577 +#7 0x000000000056962d in process_pollq (thread_id=3) at /usr/local/skysql/maxscale/server/core/poll.c:867 +#8 0x0000000000568508 in poll_waitevents (arg=0x3) at /usr/local/skysql/maxscale/server/core/poll.c:608 +#9 0x00007f542f54a9d1 in start_thread () from /lib64/libpthread.so.0 +#10 0x00007f542debb8fd in clone () from /lib64/libc.so.6 + +*/ + + + +#include +#include +#include "testconnections.h" +#include "bug670_sql.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + int i; + + Test->tprintf("Connecting to all MaxScale services\n"); + Test->add_result(Test->connect_maxscale(), "Error connecting to Maxscale\n"); + + Test->tprintf("executing sql 100 times (ReadConn Slave)\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(15); + execute_query_silent(Test->conn_slave, bug670_sql); + } + + Test->tprintf("executing sql 100 times (ReadConn Master)\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(15); + execute_query_silent(Test->conn_master, bug670_sql); + } + + Test->tprintf("executing sql 100 times (RWSplit)\n"); + for (i = 0; i < 100; i++) + { + Test->set_timeout(15); + execute_query_silent(Test->conn_rwsplit, bug670_sql); + } + + Test->set_timeout(10); + Test->close_maxscale_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug670_sql.h b/maxscale-system-test/bug670_sql.h new file mode 100644 index 000000000..88040a769 --- /dev/null +++ b/maxscale-system-test/bug670_sql.h @@ -0,0 +1,43 @@ +#ifndef BUG670_SQL_H +#define BUG670_SQL_H + +const char * bug670_sql = + "set autocommit=0;\ + use mysql;\ + set autocommit=1;\ + use test;\ + set autocommit=0;\ + use mysql;\ + set autocommit=1;\ + select user,host from user;\ + set autocommit=0;\ + use fakedb;\ + use test;\ + use mysql;\ + use dontuse;\ + use mysql;\ + drop table if exists t1;\ + commit;\ + use test;\ + use mysql;\ + set autocommit=1;\ + create table t1(id integer primary key);\ + insert into t1 values(5);\ + use test;\ + use mysql;\ + select user from user;\ + set autocommit=0;\ + set autocommit=1;\ + set autocommit=0;\ + insert into mysql.t1 values(7);\ + use mysql;\ + rollback work;\ + commit;\ + delete from mysql.t1 where id=7;\ + insert into mysql.t1 values(7);\ + select host,user from mysql.user;\ + set autocommit=1;\ + delete from mysql.t1 where id = 7; \ + select 1 as \"endof cycle\" from dual;\n"; + +#endif // BUG670_SQL_H diff --git a/maxscale-system-test/bug673.cpp b/maxscale-system-test/bug673.cpp new file mode 100644 index 000000000..2977bb2aa --- /dev/null +++ b/maxscale-system-test/bug673.cpp @@ -0,0 +1,30 @@ +/** + * @file bug673.cpp regression case for bug673 ("MaxScale crashes if "Users table data" is empty and "show dbusers" is executed in maxadmin") + * + * - configure wrong IP for all backends + * - execute maxadmin command show dbusers "RW Split Router" + * - check MaxScale is alive by executing maxadmin again + */ + +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + char result[1024]; + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(20); + + for (int i = 0; i < 2; i++) + { + Test->tprintf("Trying show dbusers \"RW Split Router\"\n"); + Test->add_result(Test->get_maxadmin_param((char *) "show dbusers \"RW Split Router\"", (char *) "User names:", + result), "Maxadmin failed\n"); + Test->tprintf("result %s\n", result); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug676.cpp b/maxscale-system-test/bug676.cpp new file mode 100644 index 000000000..0f43bb1ca --- /dev/null +++ b/maxscale-system-test/bug676.cpp @@ -0,0 +1,44 @@ +/** + * @file bug676.cpp reproducing attempt for bug676 + * - connect to RWSplit + * - stop node0 + * - sleep 20 seconds + * - reconnect + * - check if 'USE test' is ok + * - check MaxScale is alive + */ + +#include +#include "testconnections.h" +#include "mariadb_func.h" + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + + test.set_timeout(30); + + test.connect_maxscale(); + test.tprintf("Stopping node 0"); + test.galera->block_node(0); + test.close_maxscale_connections(); + + test.stop_timeout(); + + test.tprintf("Waiting until the monitor picks a new master"); + sleep(20); + + test.set_timeout(30); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "USE test"); + test.try_query(test.conn_rwsplit, "show processlist;"); + test.close_maxscale_connections(); + + test.stop_timeout(); + + test.galera->unblock_node(0); + + return test.global_result; +} + diff --git a/maxscale-system-test/bug681.cpp b/maxscale-system-test/bug681.cpp new file mode 100644 index 000000000..53350a3de --- /dev/null +++ b/maxscale-system-test/bug681.cpp @@ -0,0 +1,53 @@ +/** + * @file bug681.cpp - regression test for bug681 ("crash if max_slave_connections=10% and 4 or less backends are configured") + * + * - Configure RWSplit with max_slave_connections=10% + * - check ReadConn master and ReadConn slave are alive and RWSplit is not started + */ + +/* +Timofey Turenko 2015-01-05 11:33:29 UTC +try to start MaxScale with max_slave_connections=10% + + +Result: +Program terminated with signal 8, Arithmetic exception. +#0 0x00007ff0517fee3f in have_enough_servers (p_rses=0x7fff9ed17ed0, min_nsrv=1, router_nsrv=3, router=0x397c2b0) + at /usr/local/skysql/maxscale/server/modules/routing/readwritesplit/readwritesplit.c:4668 +4668 LOGIF(LE, (skygw_log_write_flush( +Comment 1 Markus Mäkelä 2015-01-05 11:59:38 UTC +Added casts to floating point values when doing divisions. + +*/ + + +#include +#include "testconnections.h" +#include "mariadb_func.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->connect_maxscale(); + + if (mysql_errno(Test->conn_rwsplit) == 0) + { + Test->add_result(1, "RWSplit services should fail, but it is started\n"); + } + + Test->tprintf("Trying query to ReadConn master\n"); + Test->try_query(Test->conn_master, "show processlist;"); + Test->tprintf("Trying query to ReadConn slave\n"); + Test->try_query(Test->conn_slave, "show processlist;"); + + Test->close_maxscale_connections(); + + Test->check_log_err((char *) + "Unable to start RW-Split-Router service. There are too few backend servers configured in", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug694.cpp b/maxscale-system-test/bug694.cpp new file mode 100644 index 000000000..9949bf98d --- /dev/null +++ b/maxscale-system-test/bug694.cpp @@ -0,0 +1,88 @@ +/** + * @file bug694.cpp - regression test for bug694 ("RWSplit: SELECT @a:=@a+1 as a, test.b FROM test breaks client session") + * + * - set use_sql_variables_in=all in MaxScale.cnf + * - connect to readwritesplit router and execute: + * @verbatim +CREATE TABLE test (b integer); +SELECT @a:=@a+1 as a, test.b FROM test; +USE test +@endverbatim + * - check if MaxScale alive + */ + +/* + +Description Vilho Raatikka 2015-01-14 08:09:45 UTC +Reproduce: +- set use_sql_variables_in=all in MaxScale.cnf +- connect to readwritesplit router and execute: +CREATE TABLE test (b integer); +SELECT @a:=@a+1 as a, test.b FROM test; +USE test + +You'll get: +ERROR 2006 (HY000): MySQL server has gone away + +It is a known limitation that SELÈCTs with SQL variable modifications are not supported. The issue is that they aren't detected and as a consequence hte client session is disconnected. + +It is possible to detect this kind of query in query classifier, but set_query_type loses part of the information. If both SELECT and SQL variable update are detected they can be stored in query type and rwsplit could, for example, prevent from executing the query, execute it in master only (and log), force all SQL variable modifications from that point to master (and log), etc. +Comment 1 Vilho Raatikka 2015-01-15 13:19:18 UTC +query_classifier.cc: set_query_type lost previous query type if the new was more restrictive. Problem was that if query is both READ and SESSION_WRITE and configuration parameter use_sql_variables_in=all was set, routing target became ambiguous. Replaced call to set_query_type with simply adding new type to type (=bit field) and checking unsupported combinations in readwritesplit.c:get_route_target. If such a case is met, a detailed error is written to error log in readwritesplit.c. mysql_client.c sees the error code and sends an error to client. Then mysql_client.c calls router's handleError which ensures that there are enough backend servers so that the session can continue. +*/ + + +#include +#include "testconnections.h" +#include "mariadb_func.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(120); + Test->connect_maxscale(); + + Test->try_query(Test->conn_rwsplit, "USE test"); + Test->try_query(Test->conn_rwsplit, "DROP TABLE IF EXISTS test"); + Test->try_query(Test->conn_rwsplit, "CREATE TABLE test (b integer)"); + + const int iter = Test->smoke ? 10 : 100; + Test->tprintf("Creating and inserting %d rows into a table\n", iter); + + for (int i = 0; i < iter; i++) + { + Test->set_timeout(30); + execute_query(Test->conn_rwsplit, "insert into test value(2);"); + Test->stop_timeout(); + } + + Test->set_timeout(200); + + Test->tprintf("Trying SELECT @a:=@a+1 as a, test.b FROM test\n"); + if (execute_query(Test->conn_rwsplit, "SELECT @a:=@a+1 as a, test.b FROM test;") == 0) + { + Test->add_result(1, "Query succeded, but expected to fail.\n"); + } + Test->tprintf("Trying USE test\n"); + Test->try_query(Test->conn_rwsplit, "USE test"); + + Test->try_query(Test->conn_rwsplit, "DROP TABLE IF EXISTS test"); + + Test->tprintf("Checking if MaxScale alive\n"); + Test->close_maxscale_connections(); + + Test->tprintf("Checking logs\n"); + Test->check_log_err((char *) + "The query can't be routed to all backend servers because it includes SELECT and SQL variable modifications which is not supported", + true); + Test->check_log_err((char *) + "SELECT with session data modification is not supported if configuration parameter use_sql_variables_in=all", + true); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug699.cpp b/maxscale-system-test/bug699.cpp new file mode 100644 index 000000000..6f209ffa2 --- /dev/null +++ b/maxscale-system-test/bug699.cpp @@ -0,0 +1,247 @@ +/** + * @file bug699.cpp regression case for bug 699 ( "rw-split sensitive to order of terms in field list of SELECT (round 2)" ) + * + * - compare @@hostname from "select @@wsrep_node_name, @@hostname" and "select @@hostname, @@wsrep_node_name" + * - comapre @@server_id from "select @@wsrep_node_name, @@server_id" and "select @@server_id, @@wsrep_node_name" + */ + +/* +Kolbe Kegel 2015-01-16 18:38:15 UTC +I opened bug #509 some time ago, but that bug was handled specifically for the case of last_insert_id(). The crux of that bug still exists in 1.0.4 GA: + +[root@max1 ~]# mysql -h 127.0.0.1 -P 4006 -u maxuser -pmaxpwd -e 'select @@hostname, @@wsrep_node_name; select @@wsrep_node_name, @@hostname;' ++------------+-------------------+ +| @@hostname | @@wsrep_node_name | ++------------+-------------------+ +| db2 | db2 | ++------------+-------------------+ ++-------------------+------------+ +| @@wsrep_node_name | @@hostname | ++-------------------+------------+ +| db3 | db3 | ++-------------------+------------+ + +In a single connection, fetching the values of the same two system variables results in the query being routed differently depending on the order of the variables. + +Is there some set of variables that should always be routed to the master for some reason? If so, that should be documented. + +Regardless, the order of terms in the SELECT list should not have any effect on query routing. +Comment 1 Vilho Raatikka 2015-01-16 22:33:38 UTC +@@wsrep_node_name can be resolved only in backend, MaxScale doesn't know about it. As a consequence, Since MaxScale doesn't know what it is it takes the safe bet and routes it to master. +Comment 2 Kolbe Kegel 2015-01-16 22:35:13 UTC +What does "(In reply to comment #1) +> @@wsrep_node_name can be resolved only in backend, MaxScale doesn't know +> about it. As a consequence, Since MaxScale doesn't know what it is it takes +> the safe bet and routes it to master. + +What do you mean by "resolved only in backend" and "MaxScale doesn't know about it"? + +How is @@wsrep_node_name different in that respect from any other server variable that could be different on any given backend? +Comment 3 Vilho Raatikka 2015-01-16 23:01:31 UTC +(In reply to comment #2) +> What does "(In reply to comment #1) +> > @@wsrep_node_name can be resolved only in backend, MaxScale doesn't know +> > about it. As a consequence, Since MaxScale doesn't know what it is it takes +> > the safe bet and routes it to master. +> +> What do you mean by "resolved only in backend" and "MaxScale doesn't know +> about it"? +> +> How is @@wsrep_node_name different in that respect from any other server +> variable that could be different on any given backend? + +MaxScale doesn't know that there is such system variable as @@wsrep_node_name. In my understanding the reason is that the embedded MariaDB server doesn't have Galera's patch. Thus the variable is unknown. +Comment 4 Kolbe Kegel 2015-01-16 23:03:13 UTC +> > +> > How is @@wsrep_node_name different in that respect from any other server +> > variable that could be different on any given backend? +> +> MaxScale doesn't know that there is such system variable as +> @@wsrep_node_name. In my understanding the reason is that the embedded +> MariaDB server doesn't have Galera's patch. Thus the variable is unknown. + +Ahh, right. That sounds familiar. But it's still quite strange that the order of variables in the SELECT statement is meaningful, isn't it? +Comment 5 Vilho Raatikka 2015-01-16 23:09:47 UTC +(In reply to comment #4) +> > > +> > > How is @@wsrep_node_name different in that respect from any other server +> > > variable that could be different on any given backend? +> > +> > MaxScale doesn't know that there is such system variable as +> > @@wsrep_node_name. In my understanding the reason is that the embedded +> > MariaDB server doesn't have Galera's patch. Thus the variable is unknown. +> +> Ahh, right. That sounds familiar. But it's still quite strange that the +> order of variables in the SELECT statement is meaningful, isn't it? + +The effectiveness of atrribute order in SELECT clause is a bug which is fixed in http://bugs.skysql.com/show_bug.cgi?id=694 . + +MaxScale's inability to detect different system variables is slightly problematic as well but haven't really concentrated on finding a decent solution to it yet. It might, however, be necessary. +Comment 6 Vilho Raatikka 2015-01-16 23:55:56 UTC +Attribute order effectiveness is fixed in http://bugs.skysql.com/show_bug.cgi?id=694 + +Inability to detect Galera's system variables is hard to overcome since Galera patch doesn't work with embedded library and embedded library doesn't know Galera's system variables. +Comment 7 Vilho Raatikka 2015-01-17 09:38:09 UTC +Appeared that MariaDB parsing end result depends on the order of [known,unknown] system variable pair in the query. + +There was similar-looking bug in query_classifier before which hide this one. However, debugging and examining the resulting thd and lex for the following queries shows that thd->free_list is non-empty if @@hostname (known variable) is before @@wsrep_node_name. If @@wsrep_node_name is first on the attribute list the resulting thd->free_list==NULL. +In the former case resulting query type is QUERY_TYPE_SYSVAR_READ (routed to slaves) and in the latter case it is unknown (routed to master). + +1. select @@wsrep_node_name, @@hostname; +2. select @@hostname, @@wsrep_node_name; + +Both queries produce similar response but routing them to master only limits scalability. +Comment 8 Mark Riddoch 2015-01-28 08:39:25 UTC +Raised this with the server team as the issue is related to the behaviour of the parser in the embedded server. System variables are resolved at parse time, unknown variables result in a parse error normally, however this order dependency is slightly puzzling. +Comment 9 Mark Riddoch 2015-02-13 10:06:08 UTC +Hi Sergei, + +we have an interesting bug in MaxScale related to parsing. If we try to parse the query + +select @@wsrep_node_name; + +Using the embedded server we link with we do not get a parse tree. A select of other system variables works. I guess the parser is resolving the name of the variable rather than leaving it to the execution phase. Since we do not have Galera this variable is unknown. What is even more strange is that if we have a query of the form + +select @@hostname, @@wsrep_node_name; + +We do get a parse tree, but reversing the order of the select we again fail to get a parse tree with the query + +select @@wsrep_node_name, @@hostname; + +For our purposes we would ideally like to disable the resolving of the variable name at parse time, since that would give us flexibility with regard to new variables being introduced in the servers. Do you know if this is possible or if there is some easy fix we can do to the MariaDB parser that will help us here? + +For your reference the MaxScale bug report can be found here http://bugs.skysql.com/show_bug.cgi?id=699 + + +Thanks +Mark +Comment 10 Mark Riddoch 2015-02-13 10:08:34 UTC +Hi, Mark! + +On Jan 28, Mark Riddoch wrote: +> Hi Sergei, +> +> we have an interesting bug in MaxScale related to parsing. If we try +> to parse the query +> +> select @@wsrep_node_name; +> +> Using the embedded server we link with we do not get a parse tree. A +> select of other system variables works. I guess the parser is +> resolving the name of the variable rather than leaving it to the +> execution phase. Since we do not have Galera this variable is unknown. + +Right... That would be *very* difficult to fix, it'd require a pretty +serious refactoring to get this out of the parser. + +> What is even more strange is that if we have a query of the form +> +> select @@hostname, @@wsrep_node_name; +> +> We do get a parse tree, but reversing the order of the select we again +> fail to get a parse tree with the query +> +> select @@wsrep_node_name, @@hostname; + +That depends on what you call a "parse tree". Items of the select clause +are stored in the thd->lex->current_select->item_list. + +For the first query, the list have 1 element, Item_func_get_system_var +for @@hostname. + +For the second query the list has 0 elements. In both cases, I've +examined the list in the debugger after MYSQLparse() returned. + +So apparently the parsing as aborted as soon as unknown variable is +encountered. + +> For our purposes we would ideally like to disable the resolving of the +> variable name at parse time, since that would give us flexibility with +> regard to new variables being introduced in the servers. Do you know +> if this is possible or if there is some easy fix we can do to the +> MariaDB parser that will help us here? + +I don't see how you can do that from MaxScale. +This looks like something that has to be fixed in the server. +Perhaps - not sure - it'd be possible to introduce some "mode" for the +parser where it doesn't check for valid variable names in certain +contextes. + +Regards, +Sergei + +*/ + + + +#include +#include "testconnections.h" + +const char * sel1 = "select @@wsrep_node_name, @@hostname"; +const char * sel2 = "select @@hostname, @@wsrep_node_name"; + +const char * sel3 = "select @@wsrep_node_name, @@server_id"; +const char * sel4 = "select @@server_id, @@wsrep_node_name"; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->connect_maxscale(); + + Test->tprintf("Trying \n"); + + char serverid1[1024]; + char serverid2[1024]; + + if ( ( + find_field( + Test->conn_rwsplit, sel3, + "@@server_id", &serverid1[0]) + != 0 ) || ( + find_field( + Test->conn_rwsplit, sel4, + "@@server_id", &serverid2[0]) + != 0 )) + { + Test->add_result(1, "@@server_id field not found!!\n"); + delete Test; + exit(1); + } + else + { + Test->tprintf("'%s' to RWSplit gave @@server_id %s\n", sel3, serverid1); + Test->tprintf("'%s' directly to master gave @@server_id %s\n", sel4, serverid2); + Test->add_result(strcmp(serverid1, serverid2), + "server_id are different depending in which order terms are in SELECT\n"); + } + + if ( ( + find_field( + Test->conn_rwsplit, sel1, + "@@hostname", &serverid1[0]) + != 0 ) || ( + find_field( + Test->conn_rwsplit, sel2, + "@@hostname", &serverid2[0]) + != 0 )) + { + Test->add_result(1, "@@hostname field not found!!\n"); + delete Test; + exit(1); + } + else + { + Test->tprintf("'%s' to RWSplit gave @@hostname %s\n", sel1, serverid1); + Test->tprintf("'%s' to RWSplit gave @@hostname %s\n", sel2, serverid2); + Test->add_result(strcmp(serverid1, serverid2), + "hostname are different depending in which order terms are in SELECT\n"); + } + + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug705.cpp b/maxscale-system-test/bug705.cpp new file mode 100644 index 000000000..ec11bd5b5 --- /dev/null +++ b/maxscale-system-test/bug705.cpp @@ -0,0 +1,261 @@ +/** + * @file bug705.cpp regression case for bug 705 ("Authentication fails when the user connects to a database when the SQL mode includes ANSI_QUOTES") + * + * - use only one backend + * - derectly to backend SET GLOBAL sql_mode="ANSI" + * - restart MaxScale + * - check log for "Error : Loading database names for service RW_Split encountered error: Unknown column" + */ + +/* +ivan.stoykov@skysql.com 2015-01-26 14:01:11 UTC +When the sql_mode includes ANSI_QUOTES, maxscale fails to execute the SQL at LOAD_MYSQL_DATABASE_NAMES string + +https://github.com/mariadb-corporation/MaxScale/blob/master/server/core/dbusers.c +line 90: +#define LOAD_MYSQL_DATABASE_NAMES "SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)" + +the error log outputs that string: +"Error : Loading database names for service galera_bs_router encountered error: Unknown column ''' in 'where clause'" + +I think the quotes in LOAD_MYSQL_DATABASE_NAMES and all the SQL used by MaxScale should be adjusted according to the recent sql_mode at the backend server. + +How to repeat: +mysql root@centos-7-minimal:[Mon Jan 26 15:00:48 2015][(none)]> SET SESSION sql_mode = "ORACLE"; select @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++----------------------------------------------------------------------------------------------------------------------+ +| @@sql_mode | ++----------------------------------------------------------------------------------------------------------------------+ +| PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ORACLE,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS,NO_AUTO_CREATE_USER | ++----------------------------------------------------------------------------------------------------------------------+ +1 row in set (0.00 sec) + +mysql root@centos-7-minimal:[Mon Jan 26 15:00:55 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); +ERROR 1054 (42S22): Unknown column '\'' in 'where clause' +mysql root@centos-7-minimal:[Mon Jan 26 15:00:57 2015][(none)]> +Comment 1 ivan.stoykov@skysql.com 2015-01-26 14:02:42 UTC +Work around: set the sql_mode without ANSI_QUOTES: + +mysql root@centos-7-minimal:[Mon Jan 26 15:00:57 2015][(none)]> SET SESSION sql_mode = "MYSQL323"; select @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++------------------------------+ +| @@sql_mode | ++------------------------------+ +| MYSQL323,HIGH_NOT_PRECEDENCE | ++------------------------------+ +1 row in set (0.00 sec) + +mysql root@centos-7-minimal:[Mon Jan 26 15:01:52 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); ++------------------+-----------------------+-----------------------------------+--------------------+----------------+ +| @@innodb_version | @@version | @@version_comment | GRANTEE | PRIVILEGE_TYPE | ++------------------+-----------------------+-----------------------------------+--------------------+----------------+ +| 5.6.21-70.0 | 10.0.15-MariaDB-wsrep | MariaDB Server, wsrep_25.10.r4144 | 'root'@'localhost' | SHOW DATABASES | ++------------------+-----------------------+-----------------------------------+--------------------+----------------+ +1 row in set (0.00 sec) +Comment 2 Massimiliano 2015-01-26 14:19:45 UTC +More informations needed for "the recent sql_mode at the backend server" + +Is that an issue with a particular mysql/mariadb backend version? +Comment 3 ivan.stoykov@skysql.com 2015-01-26 14:30:08 UTC +No, it is not related to any particular version. + +I tested on Percona, MySQL , MariaDB 5.5, MariaDB 10.0.15 with the same result: + +[Mon Jan 26 16:24:34 2015][mysql]> SET SESSION sql_mode = ""; SELECT @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++------------+ +| @@sql_mode | ++------------+ +| | ++------------+ +1 row in set (0.00 sec) +[Mon Jan 26 16:24:53 2015][mysql]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); ++------------------+-----------------+--------------------------------------------------+--------------------+----------------+ +| @@innodb_version | @@version | @@version_comment | GRANTEE | PRIVILEGE_TYPE | ++------------------+-----------------+--------------------------------------------------+--------------------+----------------+ +| 5.5.41-37.0 | 5.5.41-37.0-log | Percona Server (GPL), Release 37.0, Revision 727 | 'seik'@'localhost' | SHOW DATABASES | ++------------------+-----------------+--------------------------------------------------+--------------------+----------------+ +1 row in set (0.00 sec) + +[Mon Jan 26 16:24:57 2015][mysql]> SET SESSION sql_mode = "DB2";SELECT @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++-----------------------------------------------------------------------------------------------+ +| @@sql_mode | ++-----------------------------------------------------------------------------------------------+ +| PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,DB2,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS | ++-----------------------------------------------------------------------------------------------+ +1 row in set (0.00 sec) + +:[Mon Jan 26 16:26:19 2015][mysql]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); +ERROR 1054 (42S22): Unknown column '\'' in 'where clause' + +mysql root@centos-7-minimal:[Mon Jan 26 14:27:33 2015][(none)]> SET SESSION sql_mode = "POSTGRESQL"; select @@sql_mode; Query OK, 0 rows affected (0.00 sec) + ++------------------------------------------------------------------------------------------------------+ +| @@sql_mode | ++------------------------------------------------------------------------------------------------------+ +| PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,POSTGRESQL,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS | ++------------------------------------------------------------------------------------------------------+ +1 row in set (0.01 sec) + +mysql root@centos-7-minimal:[Mon Jan 26 14:42:23 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); +ERROR 1054 (42S22): Unknown column '\'' in 'where clause' +mysql root@centos-7-minimal:[Mon Jan 26 14:58:57 2015][(none)]> SET SESSION sql_mode = ""; select @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++------------+ +| @@sql_mode | ++------------+ +| | ++------------+ +1 row in set (0.00 sec) + +mysql root@centos-7-minimal:[Mon Jan 26 14:59:03 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); ++------------------+-----------+------------------------------+--------------------+----------------+ +| @@innodb_version | @@version | @@version_comment | GRANTEE | PRIVILEGE_TYPE | ++------------------+-----------+------------------------------+--------------------+----------------+ +| 5.6.22 | 5.6.22 | MySQL Community Server (GPL) | 'root'@'localhost' | SHOW DATABASES | ++------------------+-----------+------------------------------+--------------------+----------------+ +1 row in set (0.00 sec) + +mysql root@istoykov.skysql.com:[Mon Jan 26 15:28:12 2015][(none)]> SET SESSION sql_mode = "DB2"; SELECT @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++-----------------------------------------------------------------------------------------------+ +| @@sql_mode | ++-----------------------------------------------------------------------------------------------+ +| PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,DB2,NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS | ++-----------------------------------------------------------------------------------------------+ +1 row in set (0.00 sec) + +mysql root@istoykov.skysql.com:[Mon Jan 26 15:28:19 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); +ERROR 1054 (42S22): Unknown column '\'' in 'where clause' +mysql root@istoykov.skysql.com:[Mon Jan 26 15:28:32 2015][(none)]> SET SESSION sql_mode = "MYSQL40"; SELECT @@sql_mode; +Query OK, 0 rows affected (0.00 sec) + ++-----------------------------+ +| @@sql_mode | ++-----------------------------+ +| MYSQL40,HIGH_NOT_PRECEDENCE | ++-----------------------------+ +1 row in set (0.00 sec) + +mysql root@istoykov.skysql.com:[Mon Jan 26 15:29:09 2015][(none)]> SELECT @@innodb_version,@@version,@@version_comment, GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, "\'","")=CURRENT_USER(); ++---------------------+--------------------+-------------------+--------------------+----------------+ +| @@innodb_version | @@version | @@version_comment | GRANTEE | PRIVILEGE_TYPE | ++---------------------+--------------------+-------------------+--------------------+----------------+ +| 5.5.38-MariaDB-35.2 | 5.5.39-MariaDB-log | MariaDB Server | 'root'@'localhost' | SHOW DATABASES | ++---------------------+--------------------+-------------------+--------------------+----------------+ +1 row in set (0.00 sec) +Comment 4 Massimiliano 2015-01-26 14:48:04 UTC +It's still not clear if the issue is related to MaxScale or it's spotted only when yoy send the statements via mysql client +Comment 5 ivan.stoykov@skysql.com 2015-01-26 16:37:32 UTC +There is at least one case that after setting the sql_mode to string : +"REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ONLY_FULL_GROUP_BY,ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,TRADITIONAL,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" at 10.0.15-MariaDB-wsrep-log , using maxscale in this way returned an error. + +$ mysql --host max-scale-host --user=test --password=xxx --port 4449 mysqlslap +ERROR 1045 (28000): Access denied for user 'test'@'IP (using password: YES) to database 'mysqlslap' + +error at the maxscale log: +Error : Loading database names for service galera_bs_router encountered error: Unknown column ''' in 'where clause'. + +the following test was OK: +$ mysql --host max-scale-host --user=test --password=xxx --port 4449 + +After switch sql_mode to '' as "mysql> set global sql_mode='';", +the connection of the user to a database seems to work OK: +$ mysql --host max-scale-host --user=test --password=xxx -D mysqlslap +Reading table information for completion of table and column names +You can turn off this feature to get a quicker startup with -A + +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 2532 +Server version: 5.5.41-MariaDB MariaDB Server, wsrep_25.10.r4144 + +Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> Bye + + +If needed , I will prepare other test case ? +Comment 6 Massimiliano 2015-01-26 16:40:45 UTC +Yes, please provide us other test cases and we will try to reproduce it +Comment 7 Markus Mäkelä 2015-01-26 18:23:25 UTC +Changed the double quotation marks to single quotation marks because the MySQL client manual says that ANSI_QUOTES still accepts single quotes. + +This can be verified by first setting sql_mode to ANSI: + +set global sql_mode="ANSI"; + +after that, start MaxScale and the error log contains: + +MariaDB Corporation MaxScale /home/markus/build/log/skygw_err1.log Mon Jan 26 20:16:17 2015 +----------------------------------------------------------------------- +--- Logging is enabled. +2015-01-26 20:16:17 Error : Loading database names for service RW Split Router encountered error: Unknown column ''' in 'where clause'. +2015-01-26 20:16:17 Error : Loading database names for service RW Split Hint Router encountered error: Unknown column ''' in 'where clause'. +2015-01-26 20:16:17 Error : Loading database names for service Read Connection Router encountered error: Unknown column ''' in 'where clause'. + +After the change the error is gone. +Comment 8 Massimiliano 2015-01-26 21:16:03 UTC +I managed to reproduce it in my environment: + +- created a setup with 1 server in a service named "RW_Split" + +- issued SET GLOBAL sql_mode="ANSI" via mysql client to that server + +- started MaxScale and found an error in the log: + + +2015-01-26 16:10:52 Error : Loading database names for service RW_Split encountered error: Unknown column ''' in 'where clause'. + +*/ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + printf("Connecting to backend %s\n", Test->repl->IP[0]); + fflush(stdout); + Test->repl->connect(); + + Test->tprintf("Sending SET GLOBAL sql_mode=\"ANSI\" to backend %s\n", Test->repl->IP[0]); + execute_query(Test->repl->nodes[0], "SET GLOBAL sql_mode=\"ANSI\""); + + Test->repl->close_connections(); + + Test->tprintf("Restarting MaxScale\n"); + + Test->set_timeout(100); + Test->restart_maxscale(); + + Test->stop_maxscale(); + Test->stop_timeout(); + Test->tprintf("Waiting 20 seconds\n"); + sleep(20); + + Test->set_timeout(60); + Test->check_log_err((char *) "Loading database names", false); + Test->check_log_err((char *) "Unknown column", false); + + int rval = Test->global_result; + delete Test; + return rval; + // } +} diff --git a/maxscale-system-test/bug711.cpp b/maxscale-system-test/bug711.cpp new file mode 100644 index 000000000..23e74d0a7 --- /dev/null +++ b/maxscale-system-test/bug711.cpp @@ -0,0 +1,58 @@ +/** + * @file bug711.cpp bug711 regression case (Some MySQL Workbench Management actions hang with R/W split router) + * - configure rwsplit with use_sql_variables_in=all + * - try SHOW GLOBAL STATUS with all routers + * - check if Maxscale is still alive + */ + +/* +Massimiliano 2015-01-29 15:35:52 UTC +Some MySQL Workbench Management actions hang with R/W split router + +MySQL Workbench 6.2 on OSX + + +When selecting "Users and Privileges" the client gets hanged. + + + +The quick solution is setting "use_sql_variables_in" in MaxScale config file for R/W split router section + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=massi +passwd=xxxx + +use_sql_variables_in=master + + + +This way everything seems ok + +*/ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->connect_maxscale(); + Test->set_timeout(10); + Test->tprintf("Trying SHOW GLOBAL STATUS against RWSplit\n"); + Test->try_query(Test->conn_rwsplit, (char *) "SHOW GLOBAL STATUS;"); + Test->tprintf("Trying SHOW GLOBAL STATUS against ReadConn master\n"); + Test->try_query(Test->conn_master, (char *) "SHOW GLOBAL STATUS;"); + Test->tprintf("Trying SHOW GLOBAL STATUS against ReadConn slave\n"); + Test->try_query(Test->conn_slave, (char *) "SHOW GLOBAL STATUS;"); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug718.cpp b/maxscale-system-test/bug718.cpp new file mode 100644 index 000000000..fbb512e61 --- /dev/null +++ b/maxscale-system-test/bug718.cpp @@ -0,0 +1,103 @@ +/** + * @file bug718.cpp bug718 (MXS-19) regression case REMOVED FROM TEST SUITE!! (because manuall Master setting breaks backend) + * trying to execute INSERTS from several paralell threads when monitors are disabled + */ + + + +#include +#include +#include "testconnections.h" +#include "sql_t1.h" +#include "maxadmin_operations.h" + +using namespace std; + +TestConnections * Test; +void *thread1( void *ptr ); +//void *thread2( void *ptr ); + +int iterations; + +int db1_num = 0; +int main(int argc, char *argv[]) +{ + Test = new TestConnections(argc, argv); + iterations = (Test->smoke) ? 20 : 100; + Test->set_timeout(20); + int i; + + Test->execute_maxadmin_command((char *) "set server server1 master"); + Test->execute_maxadmin_command((char *) "set server server2 slave"); + Test->execute_maxadmin_command((char *) "set server server3 slave"); + Test->execute_maxadmin_command((char *) "set server server4 slave"); + + Test->execute_maxadmin_command((char *) "set server g_server1 master"); + Test->execute_maxadmin_command((char *) "set server g_server2 slave"); + Test->execute_maxadmin_command((char *) "set server g_server3 slave"); + Test->execute_maxadmin_command((char *) "set server g_server4 slave"); + + Test->tprintf("Connecting to all MaxScale services\n"); + Test->add_result(Test->connect_maxscale(), "Error connection to Maxscale\n"); + + //MYSQL * galera_rwsplit = open_conn(4016, Test->Maxscale_IP, Test->Maxscale_User, Test->Maxscale_Password); + + Test->tprintf("executing show status 1000 times\n"); + + int ThreadsNum = 25; + pthread_t thread_v1[ThreadsNum]; + + int iret1[ThreadsNum]; + for (i = 0; i < ThreadsNum; i ++) + { + iret1[i] = pthread_create(&thread_v1[i], NULL, thread1, NULL); + } + + create_t1(Test->conn_rwsplit); + for (i = 0; i < iterations; i++) + { + Test->set_timeout(200); + insert_into_t1(Test->conn_rwsplit, 4); + printf("i=%d\n", i); + } + Test->set_timeout(300); + for (i = 0; i < ThreadsNum; i ++) + { + pthread_join(thread_v1[i], NULL); + } + + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *thread1( void *ptr ) +{ + MYSQL * conn = open_conn(Test->rwsplit_port , Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, + Test->ssl); + MYSQL * g_conn = open_conn(4016 , Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, Test->ssl); + char sql[1034]; + + sprintf(sql, "CREATE DATABASE IF NOT EXISTS test%d;", db1_num); + execute_query(conn, sql); + sprintf(sql, "USE test%d", db1_num); + execute_query(conn, sql); + + create_t1(conn); + create_t1(g_conn); + for (int i = 0; i < iterations; i++) + { + insert_into_t1(conn, 4); + insert_into_t1(g_conn, 4); + if ((i / 100) * 100 == i) + { + printf("Iteration %d\n", i); + fflush(stdout); + } + } + return NULL; +} + diff --git a/maxscale-system-test/bug729.cpp b/maxscale-system-test/bug729.cpp new file mode 100644 index 000000000..f68c8c8a4 --- /dev/null +++ b/maxscale-system-test/bug729.cpp @@ -0,0 +1,72 @@ +/** + * @file bug729.cpp regression case for bug 729 ("PDO prepared statements bug introduced") + * + * - execute following PHP script + * @verbatim + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => false, +]; + +$host=$argv[1]; +$port=$argv[2]; +$user=$argv[3]; +$pass=$argv[4]; + +$dsn = "mysql:host=".$host.";port=".$port.";dbname=information_schema"; +$dbh = new PDO( $dsn, $user, $pass, $options ); +$res = $dbh + ->query( "SELECT COLLATION_NAME FROM COLLATIONS" ) + ->fetch( PDO::FETCH_COLUMN ); + +var_dump( $res ); + + @endverbatim + * - check log for "Can't route MYSQL_COM_STMT_PREPARE" + */ + +/* + +Description Andreas K-Hansen 2015-02-12 19:32:13 UTC + +The error occurred when upgrading from Maxscale 1.0.4 to 1.0.5. +The following exception occurs when trying to execute a query with prepared statements enabled: + +PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[42000]: Syntax error or access violation: 1064 Routing query to backend failed. See the error log for further details.' in /root/test.php:10 +Stack trace: +#0 /root/test.php(10): PDO->query('SELECT COLLATIO...') +#1 {main} + thrown in /root/test.php on line 10 + +- Error log +Feb 12 19:14:01 363d6aec0f8c MaxScale[263]: Error: Failed to obtain address for host ::1, Address family for hostname not supported +Feb 12 19:14:01 363d6aec0f8c MaxScale[263]: Warning: Failed to add user root@::1 for service [RW Split Router]. This user will be unavailable via MaxScale. +Feb 12 19:14:01 363d6aec0f8c MaxScale[263]: Warning: Failed to add user root@127.0.0.1 for service [RW Split Router]. This user will be unavailable via MaxScale. +Feb 12 19:14:10 363d6aec0f8c MaxScale[263]: Warning : The query can't be routed to all backend servers because it includes SELECT and SQL variable modifications which is not supported. Set use_sql_variables_in=master or split the query to two, where SQL variable modifications are done in the first and the SELECT in the second one. +Feb 12 19:14:10 363d6aec0f8c MaxScale[263]: Error : Can't route MYSQL_COM_STMT_PREPARE:QUERY_TYPE_READ|QUERY_TYPE_PREPARE_STMT:"MYSQL_COM_STMT_PREPARE". SELECT with session data modification is not supported if configuration parameter use_sql_variables_in=all . + +*/ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + char str[1024]; + + sprintf(str, "php %s/bug729.php %s %d %s %s", test_dir, Test->maxscale_IP, Test->rwsplit_port, + Test->maxscale_user, Test->maxscale_password); + + Test->tprintf("Executing PHP script: %s\n", str); + Test->add_result(system(str), "PHP script FAILED!\n"); + + Test->check_log_err((char *) "Can't route MYSQL_COM_STMT_PREPARE", false); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/bug729.php b/maxscale-system-test/bug729.php new file mode 100644 index 000000000..e3da14f10 --- /dev/null +++ b/maxscale-system-test/bug729.php @@ -0,0 +1,19 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_EMULATE_PREPARES => false, +]; + +$host=$argv[1]; +$port=$argv[2]; +$user=$argv[3]; +$pass=$argv[4]; + +$dsn = "mysql:host=".$host.";port=".$port.";dbname=information_schema"; +$dbh = new PDO( $dsn, $user, $pass, $options ); +$res = $dbh + ->query( "SELECT COLLATION_NAME FROM COLLATIONS" ) + ->fetch( PDO::FETCH_COLUMN ); + +var_dump( $res ); + diff --git a/maxscale-system-test/bug730.cpp b/maxscale-system-test/bug730.cpp new file mode 100644 index 000000000..152e84edb --- /dev/null +++ b/maxscale-system-test/bug730.cpp @@ -0,0 +1,68 @@ +/** + * @file bug730.cpp regression case for bug 730 ("Regex filter and shorter than original replacement queries MaxScale") + * + * - setup regex filter, add it to all routers + * @verbatim +[MySetOptionFilter] +type=filter +module=regexfilter +options=ignorecase +match=SET OPTION SQL_QUOTE_SHOW_CREATE +replace=SET SQL_QUOTE_SHOW_CREATE + + @endverbatim + * - try SET OPTION SQL_QUOTE_SHOW_CREATE = 1; against all routers + * - check if Maxscale alive + */ + +/* +Markus Mäkelä 2015-02-16 10:25:50 UTC +Using the following regex filter: + +[MySetOptionFilter] +type=filter +module=regexfilter +options=ignorecase +match=SET OPTION SQL_QUOTE_SHOW_CREATE +replace=SET SQL_QUOTE_SHOW_CREATE + +Sending the following query hangs MaxScale: + +SET OPTION SQL_QUOTE_SHOW_CREATE = 1; + +This happens because modutil_replace_SQL doesn't modify the SQL packet length if the resulting replacement is shorter. +Comment 1 Markus Mäkelä 2015-02-16 10:27:20 UTC +Added SQL packet length modifications to modutil_replace_SQL when the original length is different from the replacement length. +*/ + + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + + Test->tprintf("RWSplit: \n"); + fflush(stdout); + Test->try_query(Test->conn_rwsplit, (char *) "SET OPTION SQL_QUOTE_SHOW_CREATE = 1;"); + Test->tprintf("ReadConn master: \n"); + fflush(stdout); + Test->try_query(Test->conn_master, (char *) "SET OPTION SQL_QUOTE_SHOW_CREATE = 1;"); + Test->tprintf("readConn slave: \n"); + fflush(stdout); + Test->try_query(Test->conn_slave, (char *) "SET OPTION SQL_QUOTE_SHOW_CREATE = 1;"); + + Test->close_maxscale_connections(); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/bulk_insert.cpp b/maxscale-system-test/bulk_insert.cpp new file mode 100644 index 000000000..9e3595e54 --- /dev/null +++ b/maxscale-system-test/bulk_insert.cpp @@ -0,0 +1,219 @@ +/** + * MXS-1121: MariaDB 10.2 Bulk Insert test + * + * This test is a copy of one of the examples for bulk inserts: + * https://mariadb.com/kb/en/mariadb/bulk-insert-column-wise-binding/ + */ + +#include "testconnections.h" + +static int show_mysql_error(MYSQL *mysql) +{ + printf("Error(%d) [%s] \"%s\"\n", mysql_errno(mysql), + mysql_sqlstate(mysql), + mysql_error(mysql)); + return 1; +} + +static int show_stmt_error(MYSQL_STMT *stmt) +{ + printf("Error(%d) [%s] \"%s\"\n", mysql_stmt_errno(stmt), + mysql_stmt_sqlstate(stmt), + mysql_stmt_error(stmt)); + return 1; +} + +int bind_by_column(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[3]; + + /* Data for insert */ + const char *surnames[] = {"Widenius", "Axmark", "N.N."}; + unsigned long surnames_length[] = {8, 6, 4}; + const char *forenames[] = {"Monty", "David", "will be replaced by default value"}; + char forename_ind[] = {STMT_INDICATOR_NTS, STMT_INDICATOR_NTS, STMT_INDICATOR_DEFAULT}; + char id_ind[] = {STMT_INDICATOR_NULL, STMT_INDICATOR_NULL, STMT_INDICATOR_NULL}; + unsigned int array_size = 3; + + if (mysql_query(mysql, "DROP TABLE IF EXISTS test.bulk_example1")) + { + return show_mysql_error(mysql); + } + + if (mysql_query(mysql, "CREATE TABLE test.bulk_example1 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY," \ + "forename CHAR(30) NOT NULL DEFAULT 'unknown', surname CHAR(30))")) + { + return show_mysql_error(mysql); + } + + stmt = mysql_stmt_init(mysql); + if (mysql_stmt_prepare(stmt, "INSERT INTO test.bulk_example1 VALUES (?,?,?)", -1)) + { + return show_stmt_error(stmt); + } + + memset(bind, 0, sizeof(MYSQL_BIND) * 3); + + /* We autogenerate id's, so all indicators are STMT_INDICATOR_NULL */ + bind[0].u.indicator = id_ind; + bind[0].buffer_type = MYSQL_TYPE_LONG; + + bind[1].buffer = forenames; + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].u.indicator = forename_ind; + + bind[2].buffer_type = MYSQL_TYPE_STRING; + bind[2].buffer = surnames; + bind[2].length = surnames_length; + + /* set array size */ + mysql_stmt_attr_set(stmt, STMT_ATTR_ARRAY_SIZE, &array_size); + + /* bind parameter */ + mysql_stmt_bind_param(stmt, bind); + + /* execute */ + if (mysql_stmt_execute(stmt)) + { + return show_stmt_error(stmt); + } + + mysql_stmt_close(stmt); + + /* Check that the rows were inserted */ + if (mysql_query(mysql, "SELECT * FROM test.bulk_example1")) + { + return show_mysql_error(mysql); + } + + MYSQL_RES *res = mysql_store_result(mysql); + + if (res == NULL || mysql_num_rows(res) != 3) + { + printf("Expected 3 rows but got %d (%s)\n", res ? (int)mysql_num_rows(res) : 0, mysql_error(mysql)); + return 1; + } + + if (mysql_query(mysql, "DROP TABLE test.bulk_example1")) + { + return show_mysql_error(mysql); + } + + return 0; +} + +int bind_by_row(MYSQL *mysql) +{ + MYSQL_STMT *stmt; + MYSQL_BIND bind[3]; + + struct st_data + { + unsigned long id; + char id_ind; + char forename[30]; + char forename_ind; + char surname[30]; + char surname_ind; + }; + + struct st_data data[] = + { + {0, STMT_INDICATOR_NULL, "Monty", STMT_INDICATOR_NTS, "Widenius", STMT_INDICATOR_NTS}, + {0, STMT_INDICATOR_NULL, "David", STMT_INDICATOR_NTS, "Axmark", STMT_INDICATOR_NTS}, + {0, STMT_INDICATOR_NULL, "default", STMT_INDICATOR_DEFAULT, "N.N.", STMT_INDICATOR_NTS}, + }; + + unsigned int array_size = 3; + size_t row_size = sizeof(struct st_data); + + if (mysql_query(mysql, "DROP TABLE IF EXISTS bulk_example2")) + { + show_mysql_error(mysql); + } + + if (mysql_query(mysql, "CREATE TABLE bulk_example2 (id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,"\ + "forename CHAR(30) NOT NULL DEFAULT 'unknown', surname CHAR(30))")) + { + show_mysql_error(mysql); + } + + stmt = mysql_stmt_init(mysql); + if (mysql_stmt_prepare(stmt, "INSERT INTO bulk_example2 VALUES (?,?,?)", -1)) + { + show_stmt_error(stmt); + } + + memset(bind, 0, sizeof(MYSQL_BIND) * 3); + + /* We autogenerate id's, so all indicators are STMT_INDICATOR_NULL */ + bind[0].u.indicator = &data[0].id_ind; + bind[0].buffer_type = MYSQL_TYPE_LONG; + + bind[1].buffer = &data[0].forename; + bind[1].buffer_type = MYSQL_TYPE_STRING; + bind[1].u.indicator = &data[0].forename_ind; + + bind[2].buffer_type = MYSQL_TYPE_STRING; + bind[2].buffer = &data[0].surname; + bind[2].u.indicator = &data[0].surname_ind; + + /* set array size */ + mysql_stmt_attr_set(stmt, STMT_ATTR_ARRAY_SIZE, &array_size); + + /* set row size */ + mysql_stmt_attr_set(stmt, STMT_ATTR_ROW_SIZE, &row_size); + + /* bind parameter */ + mysql_stmt_bind_param(stmt, bind); + + /* execute */ + if (mysql_stmt_execute(stmt)) + { + show_stmt_error(stmt); + } + + mysql_stmt_close(stmt); + + + /* Check that the rows were inserted */ + if (mysql_query(mysql, "SELECT * FROM test.bulk_example2")) + { + return show_mysql_error(mysql); + } + + MYSQL_RES *res = mysql_store_result(mysql); + + if (res == NULL || mysql_num_rows(res) != 3) + { + printf("Expected 3 rows but got %d (%s)\n", res ? (int)mysql_num_rows(res) : 0, mysql_error(mysql)); + return 1; + } + + if (mysql_query(mysql, "DROP TABLE test.bulk_example2")) + { + return show_mysql_error(mysql); + } + +} + +int main(int argc, char** argv) +{ + TestConnections::require_repl_version("10.2"); + TestConnections test(argc, argv); + test.connect_maxscale(); + + test.tprintf("Testing column-wise binding with readwritesplit"); + test.add_result(bind_by_column(test.conn_rwsplit), "Bulk inserts with readwritesplit should work"); + test.tprintf("Testing column-wise binding with readconnroute"); + test.add_result(bind_by_column(test.conn_master), "Bulk inserts with readconnroute should work"); + + test.tprintf("Testing row-wise binding with readwritesplit"); + test.add_result(bind_by_row(test.conn_rwsplit), "Bulk inserts with readwritesplit should work"); + test.tprintf("Testing row-wise binding with readconnroute"); + test.add_result(bind_by_row(test.conn_master), "Bulk inserts with readconnroute should work"); + + test.close_maxscale_connections(); + return test.global_result; +} diff --git a/maxscale-system-test/cache/cache_basic/cache_rules.json b/maxscale-system-test/cache/cache_basic/cache_rules.json new file mode 100644 index 000000000..51a5fed30 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/cache_rules.json @@ -0,0 +1,9 @@ +{ + "store": [ + { + "attribute": "table", + "op": "=", + "value": "caching" + } + ] +} diff --git a/maxscale-system-test/cache/cache_basic/r/create.result b/maxscale-system-test/cache/cache_basic/r/create.result new file mode 100644 index 000000000..9da6dc673 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/create.result @@ -0,0 +1,4 @@ +drop database if exists cachingdb; +create database cachingdb; +use cachingdb; +create table caching (a INT, b TEXT, c FLOAT); diff --git a/maxscale-system-test/cache/cache_basic/r/delete.result b/maxscale-system-test/cache/cache_basic/r/delete.result new file mode 100644 index 000000000..1b2dae330 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/delete.result @@ -0,0 +1,2 @@ +USE cachingdb; +DELETE FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/r/drop.result b/maxscale-system-test/cache/cache_basic/r/drop.result new file mode 100644 index 000000000..bf61cc3c5 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/drop.result @@ -0,0 +1 @@ +drop database if exists cachingdb; diff --git a/maxscale-system-test/cache/cache_basic/r/insert1.result b/maxscale-system-test/cache/cache_basic/r/insert1.result new file mode 100644 index 000000000..76426ae91 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/insert1.result @@ -0,0 +1,2 @@ +USE cachingdb; +INSERT INTO caching VALUES (42, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis.', 3.14); diff --git a/maxscale-system-test/cache/cache_basic/r/select0.result b/maxscale-system-test/cache/cache_basic/r/select0.result new file mode 100644 index 000000000..f5d68b631 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/select0.result @@ -0,0 +1,3 @@ +USE cachingdb; +SELECT * FROM caching; +a b c diff --git a/maxscale-system-test/cache/cache_basic/r/select1.result b/maxscale-system-test/cache/cache_basic/r/select1.result new file mode 100644 index 000000000..3806f9135 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/select1.result @@ -0,0 +1,4 @@ +USE cachingdb; +SELECT * FROM caching; +a b c +42 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. 3.14 diff --git a/maxscale-system-test/cache/cache_basic/r/select2.result b/maxscale-system-test/cache/cache_basic/r/select2.result new file mode 100644 index 000000000..e906f4e4c --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/select2.result @@ -0,0 +1,6 @@ +USE cachingdb; +SELECT * FROM caching; +a b c +84 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula. 6.28 diff --git a/maxscale-system-test/cache/cache_basic/r/select3.result b/maxscale-system-test/cache/cache_basic/r/select3.result new file mode 100644 index 000000000..08a41efba --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/select3.result @@ -0,0 +1,8 @@ +USE cachingdb; +SELECT * FROM caching; +a b c +126 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula. 9.42 diff --git a/maxscale-system-test/cache/cache_basic/r/update1.result b/maxscale-system-test/cache/cache_basic/r/update1.result new file mode 100644 index 000000000..f66186eb1 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/update1.result @@ -0,0 +1,4 @@ +USE cachingdb; +UPDATE caching SET a=84, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=6.28; diff --git a/maxscale-system-test/cache/cache_basic/r/update2.result b/maxscale-system-test/cache/cache_basic/r/update2.result new file mode 100644 index 000000000..f66186eb1 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/update2.result @@ -0,0 +1,4 @@ +USE cachingdb; +UPDATE caching SET a=84, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=6.28; diff --git a/maxscale-system-test/cache/cache_basic/r/update3.result b/maxscale-system-test/cache/cache_basic/r/update3.result new file mode 100644 index 000000000..e0dc15564 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/r/update3.result @@ -0,0 +1,6 @@ +USE cachingdb; +UPDATE caching SET a=126, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=9.42; diff --git a/maxscale-system-test/cache/cache_basic/t/create.test b/maxscale-system-test/cache/cache_basic/t/create.test new file mode 100644 index 000000000..babd49f67 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/create.test @@ -0,0 +1,14 @@ +# +# Cache basic +# +# See ../cache_rules. + +--disable_warnings +drop database if exists cachingdb; +--enable_warnings + +create database cachingdb; +use cachingdb; + +create table caching (a INT, b TEXT, c FLOAT); + diff --git a/maxscale-system-test/cache/cache_basic/t/delete.test b/maxscale-system-test/cache/cache_basic/t/delete.test new file mode 100644 index 000000000..2c8af71ca --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/delete.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/create.test has been successfully executed. +# + +USE cachingdb; + +DELETE FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/t/drop.test b/maxscale-system-test/cache/cache_basic/t/drop.test new file mode 100644 index 000000000..ead172343 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/drop.test @@ -0,0 +1,6 @@ +# +# Cache basic +# +# See ../cache_rules. + +drop database if exists cachingdb; diff --git a/maxscale-system-test/cache/cache_basic/t/insert1.test b/maxscale-system-test/cache/cache_basic/t/insert1.test new file mode 100644 index 000000000..9bf0be92a --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/insert1.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/create.test has been successfully executed. +# + +USE cachingdb; + +INSERT INTO caching VALUES (42, 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis.', 3.14); diff --git a/maxscale-system-test/cache/cache_basic/t/select0.test b/maxscale-system-test/cache/cache_basic/t/select0.test new file mode 100644 index 000000000..dbde83e58 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/select0.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/create.test has been successfully executed. +# + +USE cachingdb; + +SELECT * FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/t/select1.test b/maxscale-system-test/cache/cache_basic/t/select1.test new file mode 100644 index 000000000..d8ad3aab5 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/select1.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/insert1.test has been successfully executed. +# + +USE cachingdb; + +SELECT * FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/t/select2.test b/maxscale-system-test/cache/cache_basic/t/select2.test new file mode 100644 index 000000000..074390786 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/select2.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/update2.test has been successfully executed. +# + +USE cachingdb; + +SELECT * FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/t/select3.test b/maxscale-system-test/cache/cache_basic/t/select3.test new file mode 100644 index 000000000..8f7df3a24 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/select3.test @@ -0,0 +1,11 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/update3.test has been successfully executed. +# + +USE cachingdb; + +SELECT * FROM caching; diff --git a/maxscale-system-test/cache/cache_basic/t/update1.test b/maxscale-system-test/cache/cache_basic/t/update1.test new file mode 100644 index 000000000..a9f70de61 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/update1.test @@ -0,0 +1,13 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/insert.test has been successfully executed. +# + +USE cachingdb; + +UPDATE caching SET a=84, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=6.28; diff --git a/maxscale-system-test/cache/cache_basic/t/update2.test b/maxscale-system-test/cache/cache_basic/t/update2.test new file mode 100644 index 000000000..a9f70de61 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/update2.test @@ -0,0 +1,13 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/insert.test has been successfully executed. +# + +USE cachingdb; + +UPDATE caching SET a=84, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=6.28; diff --git a/maxscale-system-test/cache/cache_basic/t/update3.test b/maxscale-system-test/cache/cache_basic/t/update3.test new file mode 100644 index 000000000..ef143bf48 --- /dev/null +++ b/maxscale-system-test/cache/cache_basic/t/update3.test @@ -0,0 +1,15 @@ +# +# Cache basic +# +# See ../cache_rules. +# +# This script assumes t/insert.test has been successfully executed. +# + +USE cachingdb; + +UPDATE caching SET a=126, b='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi eget turpis massa. Duis sit amet commodo ante. Aenean eleifend ipsum sed enim fermentum, eget efficitur risus pulvinar. Maecenas tellus augue, laoreet eget risus porta, porta volutpat tellus. Mauris aliquam vitae velit id faucibus. Aenean euismod, mi nec luctus lacinia, ligula eros commodo velit, ac sagittis ipsum magna scelerisque mi. Aliquam sed sapien sit amet mi convallis pharetra. Sed facilisis, felis ac eleifend fringilla, mauris augue egestas dui, aliquet mollis tellus enim eget erat. Vivamus rhoncus neque nec feugiat mollis. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula. + +Ut feugiat facilisis urna, ac mollis purus iaculis eget. Fusce egestas est quis mauris euismod, et laoreet nunc commodo. Nullam vehicula tellus in sapien viverra vulputate. Etiam eu libero ultrices mi auctor laoreet. Curabitur lacus nisi, ullamcorper eu quam a, auctor malesuada est. Sed feugiat sagittis augue, non semper ante commodo at. Donec lobortis dapibus nunc sit amet interdum. Quisque egestas elementum enim, nec malesuada nisl tincidunt eget. Suspendisse nulla purus, ullamcorper ut ultricies et, pharetra sed metus. Donec eleifend neque vitae lorem dignissim mattis. Donec gravida dui et ultricies feugiat. Aliquam est lectus, consectetur eu est at, finibus ullamcorper ex. Etiam sit amet erat quis dolor commodo facilisis sed finibus enim. Etiam iaculis ultrices vehicula.', c=9.42; diff --git a/maxscale-system-test/cache_basic.sh b/maxscale-system-test/cache_basic.sh new file mode 100755 index 000000000..a65b40f85 --- /dev/null +++ b/maxscale-system-test/cache_basic.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +user=skysql +password=skysql + +# See cnf/maxscale.cnf.template.cache_basic +port=4008 +# Ensure that these are EXACTLY like the corresponding values +# in cnf/maxscale.cnf.template.cache_basic +soft_ttl=5 +hard_ttl=10 + +function run_test +{ + local test_name=$1 + + echo $test_name + + mysqltest --host=$maxscale_IP --port=$port \ + --user=$user --password=$password \ + --logdir=log \ + --test-file=t/$test_name.test \ + --result-file=r/$test_name.result \ + --silent + + if [ $? -eq 0 ] + then + echo " OK" + rc=0 + else + echo " FAILED" + rc=1 + fi + + return $rc +} + +if [ $# -lt 1 ] +then + echo "usage: $script name" + echo "" + echo "name : The name of the test (from CMakeLists.txt) That selects the" + echo " configuration template to be used." + exit 1 +fi + +if [ "$maxscale_IP" == "" ] +then + echo "Error: The environment variable maxscale_IP must be set." + exit 1 +fi + +expected_name="cache_basic" + +if [ "$1" != "$expected_name" ] +then + echo "warning: Expected test name to be $expected_name_basic, was $1." +fi + +source=cache/$1/cache_rules.json +target=vagrant@$maxscale_IP:/home/$maxscale_access_user/cache_rules.json + +if [ $maxscale_IP != "127.0.0.1" ] ; then + scp -i $maxscale_keyfile -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $source $target +else + cp $source /home/$maxscale_access_user/cache_rules.json +fi + +if [ $? -ne 0 ] +then + echo "error: Could not copy rules file to maxscale host." + exit 1 +fi + +echo $source copied to $target + +test_dir=`pwd` + +$test_dir/non_native_setup $1 + +cd cache/$expected_name + +[ -d log ] && rm -r log +mkdir log || exit 1 + +echo + +# We sleep slightly longer than the TTL to ensure that the TTL mechanism +# kicks in. +let seconds=$soft_ttl+2 + +run_test create || exit 1 +run_test insert1 || exit 1 +# We should now get result 1, as this is the first select. +run_test select1 || exit 1 + +run_test update2 || exit 1 +# We should now get result 1, as ttl has NOT passed. +run_test select1 || exit 1 + +echo "Sleeping $seconds" +sleep $seconds +# We should now get result 2, as soft ttl has PASSED. +run_test select2 || exit 1 + +run_test update3 || exit 1 +# We should now get result 2, as ttl has NOT passed. +run_test select2 || exit 1 + +echo "Sleeping $seconds" +sleep $seconds +# We should now get result 3, as soft ttl has PASSED. +run_test select3 || exit 1 + +run_test delete || exit 1 +# We should now get result 3, as soft ttl has NOT passed. +run_test select3 || exit 1 + +echo "Sleeping $seconds" +sleep $seconds +# We should now get result 0, as soft ttl has PASSED. +run_test select0 || exit 1 + +# Cleanup +run_test drop || exit 1 diff --git a/maxscale-system-test/ccrfilter.cpp b/maxscale-system-test/ccrfilter.cpp new file mode 100644 index 000000000..fd9469780 --- /dev/null +++ b/maxscale-system-test/ccrfilter.cpp @@ -0,0 +1,144 @@ +/** + * @file ccrfilter.cpp Tests for the CCRFilter module + * - configure Maxscale to use Consistent Critical Read Filter + * - configure CCR filter with parameter 'time=10' + * - Execute INSERT + * - check that SELECT goes to Master + * - wait 11 seconds + * - chat that SELECT goes to slave + * - configure CCR filter with parameter 'count=3' + * - execute INSERT + * - execute 5 SELECTs, check that first 3 go to Master, 2 last - to slave + * - configure CCR filter with parameter 'match=t2' + * - execute INSERT INTO t1 + * - check SELECTs go to SLAVE + * - execute INSERT INTO t2 + * - check SELECTs go to Master + * - configure CCR filter with parameter 'ignore=t1' and remove parameter 'match=t2' + * - execute INSERT INTO t1 + * - check SELECTs go to SLAVE + * - execute INSERT INTO t2 + * - check SELECTs go to Master + */ + +#include +#include +#include "testconnections.h" + +static int master_id; + +bool is_master(MYSQL *conn) +{ + char str[1024]; + + if (find_field(conn, "SELECT @@server_id", "@@server_id", str) == 0) + { + int server_id = atoi(str); + return server_id == master_id; + } + + return false; +} + +int main(int argc, char *argv[]) +{ + TestConnections * test = new TestConnections(argc, argv); + + test->repl->connect(); + + /** + * Get the master's @@server_id + */ + master_id = test->repl->get_server_id(0); + test->tprintf("Master server_id: %d", master_id); + + + execute_query(test->repl->nodes[0], "CREATE OR REPLACE TABLE test.t1 (id INT);"); + execute_query(test->repl->nodes[0], "CREATE OR REPLACE TABLE test.t2 (id INT);"); + + test->connect_maxscale(); + + test->tprintf("Test `time`. The first SELECT within 10 seconds should go the " + "master and all SELECTs after it should go to the slaves."); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + sleep(1); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the first SELECT"); + sleep(11); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the second SELECT"); + + + test->tprintf("Change test setup for `count`, the first three selects after an " + "insert should go to the master."); + + test->close_maxscale_connections(); + test->ssh_maxscale(true, "sed -i -e 's/time.*/time=0/' /etc/maxscale.cnf"); + test->ssh_maxscale(true, "sed -i -e 's/###count/count/' /etc/maxscale.cnf"); + test->restart_maxscale(); + test->connect_maxscale(); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the first SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the second SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the third SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fourth SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fifth SELECT"); + + + test->tprintf("Change test setup for `count` and `match`, selects after an insert " + "to t1 should go to the slaves and selects after an insert to t2 " + "should go to the master."); + + test->close_maxscale_connections(); + test->ssh_maxscale(true, "sed -i -e 's/###match/match/' /etc/maxscale.cnf"); + test->restart_maxscale(); + test->connect_maxscale(); + + + test->tprintf("t1 first, should be ignored"); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the first SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the second SELECT"); + + test->tprintf("t2 should match and trigger the critical reads"); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t2 VALUES (1)"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the first SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the second SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the third SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fourth SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fifth SELECT"); + + + test->tprintf("Change test setup for `count` and `ignore`, expects the same " + "results as previous test."); + + test->close_maxscale_connections(); + test->ssh_maxscale(true, "sed -i -e 's/match/###match/' /etc/maxscale.cnf"); + test->ssh_maxscale(true, "sed -i -e 's/###ignore/ignore/' /etc/maxscale.cnf"); + test->restart_maxscale(); + test->connect_maxscale(); + + test->tprintf("t1 first, should be ignored"); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the first SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the second SELECT"); + + test->tprintf("t2 should match and trigger the critical reads"); + + test->try_query(test->conn_rwsplit, "INSERT INTO test.t2 VALUES (1)"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the first SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the second SELECT"); + test->add_result(!is_master(test->conn_rwsplit), "Master should reply to the third SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fourth SELECT"); + test->add_result(is_master(test->conn_rwsplit), "Master should NOT reply to the fifth SELECT"); + + execute_query(test->repl->nodes[0], "DROP TABLE test.t1"); + execute_query(test->repl->nodes[0], "DROP TABLE test.t2"); + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/cdc_client.cpp b/maxscale-system-test/cdc_client.cpp new file mode 100644 index 000000000..2c3b7fe1f --- /dev/null +++ b/maxscale-system-test/cdc_client.cpp @@ -0,0 +1,254 @@ +/** + * @file cdc_client.cpp Test of CDC protocol (avro listener) + * - configure binlog router setup, avro router, avro listener + * - connect to avro listener + * - start INSERT load thread + * - read data from avro listener, comapre it with inserted data + */ + +#include +#include +#include "testconnections.h" + +#include +#include +#include +#include +#include +#include +#include +#include "maxinfo_func.h" +#include "sql_t1.h" +#include + +using namespace std; +char reg_str[] = "REGISTER UUID=XXX-YYY_YYY, TYPE=JSON"; +char req_str[] = "REQUEST-DATA test.t1"; +int insert_val = 0; +bool exit_flag = false; + +void *query_thread(void *ptr); + +/** + * @brief cdc_com Connects to avro listenet by CDC protocal, read data, compare data with inserted data + * @param Test TestConnections object + * @return true if test PASSED + */ +bool cdc_com(TestConnections *Test) +{ + int max_inserted_val = Test->smoke ? 25 : 100; + int sock = create_tcp_socket(); + char *ip = get_ip(Test->maxscale_IP); + + if (ip == NULL) + { + Test->tprintf("Can't get IP"); + return false; + } + + struct sockaddr_in *remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *)); + remote->sin_family = AF_INET; + int tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr))); + + if ( tmpres < 0) + { + Test->tprintf("Can't set remote->sin_addr.s_addr"); + return false; + } + else if (tmpres == 0) + { + Test->tprintf("%s is not a valid IP address", ip); + return false; + } + + remote->sin_port = htons(4001); + + if (connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) + { + Test->tprintf("Could not connect"); + return false; + } + + char *get = cdc_auth_srt((char *) "skysql", (char *) "skysql"); + Test->tprintf("Auth string: %s", get); + + //Send the query to the server + if (send_so(sock, get) != 0) + { + Test->tprintf("Cat't send data to scoket"); + return false; + } + + char buf1[1024]; + recv(sock, buf1, 1024, 0); + + //Send the query to the server + if (send_so(sock, reg_str) != 0) + { + Test->tprintf("Cat't send data to scoket"); + return false; + } + + recv(sock, buf1, 1024, 0); + + //Send the query to the server + if (send_so(sock, req_str) != 0) + { + Test->tprintf("Cat't send data to scoket"); + return false; + } + + Test->stop_timeout(); + int epfd = epoll_create(1); + static struct epoll_event ev; + ev.events = EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP; + ev.data.fd = sock; + + if (epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev) < 0) + { + Test->tprintf("Error in epoll_ctl! errno = %d, %s", errno, strerror(errno)); + return false; + } + + epoll_event events[2]; + setnonblocking(sock); + + int inserted_val = 0; + int ignore_first = 2; + + while (inserted_val < max_inserted_val) + { + Test->set_timeout(60); + // wait for something to do... + Test->tprintf("epoll_wait"); + int nfds = epoll_wait(epfd, &events[0], 1, -1); + if (nfds < 0) + { + Test->tprintf("Error in epoll_wait! errno = %d, %s", errno, strerror(errno)); + return false; + } + + if (nfds > 0) + { + // for each ready socket + //for(int i = 0; i < nfds; i++) + //{ + int fd = events[0].data.fd; + char *json = read_sc(sock); + Test->tprintf("%s", json); + //} + if (ignore_first > 0) + { + ignore_first--; // ignoring first reads + if (ignore_first == 0) + { + // first reads done, starting inserting + insert_val = 10; + inserted_val = insert_val; + } + } + else + { + // trying to check JSON + long long int x1; + long long int fl; + get_x_fl_from_json(json, &x1, &fl); + Test->tprintf("data received, x1=%lld fl=%lld", x1, fl); + + if (x1 != inserted_val || fl != inserted_val + 100) + { + Test->tprintf("wrong values in JSON"); + } + + inserted_val++; + insert_val = inserted_val; + } + + free(json); + } + else + { + Test->tprintf("waiting"); + } + } + + free(remote); + free(ip); + close(sock); + + return true; +} + +static TestConnections *Test; + +int main(int argc, char *argv[]) +{ + + Test = new TestConnections(argc, argv); + + Test->set_timeout(600); + Test->stop_maxscale(); + + // Remove old data files and make sure that port 4001 is open + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/avro;" + "iptables -n -L INPUT|grep 4001 || iptables -I INPUT -p tcp --dport 4001 -j ACCEPT;"); + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], "DROP TABLE IF EXISTS t1;"); + Test->repl->close_connections(); + sleep(5); + + Test->start_binlog(); + + Test->set_timeout(120); + Test->stop_maxscale(); + + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/avro"); + + Test->set_timeout(120); + Test->start_maxscale(); + + Test->set_timeout(60); + Test->repl->connect(); + create_t1(Test->repl->nodes[0]); + execute_query(Test->repl->nodes[0], (char *) "INSERT INTO t1 VALUES (111, 222)"); + Test->repl->close_connections(); + + Test->tprintf("Waiting for binlogs to be processed..."); + Test->stop_timeout(); + sleep(15); + + Test->set_timeout(120); + + pthread_t thread; + pthread_create(&thread, NULL, query_thread, NULL); + + Test->add_result(!cdc_com(Test), "Failed to execute test"); + + exit_flag = true; + + pthread_join(thread, NULL); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *query_thread(void *ptr) +{ + + Test->repl->connect(); + + while (!exit_flag) + { + if (insert_val != 0) + { + char str[256]; + sprintf(str, "INSERT INTO t1 VALUES (%d, %d)", insert_val, insert_val + 100); + insert_val = 0; + execute_query(Test->repl->nodes[0], str); + } + } + + Test->repl->close_connections(); +} diff --git a/maxscale-system-test/cdc_connector.cpp b/maxscale-system-test/cdc_connector.cpp new file mode 100644 index 000000000..0800d698e --- /dev/null +++ b/maxscale-system-test/cdc_connector.cpp @@ -0,0 +1,308 @@ +#include "cdc_connector.h" +#include +#include +#include +#include +#include +#include +#include + +#define CDC_CONNECTOR_VERSION "1.0.0" + +#define ERRBUF_SIZE 512 +#define READBUF_SIZE 1024 + +static const char OK_RESPONSE[] = "OK\n"; + +static const char CLOSE_MSG[] = "CLOSE"; +static const char REGISTER_MSG[] = "REGISTER UUID=CDC_CONNECTOR-" CDC_CONNECTOR_VERSION ", TYPE="; +static const char REQUEST_MSG[] = "REQUEST-DATA "; + +namespace +{ + +static inline int nointr_read(int fd, void *dest, size_t size) +{ + int rc = read(fd, dest, size); + + while (rc == -1 && errno == EINTR) + { + rc = read(fd, dest, size); + } + + return rc; +} + +static inline int nointr_write(int fd, const void *src, size_t size) +{ + int rc = write(fd, src, size); + + while (rc == -1 && errno == EINTR) + { + rc = write(fd, src, size); + } + + return rc; +} + +static std::string bin2hex(const uint8_t *data, size_t len) +{ + std::string result; + static const char hexconvtab[] = "0123456789abcdef"; + + for (int i = 0; i < len; i++) + { + result += hexconvtab[data[i] >> 4]; + result += hexconvtab[data[i] & 0x0f]; + } + + return result; +} + +std::string generateAuthString(const std::string& user, const std::string& password) +{ + uint8_t digest[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast (password.c_str()), password.length(), digest); + + std::string auth_str = user; + auth_str += ":"; + + std::string part1 = bin2hex((uint8_t*)auth_str.c_str(), auth_str.length()); + std::string part2 = bin2hex(digest, sizeof(digest)); + + return part1 + part2; +} +} + +namespace CDC +{ + +/** + * Public functions + */ + +Connection::Connection(const std::string& address, + uint16_t port, + const std::string& user, + const std::string& password, + uint32_t flags) : + m_fd(-1), + m_address(address), + m_port(port), + m_user(user), + m_password(password), + m_flags(flags) { } + +Connection::~Connection() +{ + closeConnection(); +} + +bool Connection::createConnection() +{ + bool rval = false; + struct sockaddr_in remote = {}; + + remote.sin_port = htons(m_port); + remote.sin_family = AF_INET; + + if (inet_aton(m_address.c_str(), (struct in_addr*)&remote.sin_addr.s_addr) == 0) + { + m_error = "Invalid address: "; + m_error += m_address; + } + else + { + int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (fd == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to create socket: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + + m_fd = fd; + + if (connect(fd, (struct sockaddr*) &remote, sizeof (remote)) == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to connect: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + else if (doAuth()) + { + rval = doRegistration(); + } + } + + return rval; +} + +void Connection::closeConnection() +{ + if (m_fd != -1) + { + nointr_write(m_fd, CLOSE_MSG, sizeof (CLOSE_MSG) - 1); + close(m_fd); + m_fd = -1; + } +} + +bool Connection::requestData(const std::string& table, const std::string& gtid) +{ + bool rval = true; + + std::string req_msg(REQUEST_MSG); + req_msg += table; + + if (gtid.length()) + { + req_msg += " "; + req_msg += gtid; + } + + if (nointr_write(m_fd, req_msg.c_str(), req_msg.length()) == -1) + { + rval = false; + char err[ERRBUF_SIZE]; + m_error = "Failed to write request: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + + if (rval) + { + /** Read the Avro schema */ + rval = readRow(m_schema); + } + + return rval; +} + +bool Connection::readRow(std::string& dest) +{ + bool rval = true; + + while (true) + { + char buf; + int rc = nointr_read(m_fd, &buf, 1); + + if (rc == -1) + { + rval = false; + char err[ERRBUF_SIZE]; + m_error = "Failed to read row: "; + m_error += strerror_r(errno, err, sizeof (err)); + break; + } + + if (buf == '\n') + { + break; + } + else + { + dest += buf; + } + } + + return rval; +} + +/** + * Private functions + */ + +bool Connection::doAuth() +{ + bool rval = false; + std::string auth_str = generateAuthString(m_user, m_password); + + /** Send the auth string */ + if (nointr_write(m_fd, auth_str.c_str(), auth_str.length()) == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to write authentication data: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + else + { + /** Read the response */ + char buf[READBUF_SIZE]; + int bytes; + + if ((bytes = nointr_read(m_fd, buf, sizeof (buf))) == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to read authentication response: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + else if (memcmp(buf, OK_RESPONSE, sizeof (OK_RESPONSE) - 1) != 0) + { + buf[bytes] = '\0'; + m_error = "Authentication failed: "; + m_error += buf; + } + else + { + rval = true; + } + } + + return rval; +} + +bool Connection::doRegistration() +{ + bool rval = false; + std::string reg_msg(REGISTER_MSG); + + const char *type = ""; + + if (m_flags & CDC_REQUEST_TYPE_JSON) + { + type = "JSON"; + } + else if (m_flags & CDC_REQUEST_TYPE_AVRO) + { + type = "AVRO"; + } + + reg_msg += type; + + /** Send the registration message */ + if (nointr_write(m_fd, reg_msg.c_str(), reg_msg.length()) == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to write registration message: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + else + { + /** Read the response */ + char buf[READBUF_SIZE]; + int bytes; + + if ((bytes = nointr_read(m_fd, buf, sizeof (buf))) == -1) + { + char err[ERRBUF_SIZE]; + m_error = "Failed to read registration response: "; + m_error += strerror_r(errno, err, sizeof (err)); + } + else if (memcmp(buf, OK_RESPONSE, sizeof (OK_RESPONSE) - 1) != 0) + { + buf[bytes] = '\0'; + m_error = "Registration failed: "; + m_error += buf; + } + else + { + rval = true; + } + } + + return rval; +} + +} diff --git a/maxscale-system-test/cdc_connector.h b/maxscale-system-test/cdc_connector.h new file mode 100644 index 000000000..74969bfb9 --- /dev/null +++ b/maxscale-system-test/cdc_connector.h @@ -0,0 +1,47 @@ +#include +#include + +/** Request format flags */ +#define CDC_REQUEST_TYPE_JSON (1 << 0) +#define CDC_REQUEST_TYPE_AVRO (1 << 1) + +namespace CDC +{ + +class Connection +{ +public: + Connection(const std::string& address, + uint16_t port, + const std::string& user, + const std::string& password, + uint32_t flags = CDC_REQUEST_TYPE_JSON); + virtual ~Connection(); + bool createConnection(); + bool requestData(const std::string& table, const std::string& gtid = ""); + bool readRow(std::string& dest); + void closeConnection(); + const std::string& getSchema() const + { + return m_schema; + } + const std::string& getError() const + { + return m_error; + } + +private: + int m_fd; + uint32_t m_flags; + uint16_t m_port; + std::string m_address; + std::string m_user; + std::string m_password; + std::string m_error; + std::string m_schema; + + bool doAuth(); + bool doRegistration(); +}; + +} diff --git a/maxscale-system-test/cdc_datatypes/CMakeLists.txt b/maxscale-system-test/cdc_datatypes/CMakeLists.txt new file mode 100644 index 000000000..b10375204 --- /dev/null +++ b/maxscale-system-test/cdc_datatypes/CMakeLists.txt @@ -0,0 +1,3 @@ +add_test_executable(cdc_datatypes.cpp cdc_datatypes avro LABELS avrorouter binlogrouter BREAKS_REPL) +add_library(cdc_result cdc_result.cpp) +target_link_libraries(cdc_datatypes cdc_result) diff --git a/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp b/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp new file mode 100644 index 000000000..97b5191a2 --- /dev/null +++ b/maxscale-system-test/cdc_datatypes/cdc_datatypes.cpp @@ -0,0 +1,222 @@ +/** + * @file cdc_connect.cpp Test the CDC protocol + */ + +#include "../testconnections.h" +#include "../cdc_connector.h" +#include "cdc_result.h" +#include +#include + +static const char* table_name = "test.type"; +static const char* field_name = "a"; + +static const char* integer_types[] = +{ + "TINYINT", + "SMALLINT", + "MEDIUMINT", + "INT", + "BIGINT", + NULL +}; + +static const char* integer_values[] = +{ + "0", + "1", + "-1", + "20", + "-20", + NULL +}; + +static const char* decimal_types[] = +{ + "FLOAT", + "DOUBLE", + "DECIMAL(10, 2)", + NULL +}; + +static const char* decimal_values[] = +{ + "0", + "1.5", + "-1.5", + "20.5", + "-20.5", + NULL +}; + +static const char* string_types[] = +{ + "CHAR(50)", + "VARCHAR(50)", + "TINYTEXT", + "TEXT", + "MEDIUMTEXT", + "LONGTEXT", + NULL +}; + +static const char* string_values[] = +{ + "\"Hello world!\"", + "\"The quick brown fox jumps over the lazy dog\"", +// "\"The Unicode should work: äöåǢ\"", + NULL +}; + +static const char* binary_types[] = +{ + "BINARY(50)", + "VARBINARY(50)", + "TINYBLOB", + "BLOB", + "MEDIUMBLOB", + "LONGBLOB", + NULL +}; + +static const char* binary_values[] = +{ + "\"Hello world!\"", + "\"The quick brown fox jumps over the lazy dog\"", + "NULL", +// "\"The Unicode should work: äöåǢ\"", +// "\"These should work for binary types: ⦿☏☃☢😤😂\"", + NULL +}; + +struct +{ + const char** types; + const char** values; +} test_set[] +{ + { integer_types, integer_values }, + { decimal_types, decimal_values }, + { string_types, string_values }, + { binary_types, binary_values }, + { 0 } +}; + +void insert_data(TestConnections& test, const char *table, const char* type, const char** values) +{ + test.repl->connect(); + execute_query(test.repl->nodes[0], "CREATE TABLE %s(%s %s)", table, field_name, type); + + for (int i = 0; values[i]; i++) + { + execute_query(test.repl->nodes[0], "INSERT INTO %s VALUES (%s)", table, values[i]); + } + + execute_query(test.repl->nodes[0], "DROP TABLE %s", table); + test.repl->close_connections(); +} + +std::string type_to_table_name(const char* type) +{ + std::string name = table_name; + name += "_"; + name += type; + + size_t offset = name.find('('); + + if (offset != std::string::npos) + { + name = name.substr(0, offset); + } + + offset = name.find(' '); + + if (offset != std::string::npos) + { + name = name.substr(0, offset); + } + + return name; +} + +bool run_test(TestConnections& test) +{ + bool rval = true; + + for (int x = 0; test_set[x].types; x++) + { + for (int i = 0; test_set[x].types[i]; i++) + { + std::string name = type_to_table_name(test_set[x].types[i]); + insert_data(test, name.c_str(), test_set[x].types[i], test_set[x].values); + } + } + + test.repl->connect(); + execute_query(test.repl->nodes[0], "FLUSH LOGS"); + test.repl->close_connections(); + sleep(10); + + for (int x = 0; test_set[x].types; x++) + { + for (int i = 0; test_set[x].types[i]; i++) + { + test.set_timeout(60); + test.tprintf("Testing type: %s", test_set[x].types[i]); + std::string name = type_to_table_name(test_set[x].types[i]); + CDC::Connection conn(test.maxscale_IP, 4001, "skysql", "skysql"); + + if (conn.createConnection() && conn.requestData(name)) + { + for (int j = 0; test_set[x].values[j]; j++) + { + std::string row; + + if (conn.readRow(row)) + { + TestInput input(test_set[x].values[j], test_set[x].types[i]); + TestOutput output(row, field_name); + + if (input != output) + { + test.tprintf("Result mismatch: %s(%s) => %s", + test_set[x].types[i], test_set[x].values[j], output.getValue().c_str()); + rval = false; + } + } + else + { + std::string err = conn.getError(); + test.tprintf("Failed to read data: %s", err.c_str()); + } + } + } + else + { + std::string err = conn.getError(); + test.tprintf("Failed to request data: %s", err.c_str()); + rval = false; + } + test.stop_timeout(); + } + } + return rval; +} + +int main(int argc, char *argv[]) +{ + TestConnections::skip_maxscale_start(true); + TestConnections::check_nodes(false); + TestConnections test(argc, argv); + + test.start_binlog(); + test.restart_maxscale(); + + if (!run_test(test)) + { + test.add_result(1, "Test failed"); + } + + test.check_maxscale_processes(1); + return test.global_result; +} diff --git a/maxscale-system-test/cdc_datatypes/cdc_result.cpp b/maxscale-system-test/cdc_datatypes/cdc_result.cpp new file mode 100644 index 000000000..159fe5c7f --- /dev/null +++ b/maxscale-system-test/cdc_datatypes/cdc_result.cpp @@ -0,0 +1,80 @@ +#include "cdc_result.h" +#include +#include +#include +#include +#include + +using std::cout; +using std::endl; + +TestInput::TestInput(const std::string& value, const std::string& type, const std::string& name) : + m_value(value), m_type(type), m_name(name) +{ + if (m_value[0] == '"' || m_value[0] == '\'') + { + /** Remove quotes from the value */ + m_value = m_value.substr(1, m_value.length() - 2); + } +} + +TestOutput::TestOutput(const std::string& input, const std::string& name) +{ + json_error_t err; + json_t *js = json_loads(input.c_str(), 0, &err); + + if (js) + { + json_t *value = json_object_get(js, name.c_str()); + + if (value) + { + std::stringstream ss; + + if (json_is_string(value)) + { + if (strlen(json_string_value(value)) == 0) + { + ss << "NULL"; + } + else + { + ss << json_string_value(value); + } + } + else if (json_is_integer(value)) + { + ss << json_integer_value(value); + } + else if (json_is_null(value)) + { + ss << "NULL"; + } + else if (json_is_real(value)) + { + ss << json_real_value(value); + } + else + { + cout << "Value '" << name << "' is not a primitive type: " << input << endl; + } + + m_value = ss.str(); + } + else + { + cout << "Value '" << name << "' not found" << endl; + } + + json_decref(js); + } + else + { + cout << "Failed to parse JSON: " << err.text << endl; + } +} + +const std::string& TestOutput::getValue() const +{ + return m_value; +} diff --git a/maxscale-system-test/cdc_datatypes/cdc_result.h b/maxscale-system-test/cdc_datatypes/cdc_result.h new file mode 100644 index 000000000..3ef96982a --- /dev/null +++ b/maxscale-system-test/cdc_datatypes/cdc_result.h @@ -0,0 +1,60 @@ +#ifndef CDC_RESULT_H +#define CDC_RESULT_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +class TestOutput +{ +public: + TestOutput(const std::string& input, const std::string& name); + const std::string& getValue() const; + +private: + std::string m_value; +}; + +class TestInput +{ +public: + TestInput(const std::string& value, const std::string& type, const std::string& name = "a"); + const std::string& getName() const + { + return m_name; + } + const std::string& getValue() const + { + return m_value; + } + const std::string& getType() const + { + return m_type; + } + + bool operator ==(const TestOutput& output) const + { + return m_value == output.getValue(); + } + + bool operator !=(const TestOutput& output) const + { + return !(*this == output); + } + +private: + std::string m_value; + std::string m_type; + std::string m_name; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* CDC_RESULT_H */ + diff --git a/maxscale-system-test/change_master.cpp b/maxscale-system-test/change_master.cpp new file mode 100644 index 000000000..f1b02b057 --- /dev/null +++ b/maxscale-system-test/change_master.cpp @@ -0,0 +1,32 @@ + +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + int OldMaster; + int NewMaster; + + if (argc != 3) + { + printf("Usage: change_master NewMasterNode OldMasterNode\n"); + exit(1); + } + TestConnections * Test = new TestConnections(argc, argv); + + sscanf(argv[1], "%d", &NewMaster); + sscanf(argv[2], "%d", &OldMaster); + + Test->tprintf("Changing master from node %d (%s) to node %d (%s)\n", OldMaster, Test->repl->IP[OldMaster], + NewMaster, Test->repl->IP[NewMaster]); + + Test->repl->connect(); + Test->repl->change_master(NewMaster, OldMaster); + Test->repl->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/change_master_during_session.cpp b/maxscale-system-test/change_master_during_session.cpp new file mode 100644 index 000000000..382cdf647 --- /dev/null +++ b/maxscale-system-test/change_master_during_session.cpp @@ -0,0 +1,66 @@ +/** + * @file change_master_during_seesion.cpp Tries to reconfigure replication setup to use another node as a Master + * - connect to RWSplit + * - reconfugure backend + * - checks that after time > monitor_interval everything is ok + */ + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + + +int main(int argc, char *argv[]) +{ + char sql[1024]; + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->repl->connect(); + + printf("Connecting to RWsplit\n"); + Test->connect_rwsplit(); + Test->set_timeout(30); + Test->add_result(create_t1(Test->conn_rwsplit), "Error creating 't1'\n"); + + Test->try_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); + Test->tprintf("Changing master to node 1\n"); + Test->set_timeout(60); + Test->repl->change_master(1, 0); + Test->tprintf("executing 3 INSERTs\n"); + for (int i = 0; i++; i < 3) + { + Test->set_timeout(60); + sprintf(sql, "INSERT INTO t1 (x1, fl) VALUES(%d, 2);", i); + Test->tprintf("Trying: %d\n", i); + execute_query(Test->conn_rwsplit, sql); + } + Test->set_timeout(60); + Test->tprintf("executing SELECT\n"); + execute_query(Test->conn_rwsplit, (char *) "SELECT * FROM t1;"); + + Test->close_rwsplit(); + + /** Sleep for at least one monitor interval */ + Test->tprintf("Waiting for monitor to detect changes\n"); + Test->stop_timeout(); + sleep(3); + + Test->set_timeout(60); + Test->connect_rwsplit(); + Test->tprintf("Reconnecting and executing SELECT again\n"); + Test->set_timeout(60); + Test->try_query(Test->conn_rwsplit, (char *) "SELECT * FROM t1;"); + + Test->tprintf("Changing master back to node 0\n"); + Test->set_timeout(60); + Test->repl->change_master(0, 1); + Test->set_timeout(60); + Test->repl->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/change_user.cpp b/maxscale-system-test/change_user.cpp new file mode 100644 index 000000000..b4c6128a0 --- /dev/null +++ b/maxscale-system-test/change_user.cpp @@ -0,0 +1,90 @@ +/** + * @file change_user.cpp mysql_change_user test + * + * - using RWSplit and user 'skysql': GRANT SELECT ON test.* TO user@'%' identified by 'pass2'; FLUSH PRIVILEGES; + * - create a new connection to RSplit as 'user' + * - try INSERT expecting 'access denied' + * - call mysql_change_user() to change user to 'skysql' + * - try INSERT again expecting success + * - try to execute mysql_change_user() to switch to user 'user' but use rong password (expecting access denied) + * - try INSERT again expecting success (user should not be changed) + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + + Test->repl->connect(); + Test->connect_maxscale(); + + Test->tprintf("Creating user 'user' \n"); + + execute_query(Test->conn_rwsplit, "DROP USER 'user'@'%%'"); + Test->try_query(Test->conn_rwsplit, (char *) "CREATE USER user@'%%' identified by 'pass2'"); + Test->try_query(Test->conn_rwsplit, (char *) "GRANT SELECT ON test.* TO user@'%%'"); + Test->try_query(Test->conn_rwsplit, (char *) "FLUSH PRIVILEGES;"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t1"); + Test->try_query(Test->conn_rwsplit, (char *) "CREATE TABLE t1 (x1 int, fl int)"); + + Test->tprintf("Changing user... \n"); + Test->add_result(mysql_change_user(Test->conn_rwsplit, (char *) "user", (char *) "pass2", (char *) "test") , + "changing user failed \n"); + Test->tprintf("mysql_error is %s\n", mysql_error(Test->conn_rwsplit)); + + Test->tprintf("Trying INSERT (expecting access denied)... \n"); + if ( execute_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 VALUES (77, 11);") == 0) + { + Test->add_result(1, "INSERT query succedded to user which does not have INSERT PRIVILEGES\n"); + } + + Test->tprintf("Changing user back... \n"); + Test->add_result(mysql_change_user(Test->conn_rwsplit, Test->repl->user_name, Test->repl->password, + (char *) "test"), "changing user failed \n"); + + Test->tprintf("Trying INSERT (expecting success)... \n"); + Test->try_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 VALUES (77, 12);"); + + Test->tprintf("Changing user with wrong password... \n"); + if (mysql_change_user(Test->conn_rwsplit, (char *) "user", (char *) "wrong_pass2", (char *) "test") == 0) + { + Test->add_result(1, "changing user with wrong password successed! \n"); + } + Test->tprintf("%s\n", mysql_error(Test->conn_rwsplit)); + if ((strstr(mysql_error(Test->conn_rwsplit), "Access denied for user")) == NULL) + { + Test->add_result(1, "There is no proper error message\n"); + } + + Test->tprintf("Trying INSERT again (expecting success - use change should fail)... \n"); + Test->try_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 VALUES (77, 13);"); + + + Test->tprintf("Changing user with wrong password using ReadConn \n"); + if (mysql_change_user(Test->conn_slave, (char *) "user", (char *) "wrong_pass2", (char *) "test") == 0) + { + Test->add_result(1, "FAILED: changing user with wrong password successed! \n"); + } + Test->tprintf("%s\n", mysql_error(Test->conn_slave)); + if ((strstr(mysql_error(Test->conn_slave), "Access denied for user")) == NULL) + { + Test->add_result(1, "There is no proper error message\n"); + } + + Test->tprintf("Changing user for ReadConn \n"); + Test->add_result(mysql_change_user(Test->conn_slave, (char *) "user", (char *) "pass2", (char *) "test") , + "changing user failed \n"); + + Test->try_query(Test->conn_rwsplit, (char *) "DROP USER user@'%%';"); + execute_query_silent(Test->conn_rwsplit, "DROP TABLE test.t1"); + + Test->close_maxscale_connections(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/check_backend.cpp b/maxscale-system-test/check_backend.cpp new file mode 100644 index 000000000..2c4ae6852 --- /dev/null +++ b/maxscale-system-test/check_backend.cpp @@ -0,0 +1,50 @@ +/** + * @file check_backend.cpp simply checks if backend is alive + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + /*Test->restart_maxscale(); + sleep(5);*/ + Test->set_timeout(10); + + Test->tprintf("Connecting to Maxscale routers with Master/Slave backend\n"); + Test->connect_maxscale(); + Test->tprintf("Testing connections\n"); + Test->add_result(Test->test_maxscale_connections(true, true, true), "Can't connect to backend\n"); + Test->tprintf("Connecting to Maxscale router with Galera backend\n"); + MYSQL * g_conn = open_conn(4016 , Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, Test->ssl); + if (g_conn != NULL ) + { + Test->tprintf("Testing connection\n"); + Test->add_result(Test->try_query(g_conn, (char *) "SELECT 1"), + (char *) "Error executing query against RWSplit Galera\n"); + } + Test->tprintf("Closing connections\n"); + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + + char * ver = Test->ssh_maxscale_output(false, "maxscale --version-full"); + Test->tprintf("Maxscale_full_version_start:\n%s\nMaxscale_full_version_end\n", ver); + + if ((Test->global_result == 0) && (Test->use_snapshots)) + { + Test->tprintf("Taking snapshot\n"); + Test->take_snapshot((char *) "clean"); + } + else + { + Test->tprintf("Snapshots are not in use\n"); + } + + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.add_server b/maxscale-system-test/cnf/maxscale.cnf.template.add_server new file mode 100644 index 000000000..a21990d06 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.add_server @@ -0,0 +1,65 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3,server4 +user=maxskysql +passwd= skysql + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + +[server5] +type=server +address=127.0.0.1 +port=3005 +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.add_service b/maxscale-system-test/cnf/maxscale.cnf.template.add_service new file mode 100644 index 000000000..a629dc819 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.add_service @@ -0,0 +1,73 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3,server4 +user=maxskysql +passwd= skysql + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.auroramon b/maxscale-system-test/cnf/maxscale.cnf.template.auroramon new file mode 100644 index 000000000..e31f369f1 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.auroramon @@ -0,0 +1,66 @@ +[maxscale] +threads=###threads### +log_warning=1 + +# Monitors + +[Aurora Monitor] +type=monitor +module=auroramon +servers=server1,server2,server3,server4 +user=skysql +passwd=skysqlrds +monitor_interval=1000 + +# Services + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=skysql +passwd=skysqlrds + +[CLI] +type=service +router=cli + +# Listeners + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +# Servers + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.avro b/maxscale-system-test/cnf/maxscale.cnf.template.avro new file mode 100755 index 000000000..a8593069f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.avro @@ -0,0 +1,59 @@ +[maxscale] +threads=###threads### +log_warning=1 +log_notice=1 +log_info=1 +#log_debug=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[avro-converter] +type=service +router=avrorouter +router_options=binlogdir=/var/lib/maxscale/Binlog_Service, + filestem=mar-bin,start_index=1, + group_trx=1,group_rows=1, + avrodir=/var/lib/maxscale/avro/ +user=skysql +passwd=skysql + +[avro-listener] +type=listener +service=avro-converter +protocol=CDC +port=4001 +authenticator=CDCPlainAuth diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.avro_compression b/maxscale-system-test/cnf/maxscale.cnf.template.avro_compression new file mode 100644 index 000000000..1ea65a5ac --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.avro_compression @@ -0,0 +1,46 @@ +[maxscale] +threads=###threads### +log_notice=1 +log_info=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1 + +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[avro-converter] +type=service +router=avrorouter +codec=deflate +router_options=binlogdir=/var/lib/maxscale/Binlog_Service, + filestem=mar-bin,start_index=1, + group_trx=1,group_rows=1, + avrodir=/var/lib/maxscale/avro/ +user=skysql +passwd=skysql + +[avro-listener] +type=listener +service=avro-converter +protocol=CDC +port=4001 +authenticator=CDCPlainAuth diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bad_pers b/maxscale-system-test/cnf/maxscale.cnf.template.bad_pers new file mode 100755 index 000000000..e67181178 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bad_pers @@ -0,0 +1,100 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=-1 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=-1 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=-1 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=-1 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bad_ssl b/maxscale-system-test/cnf/maxscale.cnf.template.bad_ssl new file mode 100755 index 000000000..4d4c0a525 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bad_ssl @@ -0,0 +1,107 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.batchinsert b/maxscale-system-test/cnf/maxscale.cnf.template.batchinsert new file mode 100644 index 000000000..e37d64c8c --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.batchinsert @@ -0,0 +1,88 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_cbc b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_cbc new file mode 100755 index 000000000..e4259fad6 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_cbc @@ -0,0 +1,38 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1,encrypt_binlog=1,encryption_key_file=/etc/mariadb_binlog_keys.txt,encryption_algorithm=aes_cbc + + +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_ctr b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_ctr new file mode 100755 index 000000000..265a5e751 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_enc_aes_ctr @@ -0,0 +1,38 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1,encrypt_binlog=1,encryption_key_file=/etc/mariadb_binlog_keys.txt,encryption_algorithm=aes_ctr + + +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.binlog_incompl b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_incompl new file mode 100755 index 000000000..40f9d0dbd --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.binlog_incompl @@ -0,0 +1,38 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=repl +passwd=repl +version_string=5.6.15-log + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug359 b/maxscale-system-test/cnf/maxscale.cnf.template.bug359 new file mode 100755 index 000000000..203272f92 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug359 @@ -0,0 +1,79 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +router_options=slave +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug471 b/maxscale-system-test/cnf/maxscale.cnf.template.bug471 new file mode 100755 index 000000000..0a146643d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug471 @@ -0,0 +1,104 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server3 ,server4 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints|regex + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug479 b/maxscale-system-test/cnf/maxscale.cnf.template.bug479 new file mode 100755 index 000000000..51c3862df --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug479 @@ -0,0 +1,79 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +filters=non existing filter | не существуюший фильтер + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug493 b/maxscale-system-test/cnf/maxscale.cnf.template.bug493 new file mode 100755 index 000000000..8df071bfa --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug493 @@ -0,0 +1,78 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug495 b/maxscale-system-test/cnf/maxscale.cnf.template.bug495 new file mode 100755 index 000000000..10c4ff938 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug495 @@ -0,0 +1,78 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4,server5 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug526 b/maxscale-system-test/cnf/maxscale.cnf.template.bug526 new file mode 100755 index 000000000..393d62adb --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug526 @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +filters=testfilter + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[testfilter] +type=filter +module=foobar + + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug539 b/maxscale-system-test/cnf/maxscale.cnf.template.bug539 new file mode 100755 index 000000000..f79d1b450 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug539 @@ -0,0 +1,99 @@ +[maxscale] +threads=8 +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +detect_replication_lag=1 +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd + +[hints] +type=filter +module=hintfilter + + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=master +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +user=maxskysql +passwd=skysql +filters=hints + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug547 b/maxscale-system-test/cnf/maxscale.cnf.template.bug547 new file mode 100755 index 000000000..f08237e97 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug547 @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=1.1.1.1 +port=3306 +protocol=MySQLBackend + +[server3] +type=server +address=1.1.1.2 +port=3306 +protocol=MySQLBackend + +[server4] +type=server +address=1.1.1.3 +port=3306 +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug567 b/maxscale-system-test/cnf/maxscale.cnf.template.bug567 new file mode 100755 index 000000000..2f5f687d6 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug567 @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 +log_to_shm=1 +log_debug=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug571 b/maxscale-system-test/cnf/maxscale.cnf.template.bug571 new file mode 100755 index 000000000..98d03c96f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug571 @@ -0,0 +1,110 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[regex] +type=filter +module=regexfilter +match=[Ff][Oo0][rR][mM] +replace=FROM + +[r2] +type=filter +module=regexfilter +match=fetch +replace=select + +[hints] +type=filter +module=hintfilter + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints|regex|r2 + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug585 b/maxscale-system-test/cnf/maxscale.cnf.template.bug585 new file mode 100755 index 000000000..169ecdab5 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug585 @@ -0,0 +1,109 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=regex|typo|hints + +[hints] +type=filter +module=hintfilter + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug587 b/maxscale-system-test/cnf/maxscale.cnf.template.bug587 new file mode 100755 index 000000000..b1ddcaaad --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug587 @@ -0,0 +1,103 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints|regex + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug587_1 b/maxscale-system-test/cnf/maxscale.cnf.template.bug587_1 new file mode 100755 index 000000000..47a7942b8 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug587_1 @@ -0,0 +1,103 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=regex|hints + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug601 b/maxscale-system-test/cnf/maxscale.cnf.template.bug601 new file mode 100755 index 000000000..39327ceb0 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug601 @@ -0,0 +1,89 @@ +[maxscale] +threads=1 +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug620 b/maxscale-system-test/cnf/maxscale.cnf.template.bug620 new file mode 100755 index 000000000..806f6d3e7 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug620 @@ -0,0 +1,90 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +enable_root_user=true + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug643 b/maxscale-system-test/cnf/maxscale.cnf.template.bug643 new file mode 100755 index 000000000..5b0a7fdf4 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug643 @@ -0,0 +1,98 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +user=maxskysql +passwd=skysql +filters=duplicate + +[duplicate] +type=filter +module=tee +service=RW Split Router + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug643_1 b/maxscale-system-test/cnf/maxscale.cnf.template.bug643_1 new file mode 100755 index 000000000..5651c65cf --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug643_1 @@ -0,0 +1,142 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +monitor_interval=10000 +servers=server1,server2,server3,server4 +detect_replication_lag=1 +detect_stale_master=1 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[qla] +type=filter +module=qlafilter +filebase=/tmp/QueryLog + +[duplicate] +type=filter +module=tee +service=RW Split2 + +[testfilter] +type=filter +module=foobar + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +#servers=server1,server2 +max_slave_connections=100% +use_sql_variables_in=all +#use_sql_variables_in=master +user=maxskysql +passwd=skysql +#filters=typo|qla|regex|hints|regex|hints +#enable_root_user=1 +filters=duplicate + +[RW Split2] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=all +user=maxskysql +passwd=skysql +filters=qla|tests|hints + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug645 b/maxscale-system-test/cnf/maxscale.cnf.template.bug645 new file mode 100755 index 000000000..7133ba292 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug645 @@ -0,0 +1,108 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server1,server3,server2 +user=maxskysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug645_1 b/maxscale-system-test/cnf/maxscale.cnf.template.bug645_1 new file mode 100755 index 000000000..5d6d17161 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug645_1 @@ -0,0 +1,108 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=maxskysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug648 b/maxscale-system-test/cnf/maxscale.cnf.template.bug648 new file mode 100755 index 000000000..a2e35736e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug648 @@ -0,0 +1,97 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +#filters=TEE + +[TEE] +type=filter +module=tee +service=RW Split Router + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=TEE + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=TEE + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug650 b/maxscale-system-test/cnf/maxscale.cnf.template.bug650 new file mode 100755 index 000000000..623e21494 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug650 @@ -0,0 +1,109 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=maxskysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug664 b/maxscale-system-test/cnf/maxscale.cnf.template.bug664 new file mode 100755 index 000000000..e51e143fb --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug664 @@ -0,0 +1,108 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW_Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +version_string=5.1-OLD-Bored-Mysql +filters=DuplicaFilter + +[RW_Split] +type=service +router=readwritesplit +servers=server3,server2 +user=maxskysql +passwd=skysql + +[DuplicaFilter] +type=filter +module=tee +service=RW_Split + +[RW_Listener] +type=listener +service=RW_Router +protocol=MySQLClient +port=4006 + +[RW_Split_list] +type=listener +service=RW_Split +protocol=MySQLClient +port=4016 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug670 b/maxscale-system-test/cnf/maxscale.cnf.template.bug670 new file mode 100755 index 000000000..7576b054e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug670 @@ -0,0 +1,134 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +monitor_interval=10000 +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[typo] +type=filter +module=regexfilter +match=[Ff][Oo0][Rr][Mm] +replace=from + +[qla] +type=filter +module=qlafilter +filebase=/tmp/QueryLog + +[duplicate] +type=filter +module=tee +service=RW Split2 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 + +max_slave_connections=100% +use_sql_variables_in=all + +user=skysql +passwd=skysql +filters=typo|qla|regex|hints|regex|hints +enable_root_user=1 + +[RW Split2] +type=service +router=readwritesplit +servers=server1,server2 +max_slave_connections=100% +use_sql_variables_in=all +user=skysql +passwd=skysql + +[Read Connection Router] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=skysql +passwd=skysql +filters=duplicate + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Listener2] +type=listener +service=RW Split2 +protocol=MySQLClient +port=4012 + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug673 b/maxscale-system-test/cnf/maxscale.cnf.template.bug673 new file mode 100755 index 000000000..dc7c608fa --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug673 @@ -0,0 +1,42 @@ +[maxscale] +threads=###threads### +auth_connect_timeout=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 +backend_connect_timeout=1 + +[RW Split Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=1.1.1.1 +port=1234 +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug694 b/maxscale-system-test/cnf/maxscale.cnf.template.bug694 new file mode 100755 index 000000000..b145e7d8b --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug694 @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +use_sql_variables_in=all + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug705 b/maxscale-system-test/cnf/maxscale.cnf.template.bug705 new file mode 100755 index 000000000..c21b4f90e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug705 @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug711 b/maxscale-system-test/cnf/maxscale.cnf.template.bug711 new file mode 100755 index 000000000..bf3ffe11e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug711 @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +use_sql_variables_in=all + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug718 b/maxscale-system-test/cnf/maxscale.cnf.template.bug718 new file mode 100755 index 000000000..0037bb149 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug718 @@ -0,0 +1,119 @@ +[maxscale] +threads=1 +log_warning=1 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=g_server1, g_server2, g_server3, g_server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + +[g_server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[g_server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_3### +protocol=MySQLBackend + +[g_server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[g_server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bug730 b/maxscale-system-test/cnf/maxscale.cnf.template.bug730 new file mode 100755 index 000000000..cbfcdb021 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bug730 @@ -0,0 +1,100 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +filters=MySetOptionFilter + +[MySetOptionFilter] +type=filter +module=regexfilter +options=ignorecase +match=SET OPTION SQL_QUOTE_SHOW_CREATE +replace=SET SQL_QUOTE_SHOW_CREATE + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=MySetOptionFilter + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=MySetOptionFilter + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.bulk_insert b/maxscale-system-test/cnf/maxscale.cnf.template.bulk_insert new file mode 100755 index 000000000..d7bfc8a17 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.bulk_insert @@ -0,0 +1,68 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.cache_basic b/maxscale-system-test/cnf/maxscale.cnf.template.cache_basic new file mode 100644 index 000000000..04c3308e2 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.cache_basic @@ -0,0 +1,76 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql +filters=Cache + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[Cache] +type=filter +module=cache +storage=storage_inmemory +# NOTE: If you adjust the TTL values, ensure that test programs dependent +# NOTE: on the TTL are ajusted as well. +hard_ttl=10 +soft_ttl=5 +max_size=10M +rules=/###access_homedir###/cache_rules.json + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.ccrfilter b/maxscale-system-test/cnf/maxscale.cnf.template.ccrfilter new file mode 100644 index 000000000..1883ddeb6 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.ccrfilter @@ -0,0 +1,110 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +# CCRFilter + +[ccrfilter] +type=filter +module=ccrfilter +time=10 +###count=3 +###match=t2 +###ignore=t1 + +# RWSplit + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +filters=ccrfilter + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +# RConn - Slave + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +# RConn - Master + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +# MaxAdmin + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +# Servers + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.check_backend b/maxscale-system-test/cnf/maxscale.cnf.template.check_backend new file mode 100755 index 000000000..3dd76b1a7 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.check_backend @@ -0,0 +1,140 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[Galera Monitor] +type=monitor +module=galeramon +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 +#socket=/tmp/rwsplit.sock + + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.config_reload b/maxscale-system-test/cnf/maxscale.cnf.template.config_reload new file mode 100644 index 000000000..e61ab0492 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.config_reload @@ -0,0 +1,59 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.connection_limit b/maxscale-system-test/cnf/maxscale.cnf.template.connection_limit new file mode 100755 index 000000000..118fdd05d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.connection_limit @@ -0,0 +1,97 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +max_connections=10 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_connections=25 + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_connections=20 + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.failover_mysqlmon b/maxscale-system-test/cnf/maxscale.cnf.template.failover_mysqlmon new file mode 100644 index 000000000..09cc8a5ba --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.failover_mysqlmon @@ -0,0 +1,88 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers= server1, server2, server3, server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_standalone_master=true +failcount=2 +allow_cluster_recovery=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.fwf b/maxscale-system-test/cnf/maxscale.cnf.template.fwf new file mode 100755 index 000000000..8c9e1213b --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.fwf @@ -0,0 +1,77 @@ +[maxscale] +threads=###threads### +query_classifier_args=log_unrecognized_statements=3 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=100 + +[Database Firewall] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +log_match=true +log_no_match=true + +[RW Split Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=Database Firewall + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.fwf_action b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_action new file mode 100644 index 000000000..652f7e77b --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_action @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +# Service with blacklist action + +[readconn-blacklist] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=blacklist + +[readconn-blacklist-listener] +type=listener +service=readconn-blacklist +protocol=MySQLClient +port=4006 + +[blacklist] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +action=block +log_match=true +log_no_match=true + +# Service with whitelist action + +[readconn-whitelist] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=whitelist + +[readconn-whitelist-listener] +type=listener +service=readconn-whitelist +protocol=MySQLClient +port=4009 + +[whitelist] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +action=allow +log_match=true +log_no_match=true + +# Service with ignore action + +[readconn-ignore] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=ignore + +[readconn-ignore-listener] +type=listener +service=readconn-ignore +protocol=MySQLClient +port=4008 + +[ignore] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +action=ignore +log_match=true +log_no_match=true diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.fwf_com_ping b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_com_ping new file mode 100644 index 000000000..511a5a2ae --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_com_ping @@ -0,0 +1,41 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +# Service with whitelist action + +[readconn-whitelist] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=whitelist + +[readconn-whitelist-listener] +type=listener +service=readconn-whitelist +protocol=MySQLClient +port=4006 + +[whitelist] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +action=allow +log_match=true +log_no_match=true diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.fwf_logging b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_logging new file mode 100644 index 000000000..de855516b --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_logging @@ -0,0 +1,39 @@ +[maxscale] +threads=###threads### + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[readconn] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=ignore + +[readconn-listener] +type=listener +service=readconn +protocol=MySQLClient +port=4009 + +[ignore] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +action=ignore +log_match=true +log_no_match=true diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.fwf_syntax b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_syntax new file mode 100644 index 000000000..893b4d6f1 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.fwf_syntax @@ -0,0 +1,28 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=Database Firewall + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[Database Firewall] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.galera b/maxscale-system-test/cnf/maxscale.cnf.template.galera new file mode 100755 index 000000000..cddd792c5 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.galera @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.galera.bug681 b/maxscale-system-test/cnf/maxscale.cnf.template.galera.bug681 new file mode 100755 index 000000000..938111b73 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.galera.bug681 @@ -0,0 +1,107 @@ +[maxscale] +threads=1 +log_warning=1 + +[MySQL Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3 +#user=maxmon +#passwd=maxpwd +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3 +#user=maxpriv +#passwd=maxpwd +user=maxskysql +passwd=skysql +filters=MyLogFilter +version_string=MariaDBEC-10.0.14 +localhost_match_wildcard_host=1 +max_slave_connections=10% +#max_slave_replication_lag=30 +#router_options=slave_selection_criteria=LEAST_BEHIND_MASTER + +[Read Connection Router] +type=service +router=readconnroute +router_options=synced +servers=server1,server2,server3 +#user=maxpriv +#passwd=maxpwd +user=maxskysql +passwd=skysql + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[MyLogFilter] +type=filter +module=qlafilter +filebase=/tmp/QueryLog + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +#[server4] +#type=server +#address=###galera_server_IP_4### +#port=###galera_server_port_4### +#protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.galera.weight b/maxscale-system-test/cnf/maxscale.cnf.template.galera.weight new file mode 100755 index 000000000..6e782421d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.galera.weight @@ -0,0 +1,78 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +use_priority=true +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +weightby=serversize_rws +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 +user=maxskysql +passwd=skysql + +[Read Connection Router] +type=service +router=readconnroute +router_options=synced +servers=server1,server2,server3,server4 +weightby=serversize +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener] +type=listener +service=Read Connection Router +protocol=MySQLClient +port=4008 +#socket=/tmp/readconn.sock + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +serversize=1 +serversize_rws=1 +priority=1 + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +serversize=2 +serversize_rws=30000 + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +serversize=3 +serversize_rws=20000 + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +serversize=0 +serversize_rws=10000 diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.galera_mxs564 b/maxscale-system-test/cnf/maxscale.cnf.template.galera_mxs564 new file mode 100644 index 000000000..292d6b7fe --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.galera_mxs564 @@ -0,0 +1,88 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.galera_priority b/maxscale-system-test/cnf/maxscale.cnf.template.galera_priority new file mode 100644 index 000000000..88d5bfe31 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.galera_priority @@ -0,0 +1,66 @@ +[maxscale] +threads=###threads### +log_warning=1 +log_info=1 +log_notice=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +use_priority=true +monitor_interval=1000 +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +priority=2 + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +priority=4 + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +priority=1 + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +priority=3 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.gatekeeper b/maxscale-system-test/cnf/maxscale.cnf.template.gatekeeper new file mode 100644 index 000000000..7bce3ef9c --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.gatekeeper @@ -0,0 +1,81 @@ +[maxscale] +threads=###threads### +log_warning=1 + +# Monitors + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +# Services + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=master +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +user=maxskysql +passwd=skysql +filters=gatekeeper + +[CLI] +type=service +router=cli + +# Filters + +[gatekeeper] +type=filter +module=gatekeeper +mode=learn + +# Listeners + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +socket=/tmp/rwsplit.sock + +# Servers + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.hartmut b/maxscale-system-test/cnf/maxscale.cnf.template.hartmut new file mode 100755 index 000000000..2f651e5e1 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.hartmut @@ -0,0 +1,93 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=500 +detect_replication_lag=1 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_replication_lag=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.hints b/maxscale-system-test/cnf/maxscale.cnf.template.hints new file mode 100755 index 000000000..8e45ac28b --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.hints @@ -0,0 +1,98 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.hints2 b/maxscale-system-test/cnf/maxscale.cnf.template.hints2 new file mode 100644 index 000000000..575c19b22 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.hints2 @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3,server4 +user=maxskysql +passwd= skysql + +[hints] +type=filter +module=hintfilter + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +filters=hints + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.insertstream b/maxscale-system-test/cnf/maxscale.cnf.template.insertstream new file mode 100755 index 000000000..d0b63d4e0 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.insertstream @@ -0,0 +1,47 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[insertstream] +type=filter +module=insertstream + +[RW Split Router] +type=service +router=readconnroute +servers=server1 +user=maxskysql +passwd=skysql +filters=insertstream + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.java_prep_stmt b/maxscale-system-test/cnf/maxscale.cnf.template.java_prep_stmt new file mode 100644 index 000000000..5002f8c5f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.java_prep_stmt @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[galera Monitor] +type=monitor +module=galeramon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +version_string=5.5.5-10.0.0-mxs + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +version_string=5.5.5-10.0.0-mxs + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +version_string=5.5.5-10.0.0-mxs + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.kerberos b/maxscale-system-test/cnf/maxscale.cnf.template.kerberos new file mode 100755 index 000000000..32b8b8243 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.kerberos @@ -0,0 +1,106 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock +authenticator=GSSAPIAuth +authenticator_options=principal_name=mariadb/maxscale.test@MAXSCALE.TEST + + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 +authenticator=GSSAPIAuth +authenticator_options=principal_name=mariadb/maxscale.test@MAXSCALE.TEST + + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 +authenticator=GSSAPIAuth +authenticator_options=principal_name=mariadb/maxscale.test@MAXSCALE.TEST + + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +authenticator=GSSAPIBackendAuth + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +authenticator=GSSAPIBackendAuth + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +authenticator=GSSAPIBackendAuth + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +authenticator=GSSAPIBackendAuth + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.lag b/maxscale-system-test/cnf/maxscale.cnf.template.lag new file mode 100755 index 000000000..4a9fec583 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.lag @@ -0,0 +1,104 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +detect_replication_lag=1 +user=maxskysql +passwd= skysql +backend_connect_timeout=10 +backend_read_timeout=10 +backend_write_timeout=10 + + +[hints] +type=filter +module=hintfilter + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +use_sql_variables_in=all +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +max_slave_replication_lag=20 +filters=hints + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load b/maxscale-system-test/cnf/maxscale.cnf.template.load new file mode 100755 index 000000000..aa6eb2ec9 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load_galera b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera new file mode 100755 index 000000000..c68d8cdea --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers1 b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers1 new file mode 100755 index 000000000..0087b964a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers1 @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers10 b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers10 new file mode 100755 index 000000000..553ed8cb6 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load_galera_pers10 @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load_pers1 b/maxscale-system-test/cnf/maxscale.cnf.template.load_pers1 new file mode 100755 index 000000000..6bb7af479 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load_pers1 @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.load_pers10 b/maxscale-system-test/cnf/maxscale.cnf.template.load_pers10 new file mode 100755 index 000000000..8c0949bce --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.load_pers10 @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.longblob b/maxscale-system-test/cnf/maxscale.cnf.template.longblob new file mode 100755 index 000000000..732891a5a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.longblob @@ -0,0 +1,94 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +backend_connect_timeout=10 +backend_read_timeout=10 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.longblob_filters b/maxscale-system-test/cnf/maxscale.cnf.template.longblob_filters new file mode 100755 index 000000000..2d508dd3f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.longblob_filters @@ -0,0 +1,205 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +backend_connect_timeout=10 +backend_read_timeout=10 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 +#filters=duplicate|hints|regex|ccrfilter|MaxRows|Masking|Cache|namedserverfilter|Database Firewall +filters=duplicate +#filters=hints|regex|ccrfilter|MaxRows|Masking|Cache|namedserverfilter|Database Firewall + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + +[duplicate] +type=filter +module=tee +service=RW Split Router Galera + +[hints] +type=filter +module=hintfilter + +[regex] +type=filter +module=regexfilter +match=fetch +replace=select + +[ccrfilter] +type=filter +module=ccrfilter +time=1000 +###count=3 +###match=t2 +###ignore=t1 + +[MaxRows] +type=filter +module=maxrows +max_resultset_rows=20 +max_resultset_size=900000000 +debug=3 + +[Masking] +type=filter +module=masking +rules=/###access_homedir###/masking_rules.json +large_payload=ignore + +[Cache] +type=filter +module=cache +storage=storage_inmemory +# NOTE: If you adjust the TTL values, ensure that test programs dependent +# NOTE: on the TTL are ajusted as well. +hard_ttl=10 +soft_ttl=5 +max_size=10M +rules=/###access_homedir###/cache_rules.json + +[namedserverfilter] +type=filter +module=namedserverfilter +match=SELECT +server=server2 + +[Database Firewall] +type=filter +module=dbfwfilter +rules=/###access_homedir###/rules/rules.txt +log_match=true +log_no_match=true + +[Galera Monitor] +type=monitor +module=galeramon +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +monitor_interval=100 +root_node_as_master=false + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.masking_mysqltest b/maxscale-system-test/cnf/maxscale.cnf.template.masking_mysqltest new file mode 100644 index 000000000..37f1d68db --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.masking_mysqltest @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=Masking + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[Masking] +type=filter +module=masking +rules=/###access_homedir###/masking_rules.json + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.master_only b/maxscale-system-test/cnf/maxscale.cnf.template.master_only new file mode 100755 index 000000000..de5df66d2 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.master_only @@ -0,0 +1,74 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.maxinfo b/maxscale-system-test/cnf/maxscale.cnf.template.maxinfo new file mode 100644 index 000000000..ca277fde2 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.maxinfo @@ -0,0 +1,105 @@ +[maxscale] +threads=4 + +[maxinfo] +type=service +router=maxinfo +user=maxuser +passwd=maxpwd + +[Maxinfo SQL Listener] +type=listener +service=maxinfo +protocol=MySQLClient +port=4000 + +[Maxinfo HTTP Listener] +type=listener +service=maxinfo +protocol=HTTPD +port=8080 + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxuser +passwd=maxpwd +monitor_interval=10000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.maxpasswd b/maxscale-system-test/cnf/maxscale.cnf.template.maxpasswd new file mode 100755 index 000000000..0d4c1122f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.maxpasswd @@ -0,0 +1,94 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.maxrows b/maxscale-system-test/cnf/maxscale.cnf.template.maxrows new file mode 100755 index 000000000..261315c2c --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.maxrows @@ -0,0 +1,101 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +filters=MaxRows + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[MaxRows] +type=filter +module=maxrows +max_resultset_rows=20 +max_resultset_size=900000000 +debug=3 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mm b/maxscale-system-test/cnf/maxscale.cnf.template.mm new file mode 100755 index 000000000..d7ad51c4d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mm @@ -0,0 +1,79 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mmmon +servers= server1, server2 +user=maxskysql +passwd= skysql +detect_stale_master=0 +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mm_mysqlmon b/maxscale-system-test/cnf/maxscale.cnf.template.mm_mysqlmon new file mode 100644 index 000000000..f8a49d36f --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mm_mysqlmon @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers= server1, server2, server3, server4 +user=maxskysql +passwd= skysql +detect_stale_master=0 +monitor_interval=1000 +multimaster=true + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs1045 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1045 new file mode 100644 index 000000000..b69ca8ed1 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1045 @@ -0,0 +1,93 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false +script=/bin/sh -c "echo hello world!" +events=master_down,server_down + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs1123 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1123 new file mode 100644 index 000000000..a10232875 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs1123 @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +connection_timeout=60 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs118 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs118 new file mode 100755 index 000000000..482e34a75 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs118 @@ -0,0 +1,98 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[MySQL Monitor 1] +type=monitor +module=galeramon +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs127 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs127 new file mode 100755 index 000000000..0287088f9 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs127 @@ -0,0 +1,90 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS,disable_sescmd_history=YES + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs361 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs361 new file mode 100755 index 000000000..8f85d4b8a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs361 @@ -0,0 +1,154 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=100 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 + +[Galera Monitor] +type=monitor +module=galeramon +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +monitor_interval=100 +root_node_as_master=false + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +#persistpoolmax=1 +#persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +#persistpoolmax=5 +#persistmaxtime=60 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +#persistpoolmax=10 +#persistmaxtime=60 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +#persistpoolmax=30 +#persistmaxtime=30 + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +#persistpoolmax=10 +#persistmaxtime=3660 + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +#persistpoolmax=15 +#persistmaxtime=30 + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +#persistpoolmax=19 +#persistmaxtime=0 + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +#persistpoolmax=0 +#persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs501 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs501 new file mode 100755 index 000000000..f02cb93c4 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs501 @@ -0,0 +1,99 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 +filters=duplicate + +[duplicate] +type=filter +module=tee +match=insert +service=Read Connection Router Master + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs548 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs548 new file mode 100755 index 000000000..f43430f78 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs548 @@ -0,0 +1,92 @@ +[maxscale] +threads=1 +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs559 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs559 new file mode 100755 index 000000000..1cd8f26eb --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs559 @@ -0,0 +1,94 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2 +user=maxskysql +passwd= skysql +monitor_interval=1000 +backend_connect_timeout=1 +backend_read_timeout=1 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +#[server3] +#type=server +#address=###node_server_IP_3### +#port=###node_server_port_3### +#protocol=MySQLBackend + +#[server4] +#type=server +#address=###node_server_IP_4### +#port=###node_server_port_4### +#protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs710_bad_socket b/maxscale-system-test/cnf/maxscale.cnf.template.mxs710_bad_socket new file mode 100755 index 000000000..36294a748 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs710_bad_socket @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock +socket=/var/lib/mysqld/mysql.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +socket=/var/lib/mysqld/mysql.sock + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs711_two_ports b/maxscale-system-test/cnf/maxscale.cnf.template.mxs711_two_ports new file mode 100755 index 000000000..fba1e0127 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs711_two_ports @@ -0,0 +1,98 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[RW Split Listener1] +type=listener +service=RW Split Router +protocol=MySQLClient +port=22 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_line_with_no_equal b/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_line_with_no_equal new file mode 100755 index 000000000..337209132 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_line_with_no_equal @@ -0,0 +1,93 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +line_no_equal + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_wierd_line b/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_wierd_line new file mode 100755 index 000000000..31a3bd49a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs720_wierd_line @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +hren=hren' +укпоукц=так_и_крешнуться_можно + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs722 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs722 new file mode 100644 index 000000000..760e939ea --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs722 @@ -0,0 +1,109 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +filters=QLA + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=QLA + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=QLA + +[QLA] +type=filter +module=qlafilter +filebase=/tmp/QueryLog + +[Debug Interface] +type=service +router=debugcli + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[Debug Listener] +type=listener +service=Debug Interface +protocol=telnetd +port=4442 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs799 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs799 new file mode 100755 index 000000000..b7f6e60ba --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs799 @@ -0,0 +1,90 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +socket=/var/run/mysqld/mysqld.sock +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs827_write_timeout b/maxscale-system-test/cnf/maxscale.cnf.template.mxs827_write_timeout new file mode 100755 index 000000000..8486e5244 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs827_write_timeout @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +connection_keepalive=5 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs874 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs874 new file mode 100644 index 000000000..83346052e --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs874 @@ -0,0 +1,82 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3 +router_options=disable_sescmd_history=false +user=maxskysql +passwd=skysql +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs922 b/maxscale-system-test/cnf/maxscale.cnf.template.mxs922 new file mode 100644 index 000000000..af71b9a81 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs922 @@ -0,0 +1,57 @@ +[maxscale] +threads=###threads### + +[mysql-monitor] +type=monitor +module=mysqlmon +###repl51### +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[rwsplit-service] +type=service +router= readwritesplit +user=maxskysql +passwd=skysql + +[read-connection-router-slave] +type=service +router=readconnroute +user=maxskysql +passwd=skysql + +[read-connection-router-master] +type=service +router=readconnroute +router_options=master +user=maxskysql +passwd=skysql + +[rwsplit-service-listener] +type=listener +service=rwsplit-service +protocol=MySQLClient +port=4006 + +[read-connection-router-slave-listener] +type=listener +service=read-connection-router-slave +protocol=MySQLClient +port=4009 + +[read-connection-router-master-listener] +type=listener +service=read-connection-router-master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mxs922_base b/maxscale-system-test/cnf/maxscale.cnf.template.mxs922_base new file mode 100644 index 000000000..d8a68b4b4 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mxs922_base @@ -0,0 +1,32 @@ +[maxscale] +threads=###threads### + +[rwsplit-service] +type=service +router=readwritesplit +weightby=weight +user=maxskysql +passwd=skysql + +[read-connection-router-slave] +type=service +router=readconnroute +user=maxskysql +passwd=skysql + +[read-connection-router-master] +type=service +router=readconnroute +router_options=master +user=maxskysql +passwd=skysql + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.mysqlmon_backup b/maxscale-system-test/cnf/maxscale.cnf.template.mysqlmon_backup new file mode 100644 index 000000000..b8c6a8276 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.mysqlmon_backup @@ -0,0 +1,86 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +master_failure_mode=fail_on_write + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.namedserverfilter b/maxscale-system-test/cnf/maxscale.cnf.template.namedserverfilter new file mode 100644 index 000000000..dba6a0419 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.namedserverfilter @@ -0,0 +1,96 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[namedserverfilter] +type=filter +module=namedserverfilter +match=SELECT +server=server2 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit + +# Mixing the order of slaves will make server3 the first slave server +servers=server1,server3,server2,server4 +user=maxskysql +passwd=skysql +filters=namedserverfilter + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.no_ses_cmd_store b/maxscale-system-test/cnf/maxscale.cnf.template.no_ses_cmd_store new file mode 100755 index 000000000..ac07e5cfc --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.no_ses_cmd_store @@ -0,0 +1,92 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS,disable_sescmd_history=true +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.nsfilter b/maxscale-system-test/cnf/maxscale.cnf.template.nsfilter new file mode 100644 index 000000000..002a26668 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.nsfilter @@ -0,0 +1,93 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql +filters=nsfilter + +[nsfilter] +type=filter +module=namedserverfilter +match=test +server=server1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1, server2, server3, server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.open_close b/maxscale-system-test/cnf/maxscale.cnf.template.open_close new file mode 100755 index 000000000..fb4dae219 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.open_close @@ -0,0 +1,101 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=100 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=100 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=100 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=100 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.pers_01 b/maxscale-system-test/cnf/maxscale.cnf.template.pers_01 new file mode 100755 index 000000000..df4e68712 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.pers_01 @@ -0,0 +1,154 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=100 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 + +[Galera Monitor] +type=monitor +module=galeramon +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +monitor_interval=100 +root_node_as_master=false + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=5 +persistmaxtime=60 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=60 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=30 +persistmaxtime=30 + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +persistpoolmax=15 +persistmaxtime=30 + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +persistpoolmax=19 +persistmaxtime=0 + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +persistpoolmax=0 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.regexfilter1 b/maxscale-system-test/cnf/maxscale.cnf.template.regexfilter1 new file mode 100644 index 000000000..d04e388c3 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.regexfilter1 @@ -0,0 +1,103 @@ +[maxscale] +threads=###threads### +log_warning=1 + +# Normal usage, should replace SELECT 123 with SELECT 0 +[regex1] +type=filter +module=regexfilter +match=SELECT [1-5]* +replace=SELECT 0 + +[Read Connection Router 1] +type=service +router=readconnroute +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=regex1 + +[Read Connection Listener 1] +type=listener +service=Read Connection Router 1 +protocol=MySQLClient +port=4006 + +# Test with a username, should not match +[regex2] +type=filter +module=regexfilter +match=SELECT [1-5]* +replace=SELECT 0 +user=bad_username + +[Read Connection Router 2] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=regex2 + +[Read Connection Listener 2] +type=listener +service=Read Connection Router 2 +protocol=MySQLClient +port=4008 + +# Test with a hostname, should not match +[regex3] +type=filter +module=regexfilter +match=SELECT [1-5]* +replace=SELECT 0 +source=127.0.0.1 + +[Read Connection Router 3] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +filters=regex3 + +[Read Connection Listener 3] +type=listener +service=Read Connection Router 3 +protocol=MySQLClient +port=4009 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.repl_lgc b/maxscale-system-test/cnf/maxscale.cnf.template.repl_lgc new file mode 100755 index 000000000..3e7477211 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.repl_lgc @@ -0,0 +1,92 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication b/maxscale-system-test/cnf/maxscale.cnf.template.replication new file mode 100755 index 000000000..bd3d9c0a2 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication @@ -0,0 +1,90 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication.bug539 b/maxscale-system-test/cnf/maxscale.cnf.template.replication.bug539 new file mode 100755 index 000000000..938e1fc65 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication.bug539 @@ -0,0 +1,55 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[hints] +type=filter +module=hintfilter + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +max_slave_connections=100% +use_sql_variables_in=master +router_options=slave_selection_criteria=LEAST_BEHIND_MASTER +user=maxskysql +passwd=skysql +filters=hints + +[Client Interface] +type=service +router=cli + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +socket=/tmp/rwsplit.sock + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication.one_slave b/maxscale-system-test/cnf/maxscale.cnf.template.replication.one_slave new file mode 100755 index 000000000..e84463849 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication.one_slave @@ -0,0 +1,79 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Router] +type=service +router=readwritesplit +max_slave_connections=1 +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager new file mode 100644 index 000000000..d685f2424 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager @@ -0,0 +1,69 @@ +[maxscale] +threads=###threads### +log_info=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_standalone_master=true +failcount=2 +allow_cluster_recovery=true + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +master_failure_mode=fail_on_write + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[CLI Network Listener] +type=listener +service=CLI +protocol=maxscaled +address=127.0.0.1 +port=6603 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_2nodes b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_2nodes new file mode 100644 index 000000000..2cb70f249 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_2nodes @@ -0,0 +1,56 @@ +[maxscale] +threads=4 + +[MySQL Monitor] +type=monitor +module=mysqlmon +servers=server1,server2 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_standalone_master=true +failcount=2 +allow_cluster_recovery=true + +[Traffic Router] +type=service +router=readconnroute +router_options=master +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Traffic Listener] +type=listener +service=Traffic Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[CLI Network Listener] +type=listener +service=CLI +protocol=maxscaled +address=127.0.0.1 +port=6603 + +[server1] +type=server +address=node-000 +port=3306 +protocol=MySQLBackend + +[server2] +type=server +address=node-001 +port=3306 +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_3nodes b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_3nodes new file mode 100644 index 000000000..6c65d805d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.replication_manager_3nodes @@ -0,0 +1,61 @@ +[maxscale] +threads=4 + +[MySQL-Monitor] +type=monitor +module=mysqlmon +servers=server1,server2,server3 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_standalone_master=true +failcount=2 +allow_cluster_recovery=true + +[Traffic-Router] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3 +user=maxskysql +passwd=skysql + +[Traffic-Listener] +type=listener +service=Traffic-Router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI-Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[CLI-Network-Listener] +type=listener +service=CLI +protocol=maxscaled +port=6603 + +[server1] +type=server +address=node-000 +port=3306 +protocol=MySQLBackend + +[server2] +type=server +address=node-001 +port=3306 +protocol=MySQLBackend + +[server3] +type=server +address=node-002 +port=3306 +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_multi_stmt b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_multi_stmt new file mode 100644 index 000000000..52b3f29ba --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_multi_stmt @@ -0,0 +1,92 @@ +[maxscale] +threads=###threads### +log_info=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2 +user=maxskysql +passwd=skysql +router_options=strict_multi_stmt=true + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_read_only_trx b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_read_only_trx new file mode 100755 index 000000000..500764e3c --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_read_only_trx @@ -0,0 +1,87 @@ +[maxscale] +threads=###threads### + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2 +user=maxskysql +passwd=skysql + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options=slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_readonly b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_readonly new file mode 100644 index 000000000..ab510030a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.rwsplit_readonly @@ -0,0 +1,88 @@ +[maxscale] +threads=###threads### +log_info=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=master_failure_mode=fail_instantly + +[Read Connection Router Slave] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=master_failure_mode=error_on_write + +[Read Connection Router Master] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +router_options=master_failure_mode=fail_on_write + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.schemarouter_duplicate_db b/maxscale-system-test/cnf/maxscale.cnf.template.schemarouter_duplicate_db new file mode 100755 index 000000000..03610cf67 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.schemarouter_duplicate_db @@ -0,0 +1,59 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Sharding router] +type=service +router=schemarouter +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +auth_all_servers=1 + +[Sharding Listener] +type=listener +service=Sharding router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.script b/maxscale-system-test/cnf/maxscale.cnf.template.script new file mode 100755 index 000000000..026748347 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.script @@ -0,0 +1,144 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +script=/###access_homedir###/script/script.sh --event=$EVENT --initiator=$INITIATOR --nodelist=$NODELIST +events=master_down,master_up, slave_up, server_down ,server_up,lost_master,lost_slave,new_master,new_slave +monitor_interval=1000 + +[Galera Monitor] +type=monitor +module=galeramon +servers=gserver1,gserver2,gserver3,gserver4 +script=/###access_homedir###/script/script.sh --event=$EVENT --initiator=$INITIATOR --nodelist=$NODELIST +events=master_down,master_up, slave_up, server_down ,server_up,lost_master,lost_slave,new_master,new_slave,server_down,server_up,synced_down,synced_up +disable_master_role_setting=true +root_node_as_master=false +user=maxskysql +passwd=skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[RW Split Router Galera] +type=service +router= readwritesplit +servers=gserver1,gserver2,gserver3,gserver4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[RW Split Listener Galera] +type=listener +service=RW Split Router Galera +protocol=MySQLClient +port=4016 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.session_limits b/maxscale-system-test/cnf/maxscale.cnf.template.session_limits new file mode 100755 index 000000000..68f9fac21 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.session_limits @@ -0,0 +1,91 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +connection_timeout=30 +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS,max_sescmd_history=10,disable_sescmd_history=false + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog new file mode 100755 index 000000000..c85f33b95 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog @@ -0,0 +1,38 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1 + + +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in new file mode 100755 index 000000000..c85f33b95 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog.in @@ -0,0 +1,38 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1 + + +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1 new file mode 100755 index 000000000..473832a71 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog1 @@ -0,0 +1,40 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2 new file mode 100755 index 000000000..2d41e8396 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog2 @@ -0,0 +1,41 @@ +[maxscale] +threads=###threads### +log_warning=1 +log_trace=1 + +[Binlog_Service] +type=service +router=binlogrouter +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync new file mode 100755 index 000000000..4fb918415 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync @@ -0,0 +1,41 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=1,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0 new file mode 100755 index 000000000..13a459c20 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss0 @@ -0,0 +1,41 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=0,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1 new file mode 100755 index 000000000..93098385d --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs0_ss1 @@ -0,0 +1,40 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=1,transaction_safety=0,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0 b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0 new file mode 100755 index 000000000..8e78d9126 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_semisync_txs1_ss0 @@ -0,0 +1,40 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,semisync=0,transaction_safety=1,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe new file mode 100755 index 000000000..33957920a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.setup_binlog_tx_safe @@ -0,0 +1,41 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Binlog_Service] +type=service +router=binlogrouter +#servers=master +user=skysql +passwd=skysql +version_string=5.6.15-log +#router_options=server-id=3,user=repl,password=repl,master-id=1 +router_options=server-id=3,user=repl,password=repl,longburst=500,heartbeat=30,binlogdir=/var/lib/maxscale/Binlog_Service,transaction_safety=1,mariadb10-compatibility=1 + + + +# +[Binlog Listener] +type=listener +service=Binlog_Service +protocol=MySQLClient +port=5306 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[master] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.sharding b/maxscale-system-test/cnf/maxscale.cnf.template.sharding new file mode 100755 index 000000000..3e775f747 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.sharding @@ -0,0 +1,62 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Sharding router] +type=service +router=schemarouter +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +auth_all_servers=1 +ignore_databases_regex=.* + +[Sharding Listener] +type=listener +service=Sharding router +protocol=MySQLClient +port=4006 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.show_monitor_crash b/maxscale-system-test/cnf/maxscale.cnf.template.show_monitor_crash new file mode 100755 index 000000000..bf4956aa7 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.show_monitor_crash @@ -0,0 +1,93 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql_bad +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.simplejavatest b/maxscale-system-test/cnf/maxscale.cnf.template.simplejavatest new file mode 100644 index 000000000..1ca3a6713 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.simplejavatest @@ -0,0 +1,92 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_GLOBAL_CONNECTIONS +max_slave_connections=1 +version_string=5.5.5-10.0.0-mxs + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +version_string=5.5.5-10.0.0-mxs + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +version_string=5.5.5-10.0.0-mxs + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers1 b/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers1 new file mode 100755 index 000000000..8b1ef3abd --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers1 @@ -0,0 +1,100 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers10 b/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers10 new file mode 100755 index 000000000..d4f8ce3e8 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.sql_queries_pers10 @@ -0,0 +1,100 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.ssl b/maxscale-system-test/cnf/maxscale.cnf.template.ssl new file mode 100755 index 000000000..41a0bb71a --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.ssl @@ -0,0 +1,95 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=maxskysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load b/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load new file mode 100755 index 000000000..9f6faa5f5 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load @@ -0,0 +1,106 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1, server2,server3 ,server4 +user=maxskysql +passwd= skysql + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load_galera b/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load_galera new file mode 100755 index 000000000..477816318 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.ssl_load_galera @@ -0,0 +1,106 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[Galera Monitor] +type=monitor +module=galeramon +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +root_node_as_master=false + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql +max_slave_connections=100% +router_options=slave_selection_criteria=LEAST_CURRENT_OPERATIONS + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=maxskysql +passwd=skysql + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 +#socket=/tmp/rwsplit.sock +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Listener Slave] +type=listener +service=Read Connection Router Slave +protocol=MySQLClient +port=4009 +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 +ssl=required +ssl_cert=/###access_homedir###/certs/server-cert.pem +ssl_key=/###access_homedir###/certs/server-key.pem +ssl_ca_cert=/###access_homedir###/certs/ca.pem +ssl_version=TLSv12 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled + +socket=default + +[server1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend + diff --git a/maxscale-system-test/cnf/maxscale.cnf.template.user_cache b/maxscale-system-test/cnf/maxscale.cnf.template.user_cache new file mode 100644 index 000000000..52f474f30 --- /dev/null +++ b/maxscale-system-test/cnf/maxscale.cnf.template.user_cache @@ -0,0 +1,89 @@ +[maxscale] +threads=###threads### +log_warning=1 + +[MySQL Monitor] +type=monitor +module=mysqlmon +###repl51### +servers= server1,server2,server3,server4 +user=maxskysql +passwd= skysql +monitor_interval=1000 +detect_stale_master=false + +# +# The 'testuser'@'%' user will be created as a part of the test +# + +[RW Split Router] +type=service +router= readwritesplit +servers=server1,server2,server3,server4 +user=testuser +passwd=testpasswd + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=testuser +passwd=testpasswd + +[RW Split Listener] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4006 + +# +# This listener will have its own user cache +# + +[Read Connection Listener Slave] +type=listener +service=RW Split Router +protocol=MySQLClient +port=4009 + +[Read Connection Listener Master] +type=listener +service=Read Connection Router Master +protocol=MySQLClient +port=4008 + +[CLI] +type=service +router=cli + +[CLI Listener] +type=listener +service=CLI +protocol=maxscaled +#address=localhost +socket=default + +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend diff --git a/maxscale-system-test/config_operations.cpp b/maxscale-system-test/config_operations.cpp new file mode 100644 index 000000000..dedd2c8b1 --- /dev/null +++ b/maxscale-system-test/config_operations.cpp @@ -0,0 +1,222 @@ +#include "config_operations.h" + +// The configuration should use these names for the services, listeners and monitors +#define SERVICE_NAME1 "rwsplit-service" +#define SERVICE_NAME2 "read-connection-router-master" +#define SERVICE_NAME3 "read-connection-router-slave" +#define LISTENER_NAME1 "rwsplit-service-listener" +#define LISTENER_NAME2 "read-connection-router-master-listener" +#define LISTENER_NAME3 "read-connection-router-slave-listener" + +struct +{ + const char *service; + const char *listener; + int port; +} services[] +{ + {SERVICE_NAME1, LISTENER_NAME1, 4006}, + {SERVICE_NAME2, LISTENER_NAME2, 4008}, + {SERVICE_NAME3, LISTENER_NAME3, 4009} +}; + +Config::Config(TestConnections* parent): + test_(parent) +{ +} + +Config::~Config() +{ +} + +void Config::add_server(int num) +{ + test_->tprintf("Adding the servers"); + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME1, num); + test_->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME2, num); + test_->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME3, num); + + for (auto& a : created_monitors_) + { + test_->ssh_maxscale(true, "maxadmin add server server%d %s", num, a.c_str()); + } + + test_->stop_timeout(); +} + +void Config::remove_server(int num) +{ + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin remove server server%d " SERVICE_NAME1, num); + test_->ssh_maxscale(true, "maxadmin remove server server%d " SERVICE_NAME2, num); + test_->ssh_maxscale(true, "maxadmin remove server server%d " SERVICE_NAME3, num); + + for (auto& a : created_monitors_) + { + test_->ssh_maxscale(true, "maxadmin remove server server%d %s", num, a.c_str()); + } + + test_->stop_timeout(); +} + +void Config::add_created_servers(const char *object) +{ + for (auto a : created_servers_) + { + test_->ssh_maxscale(true, "maxadmin add server server%d %s", a, object); + } +} + +void Config::destroy_server(int num) +{ + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin destroy server server%d", num); + created_servers_.erase(num); + test_->stop_timeout(); +} + +void Config::create_server(int num) +{ + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin create server server%d %s %d", + num, test_->repl->IP[num], test_->repl->port[num]); + created_servers_.insert(num); + test_->stop_timeout(); +} + +void Config::alter_server(int num, const char *key, const char *value) +{ + test_->ssh_maxscale(true, "maxadmin alter server server%d %s=%s", num, key, value); +} + +void Config::alter_server(int num, const char *key, int value) +{ + test_->ssh_maxscale(true, "maxadmin alter server server%d %s=%d", num, key, value); +} + +void Config::alter_server(int num, const char *key, float value) +{ + test_->ssh_maxscale(true, "maxadmin alter server server%d %s=%f", num, key, value); +} + +void Config::create_monitor(const char *name, const char *module, int interval) +{ + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin create monitor %s %s", name, module); + alter_monitor(name, "monitor_interval", interval); + alter_monitor(name, "user", test_->maxscale_user); + alter_monitor(name, "password", test_->maxscale_password); + test_->ssh_maxscale(true, "maxadmin restart monitor %s", name); + test_->stop_timeout(); + + created_monitors_.insert(std::string(name)); +} + +void Config::alter_monitor(const char* name, const char *key, const char *value) +{ + test_->ssh_maxscale(true, "maxadmin alter monitor %s %s=%s", name, key, value); +} + +void Config::alter_monitor(const char* name, const char *key, int value) +{ + test_->ssh_maxscale(true, "maxadmin alter monitor %s %s=%d", name, key, value); +} + +void Config::alter_monitor(const char* name, const char *key, float value) +{ + test_->ssh_maxscale(true, "maxadmin alter monitor %s %s=%f", name, key, value); +} + +void Config::start_monitor(const char *name) +{ + test_->ssh_maxscale(true, "maxadmin restart monitor %s", name); +} + +void Config::destroy_monitor(const char *name) +{ + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin destroy monitor %s", name); + test_->stop_timeout(); + created_monitors_.erase(std::string(name)); +} + +void Config::restart_monitors() +{ + for (auto& a : created_monitors_) + { + test_->ssh_maxscale(true, "maxadmin shutdown monitor \"%s\"", a.c_str()); + test_->ssh_maxscale(true, "maxadmin restart monitor \"%s\"", a.c_str()); + } +} + +void Config::create_listener(Config::Service service) +{ + int i = static_cast(service); + + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin create listener %s %s default %d", + services[i].service, + services[i].listener, + services[i].port); + test_->stop_timeout(); +} + +void Config::create_ssl_listener(Config::Service service) +{ + int i = static_cast(service); + + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin create listener %s %s default %d default default default " + "/home/vagrant/certs/server-key.pem " + "/home/vagrant/certs/server-cert.pem " + "/home/vagrant/certs/ca.pem ", + services[i].service, + services[i].listener, + services[i].port); + test_->stop_timeout(); +} + +void Config::destroy_listener(Config::Service service) +{ + int i = static_cast(service); + + test_->set_timeout(120); + test_->ssh_maxscale(true, "maxadmin destroy listener %s %s", + services[i].service, services[i].listener); + test_->stop_timeout(); +} + +void Config::create_all_listeners() +{ + create_listener(SERVICE_RWSPLIT); + create_listener(SERVICE_RCONN_SLAVE); + create_listener(SERVICE_RCONN_MASTER); +} + +void Config::reset() +{ + /** Make sure the servers exist before checking that connectivity is OK */ + for (int i = 0; i < test_->repl->N; i++) + { + if (created_servers_.find(i) == created_servers_.end()) + { + create_server(i); + add_server(i); + } + } +} + +bool Config::check_server_count(int expected) +{ + bool rval = true; + + if (test_->ssh_maxscale(true, "test \"`maxadmin list servers|grep 'server[0-9]'|wc -l`\" == \"%d\"", + expected)) + { + test_->add_result(1, "Number of servers is not %d.", expected); + rval = false; + } + + return rval; +} diff --git a/maxscale-system-test/config_operations.h b/maxscale-system-test/config_operations.h new file mode 100644 index 000000000..ced7db799 --- /dev/null +++ b/maxscale-system-test/config_operations.h @@ -0,0 +1,142 @@ +#pragma once + +#include "testconnections.h" +#include +#include + +class Config +{ +public: + Config(TestConnections* parent); + ~Config(); + + /** + * Service identifiers for listener creation + */ + enum Service + { + SERVICE_RWSPLIT = 0, + SERVICE_RCONN_SLAVE = 1, + SERVICE_RCONN_MASTER = 2 + }; + + /** + * Add a server to all services and monitors + * + * @param num Backend number + */ + void add_server(int num); + + /** + * Add all created servers to an object + * + * @param object Object to add servers to + */ + void add_created_servers(const char *object); + + /** + * Remove a server + * + * @param num Backend number + */ + void remove_server(int num); + + /** + * Create a new server + * + * @param num Backend number + */ + void create_server(int num); + + /** + * Alter a server + * + * @param num Backend number + * @param key Key to alter + * @oaram value Value for @c key, empty string for no value + */ + void alter_server(int num, const char *key, const char *value); + void alter_server(int num, const char *key, int value); + void alter_server(int num, const char *key, float value); + + /** + * Destroy a server + * @param num Backend number + */ + void destroy_server(int num); + + /** + * Test that server count is at the expected amount + * @param expected How many servers are expected to exist + * @return True if the number of servers is @c expected + */ + bool check_server_count(int expected); + + /** + * Create the monitor + * @param type The name of the monitor module to use + * @param interval Monitoring interval + */ + void create_monitor(const char *name, const char *module, int interval = 1000); + + /** + * Start the created monitor + */ + void start_monitor(const char *name); + + /** + * Alter a monitor + * @param key Key to alter + * @oaram value Value for @c key, empty string for no value + */ + void alter_monitor(const char *name, const char *key, const char *value); + void alter_monitor(const char *name, const char *key, int value); + void alter_monitor(const char *name, const char *key, float value); + + /** + * Destroy the monitor + */ + void destroy_monitor(const char *name); + + /** + * Restart all created monitors + */ + void restart_monitors(); + + /** + * Create a listener + * + * @param service Service where listener is created + */ + void create_listener(Service service); + + + /** + * Create a listener with SSL enabled + * + * @param service Service where SSL listener is created + */ + void create_ssl_listener(Service service); + + /** + * Destroy a listener + * + * @param service Service whose listener is destroyed + */ + void destroy_listener(Service service); + + /** + * Create all basic listeners + */ + void create_all_listeners(); + + /** + * Reset the configuration to a standard state + */ + void reset(); + +private: + TestConnections *test_; + std::set created_servers_; + std::set created_monitors_; +}; diff --git a/maxscale-system-test/config_test.cpp b/maxscale-system-test/config_test.cpp new file mode 100644 index 000000000..fca2f88f9 --- /dev/null +++ b/maxscale-system-test/config_test.cpp @@ -0,0 +1,48 @@ +/** +* Bad configuration test +*/ + + +#include +#include +#include "testconnections.h" + +const char *bad_configs[] = +{ + "bug359", + "bug495", + "bug526", + "bug479", + "bug493", + "bad_ssl", + "mxs710_bad_socket", + "mxs711_two_ports", + "mxs720_line_with_no_equal", + "mxs720_wierd_line", + "mxs799", + NULL +}; + +int main(int argc, char **argv) +{ + TestConnections *test = new TestConnections(argc, argv); + int rval = 0; + + test->stop_maxscale(); + + for (int i = 0; bad_configs[i]; i++) + { + printf("Testing %s...\n", bad_configs[i]); + if (test->test_bad_config(bad_configs[i])) + { + printf("FAILED\n"); + rval++; + } + else + { + printf("SUCCESS\n"); + } + } + + return rval; +} diff --git a/maxscale-system-test/connect_to_nonexisting_db.cpp b/maxscale-system-test/connect_to_nonexisting_db.cpp new file mode 100644 index 000000000..bf7199db8 --- /dev/null +++ b/maxscale-system-test/connect_to_nonexisting_db.cpp @@ -0,0 +1,66 @@ +/** + * @file connect_to_nonexisting_db.cpp Tries to connect to non existing DB, expects no crash + */ + +// some relations to bug#425 + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->tprintf("Connecting to RWSplit\n"); + Test->conn_rwsplit = open_conn_no_db(Test->rwsplit_port, Test->maxscale_IP, Test->maxscale_user, + Test->maxscale_password, Test->ssl); + if (Test->conn_rwsplit == NULL) + { + Test->add_result(1, "Error connecting to MaxScale\n"); + delete Test; + return 1; + } + Test->tprintf("Removing 'test' DB\n"); + execute_query(Test->conn_rwsplit, (char *) "DROP DATABASE IF EXISTS test;"); + Test->tprintf("Closing connections and waiting 5 seconds\n"); + Test->close_rwsplit(); + sleep(5); + + Test->tprintf("Connection to non-existing DB (all routers)\n"); + Test->connect_maxscale(); + Test->close_maxscale_connections(); + + Test->tprintf("Connecting to RWSplit again to recreate 'test' db\n"); + Test->conn_rwsplit = open_conn_no_db(Test->rwsplit_port, Test->maxscale_IP, Test->maxscale_user, + Test->maxscale_password, Test->ssl); + if (Test->conn_rwsplit == NULL) + { + printf("Error connecting to MaxScale\n"); + delete Test; + return 1; + } + + Test->tprintf("Creating and selecting 'test' DB\n"); + Test->try_query(Test->conn_rwsplit, (char *) "CREATE DATABASE test; USE test"); + Test->tprintf("Creating 't1' table\n"); + Test->add_result(create_t1(Test->conn_rwsplit), "Error creation 't1'\n"); + Test->close_rwsplit(); + + Test->tprintf("Reconnectiong\n"); + Test->add_result(Test->connect_maxscale(), "error connection to Maxscale\n"); + Test->tprintf("Trying simple operations with t1 \n"); + Test->try_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_rwsplit, (char *) "SELECT * FROM t1;", 1), + "Error execution SELECT * FROM t1;\n"); + + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/connection_limit.cpp b/maxscale-system-test/connection_limit.cpp new file mode 100644 index 000000000..46294ba52 --- /dev/null +++ b/maxscale-system-test/connection_limit.cpp @@ -0,0 +1,60 @@ +/** + * @file connection_limit.cpp connection_limit check if max_connections parameter works + * + * - Maxscale.cnf contains max_connections=10 for RWSplit, max_connections=20 for ReadConn master and max_connections=25 for ReadConn slave + * - create max num of connections and check tha N+1 connection fails + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int check_max_conn(int router, int max_conn, TestConnections * Test) +{ + MYSQL * conn[max_conn + 1]; + + int i; + for (i = 0; i < max_conn; i++) + { + conn[i] = open_conn(Test->ports[router], Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, + Test->ssl); + if (mysql_errno(conn[i]) != 0) + { + Test->add_result(1, "Connection %d failed, error is %s\n", i, mysql_error(conn[i])); + } + } + conn[max_conn] = open_conn(Test->ports[router], Test->maxscale_IP, Test->maxscale_user, + Test->maxscale_password, Test->ssl); + if (mysql_errno(conn[i]) != 1040) + { + Test->add_result(1, "Max_xonnections reached, but error is not 1040, it is %d %s\n", mysql_errno(conn[i]), + mysql_error(conn[i])); + } + for (i = 0; i < max_conn; i++) + { + mysql_close(conn[i]); + } +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->tprintf("Trying 11 connections with RWSplit\n"); + check_max_conn(0, 10, Test); + Test->tprintf("Trying 21 connections with Readconn master\n"); + check_max_conn(1, 20, Test); + Test->tprintf("Trying 26 connections with Readconnn slave\n"); + check_max_conn(2, 25, Test); + + sleep(10); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/copy_logs.sh b/maxscale-system-test/copy_logs.sh new file mode 100755 index 000000000..198157bb4 --- /dev/null +++ b/maxscale-system-test/copy_logs.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# $1 - test name +# $2 - time mark (in case of periodic logs copying) +set -x + +if [ -z $1 ]; then + echo "Test name missing" + logs_dir="LOGS/nomane" +else + if [ -z $2 ]; then + logs_dir="LOGS/$1" + else + logs_dir="LOGS/$1/$2" + fi +# rm -rf $logs_dir +fi + + +echo "Creating log dir in workspace $logs_dir" +mkdir -p $logs_dir +if [ $? -ne 0 ]; then + echo "Error creating log dir" +fi + +export maxscale_sshkey=$maxscale_keyfile +echo "log_dir: $logs_dir" +echo "maxscale_sshkey: $maxscale_sshkey" +echo "maxscale_IP: $maxscale_IP" + +if [ $maxscale_IP != "127.0.0.1" ] ; then + ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP "rm -rf logs; mkdir logs; $maxscale_access_sudo cp $maxscale_log_dir/*.log logs/; $maxscale_access_sudo cp /tmp/core* logs; $maxscale_access_sudo chmod 777 -R logs" + scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:logs/* $logs_dir + if [ $? -ne 0 ]; then + echo "Error copying Maxscale logs" + fi + #scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:/tmp/core* $logs_dir + #scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:/root/core* $logs_dir + scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:$maxscale_cnf $logs_dir + chmod a+r $logs_dir/* +else + sudo cp $maxscale_log_dir/*.log $logs_dir + sudo cp /tmp/core* $logs_dir + sudo cp $maxscale_cnf $logs_dir + sudo chmod a+r $logs_dir/* +fi + +if [ -z $logs_publish_dir ] ; then + echo "logs are in workspace only" +else + echo "Logs publish dir is $logs_publish_dir" + rsync -a --no-o --no-g LOGS $logs_publish_dir +fi + diff --git a/maxscale-system-test/crash_out_of_files.cpp b/maxscale-system-test/crash_out_of_files.cpp new file mode 100644 index 000000000..62bf8d8ec --- /dev/null +++ b/maxscale-system-test/crash_out_of_files.cpp @@ -0,0 +1,52 @@ +/** + * @file crash_ot_of_files.cpp Tries to open to many connections, expect no crash + * - set global max_connections = 20 + * - create load on RWSplit using big number of threads (e.g. 100) + * - check that no backends are disconnected with error ""refresh rate limit exceeded" + */ + +#include "testconnections.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +#include "big_load.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(20); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 20;"); + + long int i1, i2; + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + + Test->tprintf("Start load\n"); + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], 100, Test, &i1, &i2, 0, false, false); + + Test->tprintf("restoring nodes\n"); + Test->set_timeout(60); + Test->repl->connect(); + + for (int i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Trying to flush node %d\n", i); + Test->add_result(execute_query(Test->repl->nodes[i], (char *) "flush hosts;"), "node %i flush failed\n", i); + Test->tprintf("Trying to set max_connections for node %d\n", i); + Test->add_result(execute_query(Test->repl->nodes[i], (char *) "set global max_connections = 151;"), + "set max_connections failed for node %d\n", i); + } + + Test->repl->close_connections(); + Test->stop_timeout(); + + Test->check_log_err((char *) "refresh rate limit exceeded", false); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/crash_out_of_files_galera.cpp b/maxscale-system-test/crash_out_of_files_galera.cpp new file mode 100644 index 000000000..ec733270b --- /dev/null +++ b/maxscale-system-test/crash_out_of_files_galera.cpp @@ -0,0 +1,54 @@ +/** + * @file crash_ot_of_files_galera.cpp Tries to open to many connections, expect no crash, Galera backend + * - set global max_connections = 20 + * - create load on RWSplit using big number of threads (e.g. 100) + * - check that no backends are disconnected with error ""refresh rate limit exceeded" + */ + +#include "testconnections.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +#include "big_load.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + int q; + long int i1, i2; + + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + + Test->galera->execute_query_all_nodes((char *) "set global max_connections = 20;"); + + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], 100, Test, &i1, &i2, 0, true, false); + sleep(10); + //load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], 1000, Test, &i1, &i2, 0, true, false); + //sleep(10); + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], 100, Test, &i1, &i2, 0, true, false); + + Test->set_timeout(20); + Test->galera->connect(); + for (int i = 0; i < Test->galera->N; i++) + { + execute_query(Test->galera->nodes[i], (char *) "flush hosts;"); + execute_query(Test->galera->nodes[i], (char *) "set global max_connections = 151;"); + } + Test->galera->close_connections(); + + Test->check_log_err((char *) "refresh rate limit exceeded", false); + + Test->galera->execute_query_all_nodes((char *) "set global max_connections = 100;"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/create.sql b/maxscale-system-test/create.sql new file mode 100644 index 000000000..11f243118 --- /dev/null +++ b/maxscale-system-test/create.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS test.large_event; +CREATE TABLE test.large_event(id INT, data LONGBLOB); diff --git a/maxscale-system-test/create_rds.cpp b/maxscale-system-test/create_rds.cpp new file mode 100644 index 000000000..2c7b3da63 --- /dev/null +++ b/maxscale-system-test/create_rds.cpp @@ -0,0 +1,17 @@ +/** + * @file create_rds.cpp Creates RDS Aurora cluster with 4 instances + * Creates VPC, subnets, subnets group, internet gateway, routing table, routes, configure security group + * In case of any error tries to remove all created stuff + */ + +#include +#include +#include "testconnections.h" +#include +#include "rds_vpc.h" + +int main(int argc, char *argv[]) +{ + RDS * cluster = new RDS((char *) "auroratest"); + return cluster->create_rds_db(4); +} diff --git a/maxscale-system-test/create_rds.sh b/maxscale-system-test/create_rds.sh new file mode 100755 index 000000000..a50de508d --- /dev/null +++ b/maxscale-system-test/create_rds.sh @@ -0,0 +1,31 @@ +set -x +vpc_id=`aws ec2 create-vpc --cidr-block 172.30.0.0/16 | grep "VpcId" | sed 's/\"VpcId\": \"//' | sed 's/\"//' | sed 's/ //g' | sed 's/,//'` +echo $vpc_id > vpc_id +subnet_id1=`aws ec2 create-subnet --cidr-block 172.30.0.0/24 --availability-zone eu-west-1b --vpc-id $vpc_id | grep "SubnetId" | sed 's/\"SubnetId\": \"//' | sed 's/\"//' | sed 's/ //g' | sed 's/,//'` +subnet_id2=`aws ec2 create-subnet --cidr-block 172.30.1.0/24 --availability-zone eu-west-1a --vpc-id $vpc_id | grep "SubnetId" | sed 's/\"SubnetId\": \"//' | sed 's/\"//' | sed 's/ //g' | sed 's/,//'` +aws rds create-db-subnet-group --db-subnet-group-name maxscaleaurora --db-subnet-group-description maxscale --subnet-ids $subnet_id1 $subnet_id2 + +gw_id=`aws ec2 create-internet-gateway | grep "InternetGatewayId" | sed 's/\"InternetGatewayId\": \"//' | sed 's/\"//' | sed 's/ //g' | sed 's/,//'` +aws ec2 attach-internet-gateway --internet-gateway-id $gw_id --vpc-id $vpc_id + +aws ec2 modify-vpc-attribute --enable-dns-support --vpc-id $vpc_id +aws ec2 modify-vpc-attribute --enable-dns-hostnames --vpc-id $vpc_id + +aws ec2 modify-subnet-attribute --map-public-ip-on-launch --subnet-id $subnet_id1 +aws ec2 modify-subnet-attribute --map-public-ip-on-launch --subnet-id $subnet_id2 + +# works only if there is only one route table +routetab_id=`aws ec2 describe-route-tables | grep "RouteTableId" | sed 's/\"RouteTableId\": \"//' | sed 's/\"//' | sed 's/ //g' | grep "," | sed 's/,//'` + +aws ec2 create-route --route-table-id $routetab_id --gateway-id $gw_id --destination-cidr-block 0.0.0.0/0 + + +aws rds create-db-cluster --database-name=test --engine=aurora --master-username=skysql --master-user-password=skysqlrds --db-cluster-identifier=auroratest --db-subnet-group-name=maxscaleaurora +aws rds create-db-instance --db-cluster-identifier=auroratest --engine=aurora --db-instance-class=db.t2.medium --publicly-accessible --db-instance-identifier=node000 +aws rds create-db-instance --db-cluster-identifier=auroratest --engine=aurora --db-instance-class=db.t2.medium --publicly-accessible --db-instance-identifier=node001 +aws rds create-db-instance --db-cluster-identifier=auroratest --engine=aurora --db-instance-class=db.t2.medium --publicly-accessible --db-instance-identifier=node002 +aws rds create-db-instance --db-cluster-identifier=auroratest --engine=aurora --db-instance-class=db.t2.medium --publicly-accessible --db-instance-identifier=node003 + + +secgr_id=`aws rds describe-db-instances --db-instance-identifier node000 | grep "VpcSecurityGroupId" | sed 's/\"VpcSecurityGroupId\": \"//' | sed 's/\"//' | sed 's/ //g' | sed 's/,//'` +aws ec2 authorize-security-group-ingress --group-id $secgr_id --protocol tcp --port 3306 --cidr 0.0.0.0/0 diff --git a/maxscale-system-test/create_user.sh b/maxscale-system-test/create_user.sh new file mode 100755 index 000000000..d10f9a55e --- /dev/null +++ b/maxscale-system-test/create_user.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +mysql -u root --force $1 <& /dev/null + +DROP USER '$node_user'@'%'; +CREATE USER '$node_user'@'%' IDENTIFIED BY '$node_password'; +GRANT ALL PRIVILEGES ON *.* TO '$node_user'@'%' WITH GRANT OPTION; + +DROP USER 'repl'@'%'; +CREATE USER 'repl'@'%' IDENTIFIED BY 'repl'; +GRANT ALL ON *.* TO 'repl'@'%' WITH GRANT OPTION; + +DROP USER 'repl'@'localhost'; +CREATE USER 'repl'@'localhost' IDENTIFIED BY 'repl'; +GRANT ALL ON *.* TO 'repl'@'localhost' WITH GRANT OPTION; + +DROP USER 'skysql'@'%'; +CREATE USER 'skysql'@'%' IDENTIFIED BY 'skysql'; +GRANT ALL ON *.* TO 'skysql'@'%' WITH GRANT OPTION; + +DROP USER 'skysql'@'localhost'; +CREATE USER 'skysql'@'localhost' IDENTIFIED BY 'skysql'; +GRANT ALL ON *.* TO 'skysql'@'localhost' WITH GRANT OPTION; + +DROP USER 'maxskysql'@'%'; +CREATE USER 'maxskysql'@'%' IDENTIFIED BY 'skysql'; +GRANT ALL ON *.* TO 'maxskysql'@'%' WITH GRANT OPTION; + +DROP USER 'maxskysql'@'localhost'; +CREATE USER 'maxskysql'@'localhost' IDENTIFIED BY 'skysql'; +GRANT ALL ON *.* TO 'maxskysql'@'localhost' WITH GRANT OPTION; + +EOF diff --git a/maxscale-system-test/create_user_galera.sh b/maxscale-system-test/create_user_galera.sh new file mode 100755 index 000000000..d82876cff --- /dev/null +++ b/maxscale-system-test/create_user_galera.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Wait until the node is ready + +for ((i=0;i<100;i++)) +do + mysql -ss -u root $1 -e 'show status like "wsrep_ready"' | grep 'ON' && break || sleep 1 +done + +# The removal of the anonymous ''@'localhost' user is done in a somewhat crude +# way. The proper way would be to do a secure installation or drop the users +# with DROP USER statements. + +mysql -u root --force < +#include +#include "testconnections.h" +#include +#include "rds_vpc.h" + +int main(int argc, char *argv[]) +{ + RDS * cluster = new RDS((char *) "auroratest"); + cluster->delete_rds_cluster(); +} diff --git a/maxscale-system-test/demo_prep1.sh b/maxscale-system-test/demo_prep1.sh new file mode 100755 index 000000000..ae930400f --- /dev/null +++ b/maxscale-system-test/demo_prep1.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# The following environment variables must be set: +# +# maxscale_sshkey The ssh key to the MaxScale VM +# maxscale_access_user The username on the VM +# maxscale_IP The IP address of the MaxScale VM +# node_000_network IP address of the +# +# The Vagrant setup is located in ~/mdbci/my-test-build/. Vagrant is used +# for SSH access to the machines. +# +# The backend server also must have log-slave-updates enabled. + +# This is the VM name where the replication-manager is installed +mrm=galera_000 + +# Helper functions for ssh and scp +function do_ssh() { + cd ~/mdbci/my-test-build/ + vagrant ssh $1 -c "$2" + cd - > /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +cat < /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +cat < /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +function stop_node() { + do_ssh $1 "sudo /etc/init.d/mysql stop" +} + +function start_node() { + do_ssh $1 "sudo /etc/init.d/mysql start" +} + +function do_test() { + mysql -v -u skysql -pskysql -h $maxscale_IP -P $maxscale_port < /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +function stop_node() { + do_ssh $1 "sudo /etc/init.d/mysql stop" +} + +function start_node() { + do_ssh $1 "sudo /etc/init.d/mysql start" +} + +function do_test() { + mysql -v -u skysql -pskysql -h $maxscale_IP -P $maxscale_port < /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +function stop_node() { + do_ssh $1 "sudo /etc/init.d/mysql stop" +} + +function start_node() { + do_ssh $1 "sudo /etc/init.d/mysql start" +} + +function do_test() { + mysql -v -u skysql -pskysql -h $maxscale_IP -P $maxscale_port < /dev/null +} + +# Helper functions for ssh and scp +function do_scp() { + cd ~/mdbci/my-test-build/ + local dest=$(vagrant ssh-config $1|grep HostName|sed 's/.*HostName //') + scp -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $2 vagrant@$dest:~ + cd - > /dev/null +} + +function stop_node() { + do_ssh $1 "sudo /etc/init.d/mysql stop" +} + +function start_node() { + do_ssh $1 "sudo /etc/init.d/mysql start" +} + +function do_test() { + mysql -v -u skysql -pskysql -h $maxscale_IP -P $maxscale_port < +#include +#include "testconnections.h" + +using namespace std; + +char * create_event_size(unsigned long size) +{ + char * prefix = (char *) "insert into test.large_event values (1, '"; + unsigned long prefix_size = strlen(prefix); + char * postfix = (char *) "');" ; + char * event = (char*)malloc(size + 1); + strcpy(event, prefix); + + unsigned long max = size - 55 - 45; + + + //printf("BLOB data size %lu\n", max); + + for (unsigned long i = 0; i < max; i++) + { + event[i + prefix_size] = 'a'; + } + + strcpy((char *) event + max + prefix_size, postfix); + return event; +} + +MYSQL * connect_to_serv(TestConnections* Test, bool binlog) +{ + MYSQL * conn; + if (binlog) + { + conn = open_conn(Test->repl->port[0], Test->repl->IP[0], Test->repl->user_name, Test->repl->password, + Test->ssl); + } + else + { + conn = Test->open_rwsplit_connection(); + } + return conn; +} + +void set_max_packet(TestConnections* Test, bool binlog, char * cmd) +{ + Test->tprintf("Setting maximum packet size ..."); + if (binlog) + { + Test->repl->connect(); + Test->try_query(Test->repl->nodes[0], cmd); + Test->repl->close_connections(); + } + else + { + Test->connect_maxscale(); + Test->try_query(Test->conn_rwsplit, cmd); + Test->close_maxscale_connections(); + } + Test->tprintf(".. done\n"); +} + +void different_packet_size(TestConnections* Test, bool binlog) +{ + MYSQL * conn; + Test->set_timeout(60); + Test->tprintf("Set big max_allowed_packet\n"); + set_max_packet(Test, binlog, (char *) "set global max_allowed_packet = 200000000;"); + + Test->set_timeout(40); + Test->tprintf("Create table\n"); + conn = connect_to_serv(Test, binlog); + Test->try_query(conn, (char *) + "DROP TABLE IF EXISTS test.large_event;CREATE TABLE test.large_event(id INT, data LONGBLOB);"); + mysql_close(conn); + + int ranges_num = 3; + unsigned int range_min[ranges_num]; + unsigned int range_max[ranges_num]; + unsigned int range[ranges_num]; + + range[0] = 50; + if (Test->smoke) + { + range[0] = 20; + } + range_min[0] = 0x0ffffff - range[0]; + range_max[0] = 0x0ffffff + range[0]; + + range[1] = 50; + if (Test->smoke) + { + range[1] = 20; + } + range_min[1] = 0x0ffffff * 2 - range[1]; + range_max[1] = 0x0ffffff * 2 + range[1]; + + range[2] = 10; + if (Test->smoke) + { + range[2] = 10; + } + range_min[2] = 0x0ffffff * 3 - range[2]; + range_max[2] = 0x0ffffff * 3 + range[2]; + + char * event; + int i; + unsigned long j; + + for (i = 0; i < ranges_num; i++) + { + for (j = range_min[i]; j < range_max[i]; j++) + { + Test->set_timeout(120); + event = create_event_size(j); + Test->tprintf("Trying event app. %d bytes\t", j); + fflush(stdout); + conn = connect_to_serv(Test, binlog); + if (execute_query_silent(conn, event) == 0) + { + Test->tprintf("OK\n"); + } + else + { + Test->tprintf("FAIL\n"); + } + + free(event); + execute_query_silent(conn, (char *) "DELETE FROM test.large_event WHERE id=1"); + mysql_close(conn); + } + } + + Test->set_timeout(40); + Test->tprintf("Restoring max_allowed_packet\n"); + set_max_packet(Test, binlog, (char *) "set global max_allowed_packet = 1048576;"); +} diff --git a/maxscale-system-test/different_size.h b/maxscale-system-test/different_size.h new file mode 100644 index 000000000..b28117dd5 --- /dev/null +++ b/maxscale-system-test/different_size.h @@ -0,0 +1,38 @@ +#ifndef DIFFERENT_SIZE_H +#define DIFFERENT_SIZE_H + +#include +#include +#include "testconnections.h" + +/** + * @brief create_event_size Creates SQL query to generate even of given size + * @param size desired size of event + * @return SQL query string + */ +char * create_event_size(unsigned long size); + +/** + * @brief connect_to_serv Open connection + * @param Test TestConnections object + * @param binlog if true - connects to Master, otherwise - to RWSplit router + * @return MYSQL handler + */ +MYSQL * connect_to_serv(TestConnections* Test, bool binlog); + +/** + * @brief set_max_packet Executes 'cmd' on Master of RWSplit ('cmd' should be 'set global max_paxket_size=...') + * @param Test TestConnections object + * @param binlog if true - connects to Master, otherwise - to RWSplit router + * @param cmd command to execute + */ +void set_max_packet(TestConnections* Test, bool binlog, char * cmd); + +/** + * @brief different_packet_size Tries INSERTs with size close to 0x0ffffff * N (N is 1, 2 and 3) + * @param Test TestConnections object + * @param binlog if true - connects to Master, otherwise - to RWSplit router + */ +void different_packet_size(TestConnections* Test, bool binlog); + +#endif // DIFFERENT_SIZE_H diff --git a/maxscale-system-test/different_size_binlog.cpp b/maxscale-system-test/different_size_binlog.cpp new file mode 100644 index 000000000..b98c1c296 --- /dev/null +++ b/maxscale-system-test/different_size_binlog.cpp @@ -0,0 +1,29 @@ +/** + * @file different_size_binlog.cpp Tries INSERTs with size close to 0x0ffffff * N + * - configure binlog + * - executes inserts with size: from 0x0ffffff * N - X up to 0x0ffffff * N - X + * (N = 3, X = 50 or 20 for 'soke' test) + * - check if Maxscale is still alive + */ + + +#include +#include +#include "testconnections.h" +#include "different_size.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(300); + Test->start_binlog(); + different_packet_size(Test, true); + + Test->check_maxscale_processes(1); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/different_size_rwsplit.cpp b/maxscale-system-test/different_size_rwsplit.cpp new file mode 100644 index 000000000..2079ef793 --- /dev/null +++ b/maxscale-system-test/different_size_rwsplit.cpp @@ -0,0 +1,27 @@ +/** + * @file different_size_rwsplit.cpp Tries INSERTs with size close to 0x0ffffff * N + * - executes inserts with size: from 0x0ffffff * N - X up to 0x0ffffff * N - X + * (N = 3, X = 50 or 20 for 'soke' test) + * - check if Maxscale is still alive + */ + + +#include +#include +#include "testconnections.h" +#include "different_size.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + different_packet_size(Test, false); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/encrypted_passwords.cpp b/maxscale-system-test/encrypted_passwords.cpp new file mode 100644 index 000000000..9accc94d0 --- /dev/null +++ b/maxscale-system-test/encrypted_passwords.cpp @@ -0,0 +1,74 @@ +/** + * @file encrypted_passwords.cpp - Test maxkeys and maxpasswd interaction with MaxScale + * - put encrypted password into maxscale.cnf and try to use Maxscale + */ + +#include +#include "testconnections.h" + +/** Remove old keys and create a new one */ +int create_key(TestConnections *test) +{ + int res = 0; + test->set_timeout(120); + test->tprintf("Creating new encryption keys\n"); + test->ssh_maxscale(true, "test -f /var/lib/maxscale/.secrets && sudo rm /var/lib/maxscale/.secrets"); + test->ssh_maxscale(true, "maxkeys"); + char *result = test->ssh_maxscale_output(false, "sudo test -f /var/lib/maxscale/.secrets && echo SUCCESS"); + + if (strncmp(result, "SUCCESS", 7) != 0) + { + test->tprintf("FAILURE: /var/lib/maxscale/.secrets was not created\n"); + res = 1; + } + else + { + test->ssh_maxscale(true, "sudo chown maxscale:maxscale /var/lib/maxscale/.secrets"); + } + + free(result); + return res; +} + + +/** Hash a new password and start MaxScale */ +int hash_password(TestConnections *test) +{ + test->stop_maxscale(); + test->stop_timeout(); + + int res = 0; + test->tprintf("Creating a new encrypted password\n"); + char *enc_pw = test->ssh_maxscale_output(true, "maxpasswd /var/lib/maxscale/ skysql"); + + char *ptr = strchr(enc_pw, '\n'); + if (ptr) + { + *ptr = '\0'; + } + + test->tprintf("Encrypted password is: %s\n", enc_pw); + test->ssh_maxscale(true, "sed -i -e 's/passwd[[:space:]]*=[[:space:]]*skysql/passwd=%s/' /etc/maxscale.cnf", + enc_pw); + free(enc_pw); + + test->tprintf("Starting MaxScale\n"); + test->start_maxscale(); + + test->tprintf("Checking if MaxScale is alive\n"); + return test->check_maxscale_alive(); +} + + + +int main(int argc, char *argv[]) +{ + TestConnections * test = new TestConnections(argc, argv); + + test->global_result += create_key(test); + test->global_result += hash_password(test); + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/execute_cmd.cpp b/maxscale-system-test/execute_cmd.cpp new file mode 100644 index 000000000..f465d1d5b --- /dev/null +++ b/maxscale-system-test/execute_cmd.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include "execute_cmd.h" + +using namespace std; + + +int execute_cmd(char * cmd, char ** res) +{ + char * result; + FILE *output = popen(cmd, "r"); + if (output == NULL) + { + printf("Error opening ssh %s\n", strerror(errno)); + return -1; + } + char buffer[10240]; + size_t rsize = sizeof(buffer); + result = (char*)calloc(rsize, sizeof(char)); + + while (fgets(buffer, sizeof(buffer), output)) + { + result = (char*)realloc(result, sizeof(buffer) + rsize); + rsize += sizeof(buffer); + strcat(result, buffer); + } + + * res = result; + + int return_code = pclose(output); + if (WIFEXITED(return_code)) + { + return WEXITSTATUS(return_code); + } + else + { + return -1; + } +} + + diff --git a/maxscale-system-test/execute_cmd.h b/maxscale-system-test/execute_cmd.h new file mode 100644 index 000000000..4562c8017 --- /dev/null +++ b/maxscale-system-test/execute_cmd.h @@ -0,0 +1,17 @@ +#ifndef EXECUTE_CMD_H +#define EXECUTE_CMD_H + +#include +#include + +using namespace std; + +/** + * @brief execute_cmd Execute shell command + * @param cmd Command line + * @param res Pointer to variable that will contain command console output (stdout) + * @return Process exit code + */ +int execute_cmd(char * cmd, char ** res); + +#endif // EXECUTE_CMD_H diff --git a/maxscale-system-test/failover_mysqlmon.cpp b/maxscale-system-test/failover_mysqlmon.cpp new file mode 100644 index 000000000..52f2a9ed1 --- /dev/null +++ b/maxscale-system-test/failover_mysqlmon.cpp @@ -0,0 +1,71 @@ +/** + * @file failover_mysqlmon.cpp MySQL Monitor Failover Test + * - block all nodes, but one + * - wait for minitor (monitor_interval) + * - check maxadmin output + * - check that queries work + * - unblock backend nodes + * - wait for monitor + * - check that we are still using the last node to which we failed over to and that the old nodes are in maintenance mode + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * test = new TestConnections(argc, argv); + + test->tprintf(" Create the test table and insert some data "); + test->connect_maxscale(); + test->try_query(test->conn_rwsplit, "CREATE OR REPLACE TABLE test.t1 (id int)"); + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test->close_maxscale_connections(); + + test->tprintf(" Block all but one node "); + test->repl->block_node(0); + test->repl->block_node(1); + test->repl->block_node(2); + test->repl->connect(); + execute_query(test->repl->nodes[3], "STOP SLAVE;RESET SLAVE ALL;"); + + test->tprintf(" Wait for the monitor to detect it "); + sleep(15); + + test->tprintf(" Connect and insert should work "); + char *output = test->ssh_maxscale_output(true, "maxadmin list servers"); + test->tprintf("%s", output); + free(output); + test->connect_maxscale(); + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test->close_maxscale_connections(); + + test->tprintf(" Unblock nodes "); + test->repl->unblock_node(0); + test->repl->unblock_node(1); + test->repl->unblock_node(2); + + test->tprintf(" Wait for the monitor to detect it "); + sleep(15); + + test->tprintf("Check that we are still using the last node to which we failed over " + "to and that the old nodes are in maintenance mode"); + + test->connect_maxscale(); + test->try_query(test->conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + char maxscale_id[256], real_id[256]; + find_field(test->conn_rwsplit, "SELECT @@server_id", "@@server_id", maxscale_id); + test->repl->connect(); + find_field(test->repl->nodes[3], "SELECT @@server_id", "@@server_id", real_id); + test->add_result(strcmp(maxscale_id, real_id) != 0, + "@@server_id is different: %s != %s", maxscale_id, real_id); + test->close_maxscale_connections(); + + test->tprintf(" Check that MaxScale is running "); + test->check_maxscale_alive(); + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/false_monitor_state_change.cpp b/maxscale-system-test/false_monitor_state_change.cpp new file mode 100644 index 000000000..268d90797 --- /dev/null +++ b/maxscale-system-test/false_monitor_state_change.cpp @@ -0,0 +1,28 @@ +/** + * Test false server state changes when manually clearing master bit + */ + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + + test.tprintf("Block master"); + test.repl->block_node(0); + + test.tprintf("Wait for monitor to see it"); + sleep(10); + + test.tprintf("Clear master status"); + test.ssh_maxscale(true, "maxadmin clear server server1 master"); + sleep(5); + + test.repl->unblock_node(0); + sleep(5); + + test.check_maxscale_alive(); + test.check_log_err("debug assert", false); + + return test.global_result; +} diff --git a/maxscale-system-test/fw/deny1 b/maxscale-system-test/fw/deny1 new file mode 100644 index 000000000..9ef40c5a7 --- /dev/null +++ b/maxscale-system-test/fw/deny1 @@ -0,0 +1 @@ +select * from mysql.user; diff --git a/maxscale-system-test/fw/deny10 b/maxscale-system-test/fw/deny10 new file mode 100644 index 000000000..61315519b --- /dev/null +++ b/maxscale-system-test/fw/deny10 @@ -0,0 +1,4 @@ +load data infile; +LOAD data infile; +LOAD DATA infile; +LOAD DATA INFILE; \ No newline at end of file diff --git a/maxscale-system-test/fw/deny11 b/maxscale-system-test/fw/deny11 new file mode 100644 index 000000000..634099d7d --- /dev/null +++ b/maxscale-system-test/fw/deny11 @@ -0,0 +1,3 @@ +SELECT sum(x1) FROM t1; +SELECT concat(x1,sum(x1)) FROM t1; +SELECT concat(sum(x1),avg(x1)) FROM t1; diff --git a/maxscale-system-test/fw/deny2 b/maxscale-system-test/fw/deny2 new file mode 100644 index 000000000..9ef40c5a7 --- /dev/null +++ b/maxscale-system-test/fw/deny2 @@ -0,0 +1 @@ +select * from mysql.user; diff --git a/maxscale-system-test/fw/deny3 b/maxscale-system-test/fw/deny3 new file mode 100644 index 000000000..0c5f68f1a --- /dev/null +++ b/maxscale-system-test/fw/deny3 @@ -0,0 +1,3 @@ +update t1 set x1=1 where fl=0; +SELECT x1 FROM t1; +select t1.x1 as 'something' from t1 as t1 limit 1; \ No newline at end of file diff --git a/maxscale-system-test/fw/deny4 b/maxscale-system-test/fw/deny4 new file mode 100644 index 000000000..f3ed2374e --- /dev/null +++ b/maxscale-system-test/fw/deny4 @@ -0,0 +1 @@ +SELECT x1 FROM t1; diff --git a/maxscale-system-test/fw/deny5 b/maxscale-system-test/fw/deny5 new file mode 100644 index 000000000..cd9b04032 --- /dev/null +++ b/maxscale-system-test/fw/deny5 @@ -0,0 +1,3 @@ +DELETE FROM t1; +SELECT fl FROM t1; +UPDATE t1 SET f1=1; diff --git a/maxscale-system-test/fw/deny6 b/maxscale-system-test/fw/deny6 new file mode 100644 index 000000000..fc543202d --- /dev/null +++ b/maxscale-system-test/fw/deny6 @@ -0,0 +1 @@ +SELECT fl FROM t1; diff --git a/maxscale-system-test/fw/deny7 b/maxscale-system-test/fw/deny7 new file mode 100644 index 000000000..dde2f4ba5 --- /dev/null +++ b/maxscale-system-test/fw/deny7 @@ -0,0 +1 @@ +UPDATE t1 SET fl=1; diff --git a/maxscale-system-test/fw/deny8 b/maxscale-system-test/fw/deny8 new file mode 100644 index 000000000..06602f22d --- /dev/null +++ b/maxscale-system-test/fw/deny8 @@ -0,0 +1 @@ +SELECT fl FROM t1 WHERE fl = 0 AND x1 = 1 OR x1=2 diff --git a/maxscale-system-test/fw/deny9 b/maxscale-system-test/fw/deny9 new file mode 100644 index 000000000..b3c24053d --- /dev/null +++ b/maxscale-system-test/fw/deny9 @@ -0,0 +1,3 @@ +UPDATE t1 SET x1=1 where fl=0; +SELECT x1 FROM t1; +SELECT * FROM t1; diff --git a/maxscale-system-test/fw/pass1 b/maxscale-system-test/fw/pass1 new file mode 100644 index 000000000..244cad7fd --- /dev/null +++ b/maxscale-system-test/fw/pass1 @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +select sleep(5); +delete from t1 where x1 like '_'; +select user from mysql.user; +SELECT COUNT( * ) \ No newline at end of file diff --git a/maxscale-system-test/fw/pass10 b/maxscale-system-test/fw/pass10 new file mode 100644 index 000000000..e0ac49d1e --- /dev/null +++ b/maxscale-system-test/fw/pass10 @@ -0,0 +1 @@ +SELECT 1; diff --git a/maxscale-system-test/fw/pass11 b/maxscale-system-test/fw/pass11 new file mode 100644 index 000000000..50d4cb06f --- /dev/null +++ b/maxscale-system-test/fw/pass11 @@ -0,0 +1,3 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +SELECT fl FROM t1; diff --git a/maxscale-system-test/fw/pass2 b/maxscale-system-test/fw/pass2 new file mode 100644 index 000000000..90c732fdf --- /dev/null +++ b/maxscale-system-test/fw/pass2 @@ -0,0 +1,2 @@ +select user from mysql.user; +delete from t1 where x1 like '_'; diff --git a/maxscale-system-test/fw/pass3 b/maxscale-system-test/fw/pass3 new file mode 100644 index 000000000..a090fe4f0 --- /dev/null +++ b/maxscale-system-test/fw/pass3 @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +select sleep(5); +SELECT fl FROM t1; +SELECT * FROM t1; +select t1.fl as 'something' from t1 as t1 limit 1; diff --git a/maxscale-system-test/fw/pass4 b/maxscale-system-test/fw/pass4 new file mode 100644 index 000000000..a0389cd31 --- /dev/null +++ b/maxscale-system-test/fw/pass4 @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +select sleep(5); +delete from t1 where x1=0; +SELECT fl FROM t1; +SELECT * FROM t1; diff --git a/maxscale-system-test/fw/pass5 b/maxscale-system-test/fw/pass5 new file mode 100644 index 000000000..ac3f1e6af --- /dev/null +++ b/maxscale-system-test/fw/pass5 @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 INT, fl INT); +DELETE FROM t1 WHERE x1=0; +SELECT fl FROM t1 WHERE x1=0; +UPDATE t1 SET fl=1 WHERE x1=0; diff --git a/maxscale-system-test/fw/pass6 b/maxscale-system-test/fw/pass6 new file mode 100644 index 000000000..d70392d53 --- /dev/null +++ b/maxscale-system-test/fw/pass6 @@ -0,0 +1,5 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +delete from t1; +UPDATE t1 SET fl=1; +SELECT fl FROM t1 where x1=0; diff --git a/maxscale-system-test/fw/pass7 b/maxscale-system-test/fw/pass7 new file mode 100644 index 000000000..61932ee28 --- /dev/null +++ b/maxscale-system-test/fw/pass7 @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +select sleep(5); +delete from t1; +SELECT fl FROM t1; +UPDATE t1 SET fl=1 WHERE x1=0; diff --git a/maxscale-system-test/fw/pass8 b/maxscale-system-test/fw/pass8 new file mode 100644 index 000000000..8d4eac501 --- /dev/null +++ b/maxscale-system-test/fw/pass8 @@ -0,0 +1 @@ +SELECT fl FROM t1 WHERE fl = 0 AND x1 = 1 ; diff --git a/maxscale-system-test/fw/pass9 b/maxscale-system-test/fw/pass9 new file mode 100644 index 000000000..cc18ab41e --- /dev/null +++ b/maxscale-system-test/fw/pass9 @@ -0,0 +1,4 @@ +DROP TABLE IF EXISTS t1; +CREATE TABLE t1 (x1 int, fl int); +select sleep(5); +SELECT fl FROM t1; diff --git a/maxscale-system-test/fw/rules1 b/maxscale-system-test/fw/rules1 new file mode 100644 index 000000000..ffa8feb0c --- /dev/null +++ b/maxscale-system-test/fw/rules1 @@ -0,0 +1,2 @@ +rule test1 deny wildcard +users %@% match all rules test1 diff --git a/maxscale-system-test/fw/rules10 b/maxscale-system-test/fw/rules10 new file mode 100644 index 000000000..0c3a7c501 --- /dev/null +++ b/maxscale-system-test/fw/rules10 @@ -0,0 +1,3 @@ +rule regex_rule deny regex '^(?i)load.*data.*infile*' +rule files2 deny regex '^(?i)load.data.*local.*infile' +users %@% match any rules regex_rule diff --git a/maxscale-system-test/fw/rules11 b/maxscale-system-test/fw/rules11 new file mode 100644 index 000000000..52a6c6850 --- /dev/null +++ b/maxscale-system-test/fw/rules11 @@ -0,0 +1,2 @@ +rule no_func deny functions sum avg +users %@% match any rules no_func diff --git a/maxscale-system-test/fw/rules2 b/maxscale-system-test/fw/rules2 new file mode 100644 index 000000000..89609cc60 --- /dev/null +++ b/maxscale-system-test/fw/rules2 @@ -0,0 +1,2 @@ +rule test2 deny wildcard on_queries select +users %@% match all rules test2 diff --git a/maxscale-system-test/fw/rules3 b/maxscale-system-test/fw/rules3 new file mode 100644 index 000000000..14cf6e181 --- /dev/null +++ b/maxscale-system-test/fw/rules3 @@ -0,0 +1,2 @@ +rule test3 deny columns x1 +users %@% match all rules test3 diff --git a/maxscale-system-test/fw/rules4 b/maxscale-system-test/fw/rules4 new file mode 100644 index 000000000..2110ab8f5 --- /dev/null +++ b/maxscale-system-test/fw/rules4 @@ -0,0 +1,2 @@ +rule test4 deny columns x1 on_queries select +users %@% match all rules test4 diff --git a/maxscale-system-test/fw/rules5 b/maxscale-system-test/fw/rules5 new file mode 100644 index 000000000..62c322890 --- /dev/null +++ b/maxscale-system-test/fw/rules5 @@ -0,0 +1,2 @@ +rule test7 deny no_where_clause on_queries select|delete|update +users %@% match all rules test7 diff --git a/maxscale-system-test/fw/rules6 b/maxscale-system-test/fw/rules6 new file mode 100644 index 000000000..2e18050fe --- /dev/null +++ b/maxscale-system-test/fw/rules6 @@ -0,0 +1,2 @@ +rule test9 deny no_where_clause on_queries select +users %@% match all rules test9 diff --git a/maxscale-system-test/fw/rules7 b/maxscale-system-test/fw/rules7 new file mode 100644 index 000000000..67e3d3f75 --- /dev/null +++ b/maxscale-system-test/fw/rules7 @@ -0,0 +1,2 @@ +rule test11 deny no_where_clause on_queries update +users %@% match all rules test11 diff --git a/maxscale-system-test/fw/rules8 b/maxscale-system-test/fw/rules8 new file mode 100644 index 000000000..27445bd78 --- /dev/null +++ b/maxscale-system-test/fw/rules8 @@ -0,0 +1,2 @@ +rule test12 deny regex ".*FROM.*t1.*WHERE.*OR.*" on_queries select +users %@% match all rules test12 diff --git a/maxscale-system-test/fw/rules9 b/maxscale-system-test/fw/rules9 new file mode 100644 index 000000000..0fc692b4b --- /dev/null +++ b/maxscale-system-test/fw/rules9 @@ -0,0 +1,3 @@ +rule no_wildcard deny wildcard +rule no_x1 deny columns x1 +users %@% match any rules no_wildcard no_x1 diff --git a/maxscale-system-test/fw/rules_actions b/maxscale-system-test/fw/rules_actions new file mode 100644 index 000000000..d8c059914 --- /dev/null +++ b/maxscale-system-test/fw/rules_actions @@ -0,0 +1,2 @@ +rule r1 deny regex 'select' +users %@% match any rules r1 diff --git a/maxscale-system-test/fw/rules_at_time b/maxscale-system-test/fw/rules_at_time new file mode 100644 index 000000000..8a3364ab7 --- /dev/null +++ b/maxscale-system-test/fw/rules_at_time @@ -0,0 +1,2 @@ +rule testrule deny no_where_clause at_times ###time### on_queries delete +users skysql@% match strict_all rules testrule diff --git a/maxscale-system-test/fw/rules_limit_queries b/maxscale-system-test/fw/rules_limit_queries new file mode 100644 index 000000000..120cf04d3 --- /dev/null +++ b/maxscale-system-test/fw/rules_limit_queries @@ -0,0 +1,2 @@ +rule testrule deny limit_queries 10 7 5 +users skysql@% match strict_all rules testrule diff --git a/maxscale-system-test/fw/rules_logging b/maxscale-system-test/fw/rules_logging new file mode 100644 index 000000000..50cb687a0 --- /dev/null +++ b/maxscale-system-test/fw/rules_logging @@ -0,0 +1,2 @@ +rule r1 deny regex 'select.*1' +users %@% match any rules r1 diff --git a/maxscale-system-test/fw/rules_syntax_error b/maxscale-system-test/fw/rules_syntax_error new file mode 100644 index 000000000..4da14bf38 --- /dev/null +++ b/maxscale-system-test/fw/rules_syntax_error @@ -0,0 +1,2 @@ +rule test1 deny wildcard1 +users %@% match all rules test1 diff --git a/maxscale-system-test/fw2/deny1 b/maxscale-system-test/fw2/deny1 new file mode 100644 index 000000000..08df3dd10 --- /dev/null +++ b/maxscale-system-test/fw2/deny1 @@ -0,0 +1,3 @@ +drop table if exists t1; +create table t1(id int); +alter table t1 add column b int; diff --git a/maxscale-system-test/fw2/deny2 b/maxscale-system-test/fw2/deny2 new file mode 100644 index 000000000..afd589610 --- /dev/null +++ b/maxscale-system-test/fw2/deny2 @@ -0,0 +1,2 @@ +grant select on *.* to 'skysql'@'localhost'; +revoke select on *.* from 'skysql'@'localhost'; diff --git a/maxscale-system-test/fw2/deny3 b/maxscale-system-test/fw2/deny3 new file mode 100644 index 000000000..dee339a0e --- /dev/null +++ b/maxscale-system-test/fw2/deny3 @@ -0,0 +1,2 @@ +use test; +load data local infile 'test.csv' into table test.t1; diff --git a/maxscale-system-test/fw2/deny4 b/maxscale-system-test/fw2/deny4 new file mode 100644 index 000000000..f000fa062 --- /dev/null +++ b/maxscale-system-test/fw2/deny4 @@ -0,0 +1,10 @@ +select sum(1) from test.t1; +select avg(1) from test.t1; +select sum(avg(1)) from test.t1; +select my_function(1) from test.t1; +select my_function("1") from test.t1; +select * from test.t1 where 1 = 1; +select * from test.t1 where 1 >= 1; +select * from test.t1 where 1 <= 1; +select * from test.t1 where 1 != 1; +select * from test.t1 where 1 <> 1; diff --git a/maxscale-system-test/fw2/pass1 b/maxscale-system-test/fw2/pass1 new file mode 100644 index 000000000..8487c645f --- /dev/null +++ b/maxscale-system-test/fw2/pass1 @@ -0,0 +1,3 @@ +select * from mysql.user; +use mysql; +set @var = 1; diff --git a/maxscale-system-test/fw2/pass2 b/maxscale-system-test/fw2/pass2 new file mode 100644 index 000000000..8487c645f --- /dev/null +++ b/maxscale-system-test/fw2/pass2 @@ -0,0 +1,3 @@ +select * from mysql.user; +use mysql; +set @var = 1; diff --git a/maxscale-system-test/fw2/pass3 b/maxscale-system-test/fw2/pass3 new file mode 100644 index 000000000..2b68250d3 --- /dev/null +++ b/maxscale-system-test/fw2/pass3 @@ -0,0 +1,2 @@ +select * from mysql.user; +set @var = 1; diff --git a/maxscale-system-test/fw2/pass4 b/maxscale-system-test/fw2/pass4 new file mode 100644 index 000000000..5f597334b --- /dev/null +++ b/maxscale-system-test/fw2/pass4 @@ -0,0 +1,5 @@ +create or replace table test.t1(id int); +create function my_function (arg int) returns int deterministic return arg * arg; +select "sum(1)"; +select (1); +select * from(select 1) as a; diff --git a/maxscale-system-test/fw2/rules1 b/maxscale-system-test/fw2/rules1 new file mode 100644 index 000000000..bb40bd532 --- /dev/null +++ b/maxscale-system-test/fw2/rules1 @@ -0,0 +1,4 @@ +rule test1 deny regex '.*' on_queries drop +rule test2 deny regex '.*' on_queries create +rule test3 deny regex '.*' on_queries alter +users %@% match any rules test1 test2 test3 diff --git a/maxscale-system-test/fw2/rules2 b/maxscale-system-test/fw2/rules2 new file mode 100644 index 000000000..2d6dc47db --- /dev/null +++ b/maxscale-system-test/fw2/rules2 @@ -0,0 +1,3 @@ +rule test1 deny regex '.*' on_queries grant +rule test2 deny regex '.*' on_queries revoke +users %@% match any rules test1 test2 diff --git a/maxscale-system-test/fw2/rules3 b/maxscale-system-test/fw2/rules3 new file mode 100644 index 000000000..6672bca49 --- /dev/null +++ b/maxscale-system-test/fw2/rules3 @@ -0,0 +1,3 @@ +rule test1 deny regex '.*' on_queries use +rule test2 deny regex '.*' on_queries load +users %@% match any rules test1 test2 diff --git a/maxscale-system-test/fw2/rules4 b/maxscale-system-test/fw2/rules4 new file mode 100644 index 000000000..f428c7c9a --- /dev/null +++ b/maxscale-system-test/fw2/rules4 @@ -0,0 +1,4 @@ +rule test1 deny function sum avg on_queries select +rule test2 deny function my_function on_queries select +rule test3 deny function = >= <= != <> on_queries select +users %@% match any rules test1 test2 test3 diff --git a/maxscale-system-test/fw_copy_rules.cpp b/maxscale-system-test/fw_copy_rules.cpp new file mode 100644 index 000000000..df5ba3141 --- /dev/null +++ b/maxscale-system-test/fw_copy_rules.cpp @@ -0,0 +1,30 @@ +#include "fw_copy_rules.h" +#include + +void copy_rules(TestConnections* Test, const char* rules_name, const char* rules_dir) +{ + Test->set_timeout(30); + Test->ssh_maxscale(true, "cd %s;" + "rm -rf rules;" + "mkdir rules;" + "chown vagrant:vagrant rules -R", + Test->maxscale_access_homedir); + + Test->set_timeout(30); + + std::string src; + std::string dest; + + src += rules_dir; + src += "/"; + src += rules_name; + + dest += Test->maxscale_access_homedir; + dest += "/rules/rules.txt"; + + Test->copy_to_maxscale(src.c_str(), dest.c_str()); + + Test->set_timeout(30); + Test->ssh_maxscale(true, "chown maxscale:maxscale %s/rules -R", Test->maxscale_access_homedir); + Test->stop_timeout(); +} diff --git a/maxscale-system-test/fw_copy_rules.h b/maxscale-system-test/fw_copy_rules.h new file mode 100644 index 000000000..fb3437270 --- /dev/null +++ b/maxscale-system-test/fw_copy_rules.h @@ -0,0 +1,14 @@ +#ifndef FW_COPY_RULES_H +#define FW_COPY_RULES_H + +#include "testconnections.h" + +/** + * @brief copy_rules Copy rules file for firewall filter to Maxscale machine + * @param Test TestConnections object + * @param rules_name Name of file to be copied + * @param rules_dir Directory where file is located + */ +void copy_rules(TestConnections* Test, const char* rules_name, const char* rules_dir); + +#endif // FW_COPY_RULES_H diff --git a/maxscale-system-test/fwf.cpp b/maxscale-system-test/fwf.cpp new file mode 100644 index 000000000..2765ee86b --- /dev/null +++ b/maxscale-system-test/fwf.cpp @@ -0,0 +1,213 @@ +/** + * @file fwf - Firewall filter test (also regression test for MXS-683 "qc_mysqlembedded reports as-name instead of original-name") + * - setup Firewall filter to use rules from rule file fw/ruleXX, where XX - number of sub-test + * - execute queries for fw/passXX file, expect OK + * - execute queries from fw/denyXX, expect Access Denied error (mysql_error 1141) + * - repeat for all XX + * - setup Firewall filter to block queries next 2 minutes using 'at_time' statement (see template fw/rules_at_time) + * - start sending queries, expect Access Denied now and OK after two mintes + * - setup Firewall filter to limit a number of queries during certain time + * - start sending queries as fast as possible, expect OK for N first quries and Access Denied for next queries + * - wait, start sending queries again, but only one query per second, expect OK + * - try to load rules with syntax error, expect failure for all sessions and queries + */ + + +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "fw_copy_rules.h" + +int main(int argc, char *argv[]) +{ + TestConnections::skip_maxscale_start(true); + TestConnections * Test = new TestConnections(argc, argv); + int local_result; + char str[4096]; + char sql[4096]; + char pass_file[4096]; + char deny_file[4096]; + char rules_dir[4096]; + FILE* file; + + sprintf(rules_dir, "%s/fw/", test_dir); + int N = 10; + int i; + + for (i = 1; i < N + 1; i++) + { + Test->set_timeout(180); + local_result = 0; + + Test->stop_maxscale(); + + sprintf(str, "rules%d", i); + copy_rules(Test, str, rules_dir); + + Test->start_maxscale(); + Test->connect_rwsplit(); + + sprintf(pass_file, "%s/fw/pass%d", test_dir, i); + sprintf(deny_file, "%s/fw/deny%d", test_dir, i); + Test->tprintf("Pass file: %s\n", pass_file); + Test->tprintf("Deny file: %s\n", deny_file); + + file = fopen(pass_file, "r"); + if (file != NULL) + { + Test->tprintf("********** Trying queries that should be OK ********** \n"); + while (fgets(sql, sizeof(sql), file)) + { + if (strlen(sql) > 1) + { + Test->tprintf("%s", sql); + local_result += execute_query(Test->conn_rwsplit, sql); + } + } + fclose(file); + } + else + { + Test->add_result(1, "Error opening query file\n"); + } + + file = fopen(deny_file, "r"); + if (file != NULL) + { + Test->tprintf("********** Trying queries that should FAIL ********** \n"); + while (fgets(sql, sizeof(sql), file)) + { + Test->set_timeout(180); + if (strlen(sql) > 1) + { + Test->tprintf("%s", sql); + execute_query(Test->conn_rwsplit, sql); + if (mysql_errno(Test->conn_rwsplit) != 1141) + { + Test->tprintf("Query succeded, but fail expected, errono is %d\n", mysql_errno(Test->conn_rwsplit)); + local_result++; + } + } + } + fclose(file); + } + else + { + Test->add_result(1, "Error opening query file\n"); + } + if (local_result != 0) + { + Test->add_result(1, "********** rules%d test FAILED\n", i); + } + else + { + Test->tprintf("********** rules%d test PASSED\n", i); + } + + mysql_close(Test->conn_rwsplit); + } + + Test->set_timeout(180); + Test->stop_maxscale(); + + // Test for at_times clause + Test->tprintf("Trying at_times clause\n"); + copy_rules(Test, (char *) "rules_at_time", rules_dir); + + Test->tprintf("DELETE quries without WHERE clause will be blocked during next 2 minutes\n"); + Test->tprintf("Put time to rules.txt: %s\n", str); + Test->ssh_maxscale(false, "start_time=`date +%%T`; stop_time=` date --date " + "\"now +2 mins\" +%%T`; %s sed -i \"s/###time###/$start_time-$stop_time/\" %s/rules/rules.txt", + Test->maxscale_access_sudo, Test->maxscale_access_homedir); + + Test->start_maxscale(); + Test->connect_rwsplit(); + + Test->tprintf("Trying 'DELETE FROM t1' and expecting FAILURE\n"); + execute_query(Test->conn_rwsplit, "DELETE FROM t1"); + if (mysql_errno(Test->conn_rwsplit) != 1141) + { + Test->add_result(1, "Query succeded, but fail expected, errono is %d\n", mysql_errno(Test->conn_rwsplit)); + } + Test->tprintf("Waiting 3 minutes and trying 'DELETE FROM t1', expecting OK\n"); + Test->stop_timeout(); + sleep(180); + Test->set_timeout(180); + Test->try_query(Test->conn_rwsplit, "DELETE FROM t1"); + + mysql_close(Test->conn_rwsplit); + Test->stop_maxscale(); + + Test->tprintf("Trying limit_queries clause\n"); + Test->tprintf("Copying rules to Maxscale machine: %s\n", str); + copy_rules(Test, (char *) "rules_limit_queries", rules_dir); + + Test->start_maxscale(); + Test->connect_rwsplit(); + + printf("Trying 10 quries as fast as possible\n"); + for (i = 0; i < 10; i++) + { + Test->add_result(execute_query(Test->conn_rwsplit, "SELECT * FROM t1"), "%d -query failed\n", i); + } + + Test->tprintf("Expecting failures during next 5 seconds\n"); + + time_t start_time_clock = time(NULL); + timeval t1, t2; + double elapsedTime; + gettimeofday(&t1, NULL); + + + do + { + gettimeofday(&t2, NULL); + elapsedTime = (t2.tv_sec - t1.tv_sec); + elapsedTime += (double) (t2.tv_usec - t1.tv_usec) / 1000000.0; + } + while ((execute_query_silent(Test->conn_rwsplit, "SELECT * FROM t1") != 0) && (elapsedTime < 10)); + + Test->tprintf("Quries were blocked during %f (using clock_gettime())\n", elapsedTime); + Test->tprintf("Quries were blocked during %lu (using time())\n", time(NULL) - start_time_clock); + if ((elapsedTime > 6) or (elapsedTime < 4)) + { + Test->add_result(1, "Queries were blocked during wrong time\n"); + } + + Test->set_timeout(180); + printf("Trying 20 quries, 1 query / second\n"); + for (i = 0; i < 20; i++) + { + sleep(1); + Test->add_result(execute_query(Test->conn_rwsplit, "SELECT * FROM t1"), "query failed\n"); + Test->tprintf("%d ", i); + } + Test->tprintf("\n"); + Test->set_timeout(180); + Test->tprintf("Stopping Maxscale\n"); + Test->stop_maxscale(); + + Test->tprintf("Trying rules with syntax error\n"); + Test->tprintf("Copying rules to Maxscale machine: %s\n", str); + copy_rules(Test, (char *) "rules_syntax_error", rules_dir); + + Test->tprintf("Starting Maxscale\n"); + Test->start_maxscale(); + Test->connect_rwsplit(); + + Test->tprintf("Trying to connectt to Maxscale when 'rules' has syntax error, expecting failures\n"); + if (execute_query(Test->conn_rwsplit, "SELECT * FROM t1") == 0) + { + Test->add_result(1, "Rule has syntax error, but query OK\n"); + } + + Test->check_maxscale_processes(0); + + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/fwf2.cpp b/maxscale-system-test/fwf2.cpp new file mode 100644 index 000000000..fdda24663 --- /dev/null +++ b/maxscale-system-test/fwf2.cpp @@ -0,0 +1,104 @@ +/** + * @file fwf - Firewall filtyer test + * - setup Firewall filter to use rules from rule file fw/ruleXX, where XX - number of sub-test + * - execute queries for fw/passXX file, expect OK + * - execute queries from fw/denyXX, expect Access Denied error (mysql_error 1141) + * - repeat for all XX + * - setup Firewall filter to block queries next 2 minutes using 'at_time' statement (see template fw/rules_at_time) + * - start sending queries, expect Access Denied now and OK after two mintes + * - setup Firewall filter to limit a number of queries during certain time + * - start sending queries as fast as possible, expect OK for N first quries and Access Denied for next queries + * - wait, start sending queries again, but only one query per second, expect OK + * - try to load rules with syntax error, expect failure for all sessions and queries + */ + + +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "fw_copy_rules.h" + +int read_and_execute_queries(TestConnections *Test, const char* filename, int expected) +{ + FILE *file = fopen(filename, "r"); + int local_result = 0; + if (file != NULL) + { + char sql[4096]; + while (fgets(sql, sizeof(sql), file)) + { + Test->set_timeout(60); + if (strlen(sql) > 1) + { + Test->tprintf("%s", sql); + if (execute_query(Test->conn_rwsplit, sql) != expected && + (expected == 1 || mysql_errno(Test->conn_rwsplit) == 1141)) + { + Test->tprintf("Query %s, but %s expected, MySQL error: %d, %s\n", + expected ? "succeeded" : "failed", + expected ? "failure" : "success", + mysql_errno(Test->conn_rwsplit), mysql_error(Test->conn_rwsplit)); + local_result++; + } + } + } + fclose(file); + } + else + { + Test->add_result(1, "Error opening file '%s'\n", filename); + } + return local_result; +} + +int main(int argc, char *argv[]) +{ + TestConnections::skip_maxscale_start(true); + TestConnections * Test = new TestConnections(argc, argv); + int local_result; + char str[4096]; + char pass_file[4096]; + char deny_file[4096]; + char rules_dir[4096]; + FILE* file; + + sprintf(rules_dir, "%s/fw2/", test_dir); + int N = 4; + int i; + + for (i = 1; i < N + 1; i++) + { + Test->set_timeout(60); + local_result = 0; + + Test->stop_maxscale(); + + sprintf(str, "rules%d", i); + copy_rules(Test, str, rules_dir); + + Test->start_maxscale(); + Test->connect_rwsplit(); + + sprintf(pass_file, "%s/fw2/pass%d", test_dir, i); + sprintf(deny_file, "%s/fw2/deny%d", test_dir, i); + + Test->tprintf("********** Trying queries that should be OK ********** \n"); + local_result += read_and_execute_queries(Test, pass_file, 0); + + Test->tprintf("********** Trying queries that should FAIL ********** \n"); + local_result += read_and_execute_queries(Test, deny_file, 1); + + Test->add_result(local_result, "********** rules%d test FAILED\n", i); + mysql_close(Test->conn_rwsplit); + } + + Test->check_maxscale_processes(1); + + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/fwf_actions.cpp b/maxscale-system-test/fwf_actions.cpp new file mode 100644 index 000000000..78652ae1c --- /dev/null +++ b/maxscale-system-test/fwf_actions.cpp @@ -0,0 +1,75 @@ +/** + * Firewall filter match action test + * + * Check if the blacklisting, whitelisting and ignoring funcionality of + * the dbfwfilter works. This test executes a matching and a non-matching query + * to three services configured in block, allow and ignore modes. + */ + + +#include +#include +#include "testconnections.h" +#include "fw_copy_rules.h" + +int main(int argc, char** argv) +{ + TestConnections::skip_maxscale_start(true); + char rules_dir[4096]; + + TestConnections *test = new TestConnections(argc, argv); + test->stop_timeout(); + + test->tprintf("Creating rules\n"); + test->stop_maxscale(); + + sprintf(rules_dir, "%s/fw/", test_dir); + copy_rules(test, (char*) "rules_actions", rules_dir); + + test->set_timeout(60); + test->start_maxscale(); + + test->set_timeout(30); + test->connect_maxscale(); + + /** Test blacklisting functionality */ + test->tprintf("Trying matching query to blacklisted RWSplit, expecting failure\n"); + test->set_timeout(30); + test->add_result(!execute_query_silent(test->conn_rwsplit, "select 1"), + "Matching query to blacklist service should fail.\n"); + test->tprintf("Trying non-matching query to blacklisted RWSplit, expecting success\n"); + test->set_timeout(30); + test->add_result(execute_query_silent(test->conn_rwsplit, "show status"), + "Non-matching query to blacklist service should succeed.\n"); + + /** Test whitelisting functionality */ + test->tprintf("Trying matching query to whitelisted Conn slave, expecting success\n"); + test->set_timeout(30); + test->add_result(execute_query_silent(test->conn_slave, "select 1"), + "Query to whitelist service should succeed.\n"); + test->tprintf("Trying non-matching query to whitelisted Conn slave, expecting failure\n"); + test->set_timeout(30); + test->add_result(!execute_query_silent(test->conn_slave, "show status"), + "Non-matching query to blacklist service should fail.\n"); + + /** Testing NO OP mode */ + test->tprintf("Trying matching query to ignoring Conn master, expecting success\n"); + test->set_timeout(30); + test->add_result(execute_query_silent(test->conn_master, "select 1"), + "Query to ignoring service should succeed.\n"); + test->tprintf("Trying non-matching query to ignoring Conn master, expecting success\n"); + test->set_timeout(30); + test->add_result(execute_query_silent(test->conn_master, "show status"), + "Non-matching query to ignoring service should succeed.\n"); + + test->stop_timeout(); + test->tprintf("Checking if MaxScale is alive\n"); + test->check_maxscale_processes(1); + test->stop_maxscale(); + sleep(10); + test->tprintf("Checking if MaxScale was succesfully terminated\n"); + test->check_maxscale_processes(0); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/fwf_com_ping.cpp b/maxscale-system-test/fwf_com_ping.cpp new file mode 100644 index 000000000..5bbaa1d26 --- /dev/null +++ b/maxscale-system-test/fwf_com_ping.cpp @@ -0,0 +1,33 @@ +/** + * MXS-1111: Dbfwfilter COM_PING test + * + * Check that COM_PING is allowed with `action=allow` + */ + +#include "testconnections.h" + +const char *rules = "rule test1 deny regex '.*'\n" + "users %@% match any rules test1\n"; + +int main(int argc, char** argv) +{ + /** Create the rule file */ + FILE *file = fopen("rules.txt", "w"); + fwrite(rules, 1, strlen(rules), file); + fclose(file); + + TestConnections::skip_maxscale_start(true); + TestConnections test(argc, argv); + + test.ssh_maxscale(true, "mkdir -p /home/vagrant/rules/; chown -R vagrant:vagrant /home/vagrant/rules/"); + test.copy_to_maxscale((char*)"rules.txt", (char*)"~/rules/rules.txt"); + test.ssh_maxscale(true, "chmod a+r /home/vagrant/rules/rules.txt;"); + + test.restart_maxscale(); + test.connect_maxscale(); + test.tprintf("Pinging MaxScale, expecting success"); + test.add_result(mysql_ping(test.conn_rwsplit), "Ping should not fail: %s", mysql_error(test.conn_rwsplit)); + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/fwf_duplicate_rules.cpp b/maxscale-system-test/fwf_duplicate_rules.cpp new file mode 100644 index 000000000..0b9d556f7 --- /dev/null +++ b/maxscale-system-test/fwf_duplicate_rules.cpp @@ -0,0 +1,36 @@ +/** + * Dbfwfilter duplicate rule test + * + * Check if duplicate rules are detected. + */ + +#include "testconnections.h" + +const char *rules = "rule test1 deny no_where_clause\n" + "rule test1 deny columns a b c\n" + "users %@% match any rules test1\n"; + +int main(int argc, char** argv) +{ + /** Create the rule file */ + FILE *file = fopen("rules.txt", "w"); + fwrite(rules, 1, strlen(rules), file); + fclose(file); + + TestConnections::skip_maxscale_start(true); + TestConnections test(argc, argv); + + test.ssh_maxscale(true, "mkdir -p /home/vagrant/rules/; chown -R vagrant:vagrant /home/vagrant/rules/"); + test.copy_to_maxscale((char*)"rules.txt", (char*)"~/rules/rules.txt"); + test.ssh_maxscale(true, "chmod a+r /home/vagrant/rules/rules.txt;"); + + int rc = 0; + + if (test.restart_maxscale() == 0) + { + test.tprintf("Restarting MaxScale succeeded when it should've failed!"); + rc = 1; + } + + return rc; +} diff --git a/maxscale-system-test/fwf_logging.cpp b/maxscale-system-test/fwf_logging.cpp new file mode 100644 index 000000000..2095a2180 --- /dev/null +++ b/maxscale-system-test/fwf_logging.cpp @@ -0,0 +1,56 @@ +/** + * Firewall filter logging test + * + * Check if the log_match and log_no_match parameters work + */ + + +#include +#include +#include "testconnections.h" +#include "fw_copy_rules.h" + +int main(int argc, char** argv) +{ + TestConnections::skip_maxscale_start(true); + char rules_dir[4096]; + + TestConnections *test = new TestConnections(argc, argv); + test->stop_timeout(); + + sprintf(rules_dir, "%s/fw/", test_dir); + + test->tprintf("Creating rules\n"); + test->stop_maxscale(); + copy_rules(test, (char*) "rules_logging", rules_dir); + + test->start_maxscale(); + test->set_timeout(20); + test->connect_maxscale(); + + test->tprintf("trying first: 'select 1'\n"); + test->set_timeout(20); + test->add_result(execute_query_silent(test->conn_slave, "select 1"), "First query should succeed\n"); + + test->tprintf("trying second: 'select 2'\n"); + test->set_timeout(20); + test->add_result(execute_query_silent(test->conn_slave, "select 2"), "Second query should succeed\n"); + + /** Check that MaxScale is alive */ + test->stop_timeout(); + test->check_maxscale_processes(1); + + /** Check that MaxScale was terminated successfully */ + test->stop_maxscale(); + sleep(10); + test->check_maxscale_processes(0); + + /** Check that the logs contains entries for both matching and + * non-matching queries */ + test->check_log_err("matched by", true); + test->check_log_err("was not matched", true); + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/fwf_prepared_stmt.cpp b/maxscale-system-test/fwf_prepared_stmt.cpp new file mode 100644 index 000000000..11b884a38 --- /dev/null +++ b/maxscale-system-test/fwf_prepared_stmt.cpp @@ -0,0 +1,53 @@ +/** + * Dbfwfilter prepared statement test + * + * Checks that both text protocol and binary protocol prepared statements are + * properly handled. + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + TestConnections::skip_maxscale_start(true); + TestConnections test(argc, argv); + test.ssh_maxscale(true, "mkdir -p /home/vagrant/rules/;" + "echo 'rule test1 deny columns c on_queries select' > /home/vagrant/rules/rules.txt;" + "echo 'users %%@%% match any rules test1' >> /home/vagrant/rules/rules.txt;" + "chmod a+r /home/vagrant/rules/rules.txt;"); + + test.add_result(test.restart_maxscale(), "Restarting MaxScale failed"); + + test.connect_maxscale(); + execute_query_silent(test.conn_rwsplit, "DROP TABLE test.t1"); + + test.try_query(test.conn_rwsplit, "CREATE TABLE test.t1(a INT, b INT, c INT)"); + test.try_query(test.conn_rwsplit, "INSERT INTO test.t1 VALUES (1, 1, 1)"); + + test.add_result(execute_query(test.conn_rwsplit, "PREPARE my_ps FROM 'SELECT a, b FROM test.t1'"), + "Text protocol preparation should succeed"); + test.add_result(execute_query(test.conn_rwsplit, "EXECUTE my_ps"), + "Text protocol execution should succeed"); + + test.add_result(execute_query(test.conn_rwsplit, "PREPARE my_ps2 FROM 'SELECT c FROM test.t1'") == 0, + "Text protocol preparation should fail"); + test.add_result(execute_query(test.conn_rwsplit, "EXECUTE my_ps2") == 0, + "Text protocol execution should fail"); + + MYSQL_STMT* stmt = mysql_stmt_init(test.conn_rwsplit); + const char *query = "SELECT a, b FROM test.t1"; + + test.add_result(mysql_stmt_prepare(stmt, query, strlen(query)), "Binary protocol preparation should succeed"); + test.add_result(mysql_stmt_execute(stmt), "Binary protocol execution should succeed"); + mysql_stmt_close(stmt); + + stmt = mysql_stmt_init(test.conn_rwsplit); + query = "SELECT c FROM test.t1"; + + test.add_result(!mysql_stmt_prepare(stmt, query, strlen(query)), "Binary protocol preparation should fail"); + mysql_stmt_close(stmt); + + test.try_query(test.conn_rwsplit, "DROP TABLE test.t1"); + + return test.global_result; +} diff --git a/maxscale-system-test/fwf_reload.cpp b/maxscale-system-test/fwf_reload.cpp new file mode 100644 index 000000000..a820caf59 --- /dev/null +++ b/maxscale-system-test/fwf_reload.cpp @@ -0,0 +1,112 @@ +/** + * @file fwf_reload - Same as fwf but with reloading of rules + */ + + +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "fw_copy_rules.h" + +int main(int argc, char *argv[]) +{ + TestConnections::skip_maxscale_start(true); + TestConnections *Test = new TestConnections(argc, argv); + char sql[4096]; + char pass_file[4096]; + char deny_file[4096]; + char rules_dir[4096]; + + sprintf(rules_dir, "%s/fw/", test_dir); + int N = 10; + int i; + + Test->stop_maxscale(); + char first_rule[] = "rules1"; + copy_rules(Test, first_rule, rules_dir); + Test->start_maxscale(); + Test->connect_rwsplit(); + + + for (i = 1; i <= N; i++) + { + char str[1024]; + sprintf(str, "rules%d", i); + Test->set_timeout(180); + copy_rules(Test, str, rules_dir); + Test->ssh_maxscale(true, "maxadmin call command dbfwfilter rules/reload Database-Firewall"); + + int local_result = 0; + sprintf(pass_file, "%s/fw/pass%d", test_dir, i); + FILE *file = fopen(pass_file, "r"); + + if (file) + { + Test->tprintf("********** Trying queries that should be OK ********** \n"); + + while (!feof(file)) + { + Test->set_timeout(180); + + if (execute_query_from_file(Test->conn_rwsplit, file) == 1) + { + Test->tprintf("Query should succeed: %s\n", sql); + local_result++; + } + } + fclose(file); + } + else + { + Test->add_result(1, "Error opening file '%s': %d, %s\n", pass_file, errno, strerror(errno)); + break; + } + + sprintf(deny_file, "%s/fw/deny%d", test_dir, i); + file = fopen(deny_file, "r"); + + if (file) + { + Test->tprintf("********** Trying queries that should FAIL ********** \n"); + + while (!feof(file)) + { + Test->set_timeout(180); + + int rc = execute_query_from_file(Test->conn_rwsplit, file); + + if (rc != -1 && (rc == 0 || + mysql_errno(Test->conn_rwsplit) != 1141)) + { + Test->tprintf("Query should fail: %s\n", sql); + local_result++; + } + } + + fclose(file); + } + else + { + Test->add_result(1, "Error opening file '%s': %d, %s\n", deny_file, errno, strerror(errno)); + break; + } + + Test->add_result(local_result, "********** rules%d test FAILED\n", i); + } + + Test->tprintf("Trying rules with syntax error\n"); + copy_rules(Test, (char *) "rules_syntax_error", rules_dir); + + char *output = Test->ssh_maxscale_output(true, + "maxadmin call command dbfwfilter rules/reload \"Database Firewall\""); + Test->add_result(strcasestr(output, "Failed") == NULL, "Reloading rules should fail with syntax errors"); + + Test->check_maxscale_processes(1); + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/fwf_syntax.cpp b/maxscale-system-test/fwf_syntax.cpp new file mode 100644 index 000000000..be1aa19c0 --- /dev/null +++ b/maxscale-system-test/fwf_syntax.cpp @@ -0,0 +1,85 @@ +/** + * Firewall filter syntax error test + * + * Generate various syntax errors and check if they are detected. + * With every rule file in this test, MaxScale should not start and the error + * log should contain a message about a syntax error. + */ + + +#include +#include +#include "testconnections.h" +#include "fw_copy_rules.h" + +const char *temp_rules = "rules_tmp.txt"; + +const char *users_ok[] = +{ + "users %@% match any rules testrule", + NULL +}; + +const char *rules_failure[] = +{ + "rule testrule deny nothing", + "rule testrule deny regex", + "rule testrule deny columns", + "rule testrule deny limit_queries", + "rule testrule deny no-where-clause", + "rule testrule deny wildcard wildcard", + "rule testrule deny wildcard rule testrule deny no_where_clause", + "rule testrule allow anything", + "rule testrule block", + "rule deny wildcard", + "testrule deny wildcard", + "rule testrule deny wildcard on_queries select | not_select", + "rule testrule deny wildcard on_queries select|not_select", + "rule testrule deny wildcard on_queries select |", + "rule testrule deny wildcard on_queries select|", + "rule ᐫᐬᐭᐮᐯᐰᐱ deny wildcard on_queries select|", + NULL +}; + +void truncate_maxscale_logs(TestConnections *test) +{ + test->ssh_maxscale(true, "truncate -s 0 /var/log/maxscale/*"); +} + +void add_rule(const char *rule) +{ + FILE *file = fopen(temp_rules, "a"); + fprintf(file, "%s\n", rule); + fclose(file); +} + +int main(int argc, char** argv) +{ + TestConnections::skip_maxscale_start(true); + TestConnections *test = new TestConnections(argc, argv); + test->stop_timeout(); + test->stop_maxscale(); + + for (int i = 0; rules_failure[i]; i++) + { + /** Create rule file with syntax error */ + truncate(temp_rules, 0); + add_rule(rules_failure[i]); + add_rule(users_ok[0]); + copy_rules(test, (char*)temp_rules, (char*)test_dir); + + test->tprintf("Testing rule: %s\n", rules_failure[i]); + test->start_maxscale(); + sleep(3); + + /** Check that MaxScale did not start and that the log contains + * a message about the syntax error. */ + test->check_maxscale_processes(0); + test->check_log_err("syntax error", true); + truncate_maxscale_logs(test); + } + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/galera_priority.cpp b/maxscale-system-test/galera_priority.cpp new file mode 100644 index 000000000..538bf61e2 --- /dev/null +++ b/maxscale-system-test/galera_priority.cpp @@ -0,0 +1,159 @@ +/** + * @file galera_priority.cpp Galera node priority test + * + * Node priorities are configured in the following order: + * node3 > node1 > node4 > node2 + * + * The test executes a SELECT @@server_id to get the server id of each + * node. The same query is executed in a transaction through MaxScale + * and the server id should match the expected output depending on which + * of the nodes are available. The simple test blocks nodes from highest priority + * to lowest priority. + */ + + +#include +#include "testconnections.h" + +using namespace std; + +int check_server_id(TestConnections* test, char *node_id) +{ + char str[1024]; + int rval = 0; + if (execute_query(test->conn_rwsplit, "BEGIN") || + find_field(test->conn_rwsplit, "SELECT @@server_id", "@@server_id", str) || + execute_query(test->conn_rwsplit, "COMMIT")) + { + test->tprintf("Failed to compare @@server_id.\n"); + rval = 1; + } + else if (strcmp(node_id, str)) + { + test->tprintf("@@server_id is %s instead of %s\n", str, node_id); + rval = 1; + } + return rval; +} + +int simple_failover(TestConnections* test) +{ + test->galera->connect(); + int rval = 0; + bool blocked = false; + char server_id[test->galera->N][1024]; + + /** Get server_id for each node */ + for (int i = 0; i < test->galera->N; i++) + { + sprintf(server_id[i], "%d", test->galera->get_server_id(i)); + } + + do + { + /** Node 3 should be master */ + test->tprintf("Executing SELECT @@server_id, expecting '%s'...\n", server_id[2]); + if (test->connect_rwsplit() || check_server_id(test, server_id[2])) + { + test->tprintf("Test failed without any blocked nodes.\n"); + rval = 1; + break; + } + test->close_rwsplit(); + test->galera->block_node(2); + blocked = true; + test->tprintf("OK\n"); + sleep(15); + + /** Block node 3 and node 1 should be master */ + test->tprintf("Expecting '%s'...\n", server_id[0]); + if (test->connect_rwsplit() || check_server_id(test, server_id[0])) + { + test->tprintf("Test failed with first blocked node.\n"); + rval = 1; + break; + } + test->close_rwsplit(); + test->galera->block_node(0); + test->tprintf("OK\n"); + sleep(15); + + /** Block node 1 and node 4 should be master */ + test->tprintf("Expecting '%s'...\n", server_id[3]); + if (test->connect_rwsplit() || check_server_id(test, server_id[3])) + { + test->tprintf("Test failed with second blocked node.\n"); + rval = 1; + break; + } + test->close_rwsplit(); + test->galera->block_node(3); + test->tprintf("OK\n"); + sleep(15); + + /** Block node 4 and node 2 should be master */ + test->tprintf("Expecting '%s'...\n", server_id[1]); + if (test->connect_rwsplit() || check_server_id(test, server_id[1])) + { + test->tprintf("Test failed with third blocked node.\n"); + rval = 1; + break; + } + test->close_rwsplit(); + test->galera->block_node(1); + test->tprintf("OK\n"); + sleep(15); + + /** All nodes blocked, expect failure */ + test->tprintf("Expecting failure...\n"); + int myerrno = 0; + if ((myerrno = test->connect_rwsplit()) == 0 && test->conn_rwsplit) + { + test->tprintf("Connecting to rwsplit was expected to fail but it was" + " successful. Returned error was %d.\n", myerrno); + if (execute_query(test->conn_rwsplit, "SELECT @@server_id") == 0) + { + test->tprintf("SELECT @@server_id was expected to fail but the query was successful.\n"); + } + else + { + test->tprintf("Connection succeeded but query failed.\n"); + } + test->tprintf("Test failed with all nodes blocked.\n"); + rval = 1; + } + test->tprintf("OK\n"); + + /** Unblock all nodes, node 3 should be master again */ + test->galera->unblock_all_nodes(); + blocked = false; + sleep(15); + test->tprintf("Expecting '%s'...\n", server_id[2]); + if (test->connect_rwsplit() || check_server_id(test, server_id[2])) + { + test->tprintf("Test failed after unblocking all nodes.\n"); + rval = 1; + break; + } + test->close_rwsplit(); + test->tprintf("OK\n"); + } + while (false); + + if (blocked) + { + test->galera->unblock_all_nodes(); + } + return rval; +} + +int main(int argc, char **argv) +{ + TestConnections *test = new TestConnections(argc, argv); + test->galera->verbose = false; + int rval1 = 0; + rval1 += simple_failover(test); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/gatekeeper.cpp b/maxscale-system-test/gatekeeper.cpp new file mode 100644 index 000000000..a01a15344 --- /dev/null +++ b/maxscale-system-test/gatekeeper.cpp @@ -0,0 +1,92 @@ + +#include +#include +#include "testconnections.h" + +/** + * Test for the gatekeeper module + */ + +const char* training_queries[] = +{ + "SELECT * FROM test.t1 WHERE id = 1", + "INSERT INTO test.t1 VALUES (1)", + "UPDATE test.t1 SET id = 2 WHERE id = 1", + NULL +}; + +const char* allowed_queries[] = +{ + "SELECT * FROM test.t1 WHERE id = 1", + "SELECT * FROM test.t1 WHERE id = 2", + "SELECT * FROM test.t1 WHERE id = 102", + "INSERT INTO test.t1 VALUES (1)", + "INSERT INTO test.t1 VALUES (124)", + "INSERT INTO test.t1 VALUES (127419823)", + "UPDATE test.t1 SET id = 4 WHERE id = 1", + "UPDATE test.t1 SET id = 3 WHERE id = 2", + "UPDATE test.t1 SET id = 2 WHERE id = 3", + "UPDATE test.t1 SET id = 1 WHERE id = 4", + " UPDATE test.t1 SET id = 1 WHERE id = 4 ", + NULL +}; + +const char* denied_queries[] = +{ + "SELECT * FROM test.t1 WHERE id = 1 OR 1=1", + "INSERT INTO test.t1 VALUES (1), ('This is not a number')", + "UPDATE test.t1 SET id = 2 WHERE id = 1 OR id > 0", + NULL +}; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->ssh_maxscale(true, "rm -f /var/lib/maxscale/gatekeeper.data"); + Test->set_timeout(30); + + Test->connect_rwsplit(); + + Test->try_query(Test->conn_rwsplit, "CREATE OR REPLACE TABLE test.t1 (id INT)"); + + for (int i = 0; training_queries[i]; i++) + { + Test->try_query(Test->conn_rwsplit, training_queries[i]); + } + + Test->close_rwsplit(); + + Test->ssh_maxscale(true, "sed -i -e 's/mode=learn/mode=enforce/' /etc/maxscale.cnf"); + + Test->restart_maxscale(); + + sleep(5); + + Test->connect_rwsplit(); + + for (int i = 0; training_queries[i]; i++) + { + Test->set_timeout(30); + Test->add_result(execute_query(Test->conn_rwsplit, training_queries[i]), "Query should not fail: %s", + training_queries[i]); + } + + for (int i = 0; allowed_queries[i]; i++) + { + Test->set_timeout(30); + Test->add_result(execute_query(Test->conn_rwsplit, allowed_queries[i]), "Query should not fail: %s", + allowed_queries[i]); + } + + for (int i = 0; denied_queries[i]; i++) + { + Test->set_timeout(30); + Test->add_result(execute_query(Test->conn_rwsplit, denied_queries[i]) == 0, "Query should fail: %s", + denied_queries[i]); + } + + Test->ssh_maxscale(true, "rm -f /var/lib/maxscale/gatekeeper.data"); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/generate_log_sql.cpp b/maxscale-system-test/generate_log_sql.cpp new file mode 100644 index 000000000..7aea3cc0a --- /dev/null +++ b/maxscale-system-test/generate_log_sql.cpp @@ -0,0 +1,30 @@ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + + +int main(int argc, char *argv[]) +{ + char sql[1000000]; + create_insert_string(sql, 16, 0); + + printf("%s\n", sql); + + create_insert_string(sql, 256, 1); + + printf("%s\n", sql); + + create_insert_string(sql, 4096, 2); + + printf("%s\n", sql); + + create_insert_string(sql, 65536, 3); + + printf("%s\n", sql); + + exit(0); +} diff --git a/maxscale-system-test/get_com_select_insert.cpp b/maxscale-system-test/get_com_select_insert.cpp new file mode 100644 index 000000000..a821990d9 --- /dev/null +++ b/maxscale-system-test/get_com_select_insert.cpp @@ -0,0 +1,105 @@ +#include "testconnections.h" + +/** +Reads COM_SELECT and COM_INSERT variables from all nodes and stores into 'selects' and 'inserts' +*/ +int get_global_status_allnodes(long int *selects, long int *inserts, Mariadb_nodes * nodes, int silent) +{ + int i; + MYSQL_RES *res; + MYSQL_ROW row; + + for (i = 0; i < nodes->N; i++) + { + if (nodes->nodes[i] != NULL) + { + + if (mysql_query(nodes->nodes[i], "show global status like 'COM_SELECT';") != 0) + { + printf("Error: can't execute SQL-query\n"); + printf("%s\n", mysql_error(nodes->nodes[i])); + return 1; + } + + res = mysql_store_result(nodes->nodes[i]); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + return 1; + } + + if (mysql_num_rows(res) > 0) + { + while ((row = mysql_fetch_row(res)) != NULL) + { + if (silent == 0) + { + printf("Node %d COM_SELECT=%s\n", i, row[1]); + } + sscanf(row[1], "%ld", &selects[i]); + } + } + + mysql_free_result(res); + while ( mysql_next_result(nodes->nodes[i]) == 0 ) + { + res = mysql_store_result(nodes->nodes[i]); + mysql_free_result(res); + } + + if (mysql_query(nodes->nodes[i], "show global status like 'COM_INSERT';") != 0) + { + printf("Error: can't execute SQL-query\n"); + } + + res = mysql_store_result(nodes->nodes[i]); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + } + + if (mysql_num_rows(res) > 0) + { + while ((row = mysql_fetch_row(res)) != NULL) + { + if (silent == 0) + { + printf("Node %d COM_INSERT=%s\n", i, row[1]); + } + sscanf(row[1], "%ld", &inserts[i]); + } + } + + mysql_free_result(res); + while ( mysql_next_result(nodes->nodes[i]) == 0 ) + { + res = mysql_store_result(nodes->nodes[i]); + mysql_free_result(res); + } + } + else + { + selects[i] = 0; + inserts[i] = 0; + } + } + return 0; + +} + +/** +Prints difference in COM_SELECT and COM_INSERT +*/ +int print_delta(long int *new_selects, long int *new_inserts, long int *selects, long int *inserts, + int nodes_num) +{ + int i; + for (i = 0; i < nodes_num; i++) + { + printf("COM_SELECT increase on node %d is %ld\n", i, new_selects[i] - selects[i]); + printf("COM_INSERT increase on node %d is %ld\n", i, new_inserts[i] - inserts[i]); + } + return 0; +} + + diff --git a/maxscale-system-test/get_com_select_insert.h b/maxscale-system-test/get_com_select_insert.h new file mode 100644 index 000000000..2beb46376 --- /dev/null +++ b/maxscale-system-test/get_com_select_insert.h @@ -0,0 +1,29 @@ +#ifndef GET_COM_SELECT_INSERT_H +#define GET_COM_SELECT_INSERT_H + +#include "testconnections.h" + +/** + * @brief get_global_status_allnodes Reads COM_SELECT and COM_INSERT variables from all nodes and stores into 'selects' and 'inserts' + * @param selects pointer to array to store COM_SELECT for all nodes + * @param inserts pointer to array to store COM_INSERT for all nodes + * @param nodes Mariadb_nodes object that contains information about nodes + * @param silent if 1 do not print anything + * @return 0 in case of success + */ +int get_global_status_allnodes(long int *selects, long int *inserts, Mariadb_nodes * nodes, int silent); + +/** + * @brief print_delta Prints difference in COM_SELECT and COM_INSERT + * @param new_selects pointer to array to store COM_SELECT for all nodes after test + * @param new_inserts pointer to array to store COM_INSERT for all nodes after test + * @param selects pointer to array to store COM_SELECT for all nodes before test + * @param inserts pointer to array to store COM_INSERT for all nodes before test + * @param NodesNum Number of nodes + * @return + */ +int print_delta(long int *new_selects, long int *new_inserts, long int *selects, long int *inserts, + int nodes_num); + + +#endif // GET_COM_SELECT_INSERT_H diff --git a/maxscale-system-test/get_logs.sh b/maxscale-system-test/get_logs.sh new file mode 100755 index 000000000..b97a52999 --- /dev/null +++ b/maxscale-system-test/get_logs.sh @@ -0,0 +1,15 @@ +#!/bin/bash +#set -x + +export maxscale_sshkey=$maxscale_keyfile +if [ $maxscale_IP != "127.0.0.1" ] ; then + ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP "mkdir -p logs; $maxscale_access_sudo cp $maxscale_log_dir/* logs/; $maxscale_access_sudo chmod a+r logs/*" + scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:logs/* . + scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP:$maxscale_cnf . +else + mkdir -p logs; + sudo cp $maxscale_log_dir/* logs/ + cp $maxscale_cnf logs/ + sudo chmod a+r logs/* + cp logs/* . +fi diff --git a/maxscale-system-test/get_my_ip.cpp b/maxscale-system-test/get_my_ip.cpp new file mode 100644 index 000000000..2b4574d2d --- /dev/null +++ b/maxscale-system-test/get_my_ip.cpp @@ -0,0 +1,59 @@ +/* + * Find local ip used as source ip in ip packets. + * Use getsockname and a udp connection + */ + +#include //printf +#include //memset +#include //errno +#include //socket +#include //sockaddr_in +#include //getsockname +#include //close + +#include "get_my_ip.h" + +int get_my_ip(char * remote_ip, char * my_ip ) +{ + + int dns_port = 53; + + struct sockaddr_in serv; + + int sock = socket ( AF_INET, SOCK_DGRAM, 0); + + //Socket could not be created + if (sock < 0) + { + return 1; + } + + memset( &serv, 0, sizeof(serv) ); + serv.sin_family = AF_INET; + serv.sin_addr.s_addr = inet_addr( remote_ip ); + serv.sin_port = htons( dns_port ); + + int err = connect( sock , (const struct sockaddr*) &serv , sizeof(serv) ); + + struct sockaddr_in name; + socklen_t namelen = sizeof(name); + err = getsockname(sock, (struct sockaddr*) &name, &namelen); + + char buffer[100]; + const char* p = inet_ntop(AF_INET, &name.sin_addr, buffer, 100); + + if (p != NULL) + { + //printf("Local ip is : %s \n" , buffer); + strcpy(my_ip, buffer); + close(sock); + return 0; + } + else + { + //Some error + printf ("Error number : %d . Error message : %s \n" , errno , strerror(errno)); + close(sock); + return 2; + } +} diff --git a/maxscale-system-test/get_my_ip.h b/maxscale-system-test/get_my_ip.h new file mode 100644 index 000000000..255d935b0 --- /dev/null +++ b/maxscale-system-test/get_my_ip.h @@ -0,0 +1,13 @@ +#ifndef GET_MY_IP_H +#define GET_MY_IP_H + +/** + * @brief get_my_ip Get IP address of machine where this code is executed as it is visible from remote machine + * Connects to DNS port 53 of remote machine and gets own IP from socket info + * @param remote_ip IP of remote machine + * @param my_ip Pointer to result (own IP string) + * @return 0 in case of success + */ +int get_my_ip(char * remote_ip, char *my_ip ); + +#endif // GET_MY_IP_H diff --git a/maxscale-system-test/insertstream.sh b/maxscale-system-test/insertstream.sh new file mode 100755 index 000000000..6e7a59c61 --- /dev/null +++ b/maxscale-system-test/insertstream.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +./mysqltest_driver.sh insertstream insertstream 4006 diff --git a/maxscale-system-test/insertstream/r/insert.result b/maxscale-system-test/insertstream/r/insert.result new file mode 100644 index 000000000..fed1b5fd7 --- /dev/null +++ b/maxscale-system-test/insertstream/r/insert.result @@ -0,0 +1,118 @@ +DROP TABLE IF EXISTS test.t1; +CREATE TABLE test.t1(id INT); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +COMMIT; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +1 +DELETE FROM test.t1; +FLUSH STATUS; +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (2); +INSERT INTO test.t1 VALUES (3); +COMMIT; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +3 +DELETE FROM test.t1; +FLUSH STATUS; +START TRANSACTION; +INSERT INTO test.t1 VALUES (1), (2), (3); +COMMIT; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +3 +DELETE FROM test.t1; +FLUSH STATUS; +INSERT INTO test.t1 VALUES (1); +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 1 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 0 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +1 +DELETE FROM test.t1; +FLUSH STATUS; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (2); +INSERT INTO test.t1 VALUES (3); +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 3 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 0 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +3 +DELETE FROM test.t1; +FLUSH STATUS; +DROP TABLE IF EXISTS test.t2; +CREATE TABLE test.t2(id int); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t2 VALUES (1); +ERROR HY000: Invalid insert target +COMMIT; +SELECT COUNT(*) FROM test.t2; +COUNT(*) +0 +DROP TABLE test.t2; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +1 +DELETE FROM test.t1; +FLUSH STATUS; +CREATE TABLE test.t2(id int); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT 1; +1 +1 +INSERT INTO test.t2 VALUES (1); +COMMIT; +SELECT COUNT(*) FROM test.t2; +COUNT(*) +1 +DROP TABLE test.t2; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 2 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +1 +DELETE FROM test.t1; +FLUSH STATUS; +DROP TABLE test.t1; diff --git a/maxscale-system-test/insertstream/r/mixed.result b/maxscale-system-test/insertstream/r/mixed.result new file mode 100644 index 000000000..3ee9f3e34 --- /dev/null +++ b/maxscale-system-test/insertstream/r/mixed.result @@ -0,0 +1,68 @@ +DROP TABLE IF EXISTS test.t1; +CREATE TABLE test.t1(id INT); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT 1; +1 +1 +INSERT INTO test.t1 VALUES (1); +COMMIT; +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 0 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 2 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +2 +DELETE FROM test.t1; +FLUSH STATUS; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +COMMIT; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 4 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +5 +DELETE FROM test.t1; +FLUSH STATUS; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +SELECT COUNT(*) FROM test.t1; +COUNT(*) +2 +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT COUNT(*) FROM test.t1; +COUNT(*) +3 +UPDATE test.t1 SET id = 0; +DELETE FROM test.t1; +COMMIT; +SELECT COUNT(*) FROM test.t1; +COUNT(*) +0 +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 4 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 1 +SELECT COUNT(*) FROM test.t1; +COUNT(*) +2 +DELETE FROM test.t1; +FLUSH STATUS; +DROP TABLE test.t1; diff --git a/maxscale-system-test/insertstream/r/non-trx.result b/maxscale-system-test/insertstream/r/non-trx.result new file mode 100644 index 000000000..b2afa6c26 --- /dev/null +++ b/maxscale-system-test/insertstream/r/non-trx.result @@ -0,0 +1,29 @@ +DROP TABLE IF EXISTS test.t1; +CREATE TABLE test.t1(id INT); +SELECT COUNT(*) FROM test.t1; +COUNT(*) +0 +DELETE FROM test.t1; +INSERT INTO test.t1 VALUES (1), (2), (3); +SELECT COUNT(*) FROM test.t1; +COUNT(*) +3 +UPDATE test.t1 SET id = 0; +CREATE TABLE test.new_table(id int) ENGINE=MyISAM; +INSERT INTO test.new_table VALUES (1), (2); +DROP TABLE test.new_table; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +SHOW STATUS LIKE 'COM_INSERT'; +Variable_name Value +Com_insert 10 +SHOW STATUS LIKE 'COM_LOAD'; +Variable_name Value +Com_load 0 +DROP TABLE test.t1; diff --git a/maxscale-system-test/insertstream/t/insert.test b/maxscale-system-test/insertstream/t/insert.test new file mode 100644 index 000000000..76d76cdb3 --- /dev/null +++ b/maxscale-system-test/insertstream/t/insert.test @@ -0,0 +1,108 @@ +# Setup +--disable_warnings +DROP TABLE IF EXISTS test.t1; +--enable_warnings +CREATE TABLE test.t1(id INT); + +# Test one insert inside transaction + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +COMMIT; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test multiple inserts inside transaction + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (2); +INSERT INTO test.t1 VALUES (3); +COMMIT; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test multi-value insert inside transaction + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1), (2), (3); +COMMIT; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test non-transaction inserts + +INSERT INTO test.t1 VALUES (1); + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (2); +INSERT INTO test.t1 VALUES (3); + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test different tables in inserts inside transactions + +--disable_warnings +DROP TABLE IF EXISTS test.t2; +--enable_warnings +CREATE TABLE test.t2(id int); + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +--disable_abort_on_error +INSERT INTO test.t2 VALUES (1); +--enable_abort_on_error +COMMIT; + +SELECT COUNT(*) FROM test.t2; +DROP TABLE test.t2; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test inserts to different tables with selects between them inside a transaction + +CREATE TABLE test.t2(id int); + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT 1; +INSERT INTO test.t2 VALUES (1); +COMMIT; + +SELECT COUNT(*) FROM test.t2; +DROP TABLE test.t2; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Cleanup +DROP TABLE test.t1; diff --git a/maxscale-system-test/insertstream/t/mixed.test b/maxscale-system-test/insertstream/t/mixed.test new file mode 100644 index 000000000..46065c91e --- /dev/null +++ b/maxscale-system-test/insertstream/t/mixed.test @@ -0,0 +1,59 @@ +# Setup +--disable_warnings +DROP TABLE IF EXISTS test.t1; +--enable_warnings +CREATE TABLE test.t1(id INT); + +# Test mixed queries in transactions + +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT 1; +INSERT INTO test.t1 VALUES (1); +COMMIT; + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Test transaction and non-transaction inserts + +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +COMMIT; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Mix inserts and selects in and out of transactions + +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +SELECT COUNT(*) FROM test.t1; +START TRANSACTION; +INSERT INTO test.t1 VALUES (1); +SELECT COUNT(*) FROM test.t1; +UPDATE test.t1 SET id = 0; +DELETE FROM test.t1; +COMMIT; +SELECT COUNT(*) FROM test.t1; +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +FLUSH STATUS; + +# Cleanup +DROP TABLE test.t1; diff --git a/maxscale-system-test/insertstream/t/non-trx.test b/maxscale-system-test/insertstream/t/non-trx.test new file mode 100644 index 000000000..872bef222 --- /dev/null +++ b/maxscale-system-test/insertstream/t/non-trx.test @@ -0,0 +1,31 @@ +# Setup +--disable_warnings +DROP TABLE IF EXISTS test.t1; +--enable_warnings +CREATE TABLE test.t1(id INT); + +# Test that nothing increases COM_LOAD outside of a transaction + +SELECT COUNT(*) FROM test.t1; +DELETE FROM test.t1; +INSERT INTO test.t1 VALUES (1), (2), (3); +SELECT COUNT(*) FROM test.t1; +UPDATE test.t1 SET id = 0; +CREATE TABLE test.new_table(id int) ENGINE=MyISAM; +INSERT INTO test.new_table VALUES (1), (2); +DROP TABLE test.new_table; + +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); +INSERT INTO test.t1 VALUES (1); + +SHOW STATUS LIKE 'COM_INSERT'; +SHOW STATUS LIKE 'COM_LOAD'; + +# Cleanup +DROP TABLE test.t1; diff --git a/maxscale-system-test/install_aws_tool.sh b/maxscale-system-test/install_aws_tool.sh new file mode 100755 index 000000000..4be9e729e --- /dev/null +++ b/maxscale-system-test/install_aws_tool.sh @@ -0,0 +1,4 @@ +sudo yum install -y curl unzip +curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" +unzip awscli-bundle.zip +sudo ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws diff --git a/maxscale-system-test/kerb.cnf b/maxscale-system-test/kerb.cnf new file mode 100644 index 000000000..74fd1671c --- /dev/null +++ b/maxscale-system-test/kerb.cnf @@ -0,0 +1,3 @@ +[mysqld] +gssapi-keytab-path=/etc/krb5.keytab +gssapi-principal-name=mariadb/maxscale.test@MAXSCALE.TEST diff --git a/maxscale-system-test/kerberos_setup.cpp b/maxscale-system-test/kerberos_setup.cpp new file mode 100644 index 000000000..a6a2fe7c7 --- /dev/null +++ b/maxscale-system-test/kerberos_setup.cpp @@ -0,0 +1,139 @@ +/** + * @file kerberos_setup.cpp Attempt to configure KDC and try to use passwordless authentification + * - configure KDC on Maxscale machine and Kerberos workstation on all other nodes + * - create MariaDB user which is authentificated via GSSAPI + * - try to login to Maxscale as this GSSAPI user and execute simple query + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(1000); + char str[1024]; + + int i; + + // To be moved to MDBCI + Test->tprintf("Creating 'hosts' file\n"); + FILE * f; + f = fopen("hosts", "wt"); + for (i = 0; i < Test->repl->N; i++) + { + fprintf(f, "%s node_%03d.maxscale.test\n", Test->repl->IP[i], i); + } + fprintf(f, "%s maxscale.maxscale.test\n", Test->maxscale_IP); + fclose(f); + + Test->tprintf("Copying 'hosts' and krb5.conf files to all nodes, installing kerberos client and MariaDB plugins\n"); + sprintf(str, "%s/krb5.conf", test_dir); + for (i = 0; i < Test->repl->N; i++) + { + Test->repl->ssh_node(i, (char *) + "yum install -y MariaDB-gssapi-server MariaDB-gssapi-client krb5-workstation pam_krb5", true); + Test->repl->copy_to_node(str, (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/krb5.conf /etc/", true); + + Test->repl->copy_to_node((char *) "hosts", (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/hosts /etc/", true); + } + + Test->tprintf("Copying 'hosts' and krb5.conf files to Maxscale node\n"); + + Test->copy_to_maxscale((char *) "hosts", (char *) "~/"); + Test->ssh_maxscale(true, (char *) "cp ~/hosts /etc/"); + + Test->copy_to_maxscale(str, (char *) "~/"); + Test->ssh_maxscale(true, (char *) "cp ~/krb5.conf /etc/"); + + Test->tprintf("Instaling Kerberos server packages to Maxscale node\n"); + Test->ssh_maxscale(true, (char *) "yum install rng-tools -y"); + Test->ssh_maxscale(true, (char *) "rngd -r /dev/urandom -o /dev/random"); + + Test->ssh_maxscale(true, (char *) + "yum install -y MariaDB-gssapi-server MariaDB-gssapi-client krb5-server krb5-workstation pam_krb5"); + + + Test->tprintf("Configuring Kerberos server\n"); + Test->ssh_maxscale(true, (char *) "sed -i \"s/EXAMPLE.COM/MAXSCALE.TEST/\" /var/kerberos/krb5kdc/kdc.conf"); + Test->ssh_maxscale(true, (char *) "sed -i \"s/EXAMPLE.COM/MAXSCALE.TEST/\" /var/kerberos/krb5kdc/kadm5.acl"); + + Test->tprintf("Creating Kerberos DB and admin principal\n"); + Test->ssh_maxscale(true, (char *) "kdb5_util create -P skysql -r MAXSCALE.TEST -s"); + Test->ssh_maxscale(true, (char *) "kadmin.local -q \"addprinc -pw skysql admin/admin@MAXSCALE.TEST\""); + + Test->tprintf("Opening ports 749 and 88\n"); + Test->ssh_maxscale(true, (char *) "iptables -I INPUT -p tcp --dport 749 -j ACCEPT"); + Test->ssh_maxscale(true, (char *) "iptables -I INPUT -p tcp --dport 88 -j ACCEPT"); + + Test->tprintf("Starting Kerberos\n"); + Test->ssh_maxscale(true, (char *) "service krb5kdc start"); + Test->ssh_maxscale(true, (char *) "service kadmin start"); + + Test->tprintf("Creating principal\n"); + Test->ssh_maxscale(true, (char *) + "echo \"skysql\" | sudo kadmin -p admin/admin -q \"addprinc -randkey mariadb/maxscale.test\""); + + Test->tprintf("Creating keytab file\n"); + Test->ssh_maxscale(true, (char *) + "echo \"skysql\" | sudo kadmin -p admin/admin -q \"ktadd mariadb/maxscale.test\""); + + Test->tprintf("Making keytab file readable for all\n"); + Test->ssh_maxscale(true, (char *) "chmod a+r /etc/krb5.keytab;"); + + Test->ssh_maxscale(false, (char *) "kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab"); + Test->ssh_maxscale(true, (char *) + "su maxscale --login -s /bin/sh -c \"kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab\""); + + Test->tprintf("Coping keytab file from Maxscale node\n"); + Test->copy_from_maxscale((char *) "/etc/krb5.keytab", (char *) "."); + + Test->tprintf("Coping keytab and .cnf files to all nodes and executing knit for all nodes\n"); + for (i = 0; i < Test->repl->N; i++) + { + sprintf(str, "%s/kerb.cnf", test_dir); + Test->repl->copy_to_node(str, (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/kerb.cnf /etc/my.cnf.d/", true); + + Test->repl->copy_to_node((char *) "krb5.keytab", (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/krb5.keytab /etc/", true); + + Test->repl->ssh_node(i, (char *) "kinit mariadb/maxscale.test@MAXSCALE.TEST -k -t /etc/krb5.keytab", false); + } + + Test->tprintf("Installing gssapi plugin to all nodes\n"); + Test->repl->connect(); + Test->repl->execute_query_all_nodes((char *) "INSTALL SONAME 'auth_gssapi'"); + Test->repl->close_connections(); + + Test->tprintf("Creating usr1 user\n"); + Test->repl->connect(); + Test->try_query(Test->repl->nodes[0], + (char *) "CREATE USER usr1 IDENTIFIED VIA gssapi AS 'mariadb/maxscale.test@MAXSCALE.TEST'"); + Test->try_query(Test->repl->nodes[0], (char *) "grant all privileges on *.* to 'usr1'"); + Test->repl->close_connections(); + + Test->tprintf("Trying use usr1 to execute query: RW Split\n"); + Test->add_result( + Test->repl->ssh_node(1, + "echo select User,Host from mysql.user | mysql -uusr1 -h maxscale.maxscale.test -P 4006", false), + "Error executing query against RW Split\n"); + Test->tprintf("Trying use usr1 to execute query: Read Connection Master\n"); + Test->add_result( + Test->repl->ssh_node(1, + "echo select User,Host from mysql.user | mysql -uusr1 -h maxscale.maxscale.test -P 4008", false), + "Error executing query against Read Connection Master\n"); + Test->tprintf("Trying use usr1 to execute query: Read Connection Slave\n"); + Test->add_result( + Test->repl->ssh_node(1, + "echo select User,Host from mysql.user | mysql -uusr1 -h maxscale.maxscale.test -P 4009", false), + "Error executing query against Read Connection Slave\n"); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/kill_master.cpp b/maxscale-system-test/kill_master.cpp new file mode 100644 index 000000000..2fb8fb243 --- /dev/null +++ b/maxscale-system-test/kill_master.cpp @@ -0,0 +1,48 @@ +/** + * @file kill_master.cpp Checks Maxscale behaviour in case if Master node is blocked + * + * - Connect to RWSplit + * - block Mariadb server on Master node by Firewall + * - try simple query *show processlist" expecting failure, but not a crash + * - check if Maxscale is alive + * - reconnect and check if query execution is ok + */ + + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + Test->connect_rwsplit(); + + Test->set_timeout(30); + Test->tprintf("Setup firewall to block mysql on master\n"); + Test->repl->block_node(0); + + Test->tprintf("Trying query to RWSplit, expecting failure, but not a crash\n"); + Test->set_timeout(30); + execute_query(Test->conn_rwsplit, (char *) "show processlist;"); + + Test->set_timeout(30); + Test->tprintf("Setup firewall back to allow mysql\n"); + Test->repl->unblock_node(0); + + Test->stop_timeout(); + sleep(10); + + Test->set_timeout(30); + Test->tprintf("Reconnecting and trying query to RWSplit\n"); + Test->connect_rwsplit(); + Test->try_query(Test->conn_rwsplit, (char *) "show processlist;"); + Test->close_rwsplit(); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/krb5.conf b/maxscale-system-test/krb5.conf new file mode 100644 index 000000000..0fc9a3c91 --- /dev/null +++ b/maxscale-system-test/krb5.conf @@ -0,0 +1,24 @@ +[logging] + default = FILE:/var/log/krb5libs.log + kdc = FILE:/var/log/krb5kdc.log + admin_server = FILE:/var/log/kadmind.log + +[libdefaults] + dns_lookup_realm = false + ticket_lifetime = 24h + renew_lifetime = 7d + forwardable = true + rdns = false + default_realm = MAXSCALE.TEST + default_ccache_name = KEYRING:persistent:%{uid} + +[realms] + MAXSCALE.TEST = { + kdc = maxscale.maxscale.test + admin_server = maxscale.maxscale.test + } + +[domain_realm] + .maxscale.test = MAXSCALE.TEST + maxscale.test = MAXSCALE.TEST + diff --git a/maxscale-system-test/labels_list.sh b/maxscale-system-test/labels_list.sh new file mode 100755 index 000000000..97628c6ef --- /dev/null +++ b/maxscale-system-test/labels_list.sh @@ -0,0 +1 @@ +ctest --print-labels | grep " " | sed "s/ //g" | tr '\n' ',' diff --git a/maxscale-system-test/load_balancing.cpp b/maxscale-system-test/load_balancing.cpp new file mode 100644 index 000000000..a7c23df87 --- /dev/null +++ b/maxscale-system-test/load_balancing.cpp @@ -0,0 +1,90 @@ +/** + * @file load_balancing.cpp Checks how Maxscale balances load + * + * - also used for 'load_balancing_pers1' and 'load_balancing_pers10' tests (with 'persistpoolmax=1' and 'persistpoolmax=10' for all servers) + * + * - start two groups of threads: each group consists of 25 threads, each thread creates connections to RWSplit, + * threads from first group try to execute as many SELECTs as possible, from second group - one query per second + * - after 100 seconds all threads are stopped + * - check number of connections to every slave: test PASSED if COM_SELECT difference between slaves is not greater then 3 times and no + * more then 10% of quesries went to Master + */ + + + +#include "testconnections.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +#include "big_load.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + long int q; + int threads_num = 25; + + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + long int i1, i2; + + if (Test->smoke) + { + threads_num = 15; + } + Test->tprintf("Increasing connection and error limits on backend nodes.\n"); + Test->repl->connect(); + for (int i = 0; i < Test->repl->N; i++) + { + execute_query(Test->repl->nodes[i], (char *) "set global max_connections = 300;"); + execute_query(Test->repl->nodes[i], (char *) "set global max_connect_errors = 100000;"); + } + Test->repl->close_connections(); + + Test->tprintf("Creating query load with %d threads...\n", threads_num); + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], threads_num, Test, &i1, &i2, 1, false, true); + + long int avr = (i1 + i2 ) / (Test->repl->N); + Test->tprintf("average number of quries per node %ld\n", avr); + long int min_q = avr / 3; + long int max_q = avr * 3; + Test->tprintf("Acceplable value for every node from %ld until %ld\n", min_q, max_q); + + for (int i = 1; i < Test->repl->N; i++) + { + q = new_selects[i] - selects[i]; + if ((q > max_q) || (q < min_q)) + { + Test->add_result(1, "number of queries for node %d is %ld\n", i + 1, q); + } + } + + if ((new_selects[0] - selects[0]) > avr / 3 ) + { + Test->add_result(1, + "number of queries for master greater then 30%% of averange number of queries per node\n"); + } + + Test->tprintf("Restoring nodes\n"); + Test->repl->connect(); + for (int i = 0; i < Test->repl->N; i++) + { + execute_query(Test->repl->nodes[i], (char *) "flush hosts;"); + execute_query(Test->repl->nodes[i], (char *) "set global max_connections = 151;"); + } + Test->repl->close_connections(); + + + Test->check_maxscale_alive(); + + Test->repl->start_replication(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/load_balancing_galera.cpp b/maxscale-system-test/load_balancing_galera.cpp new file mode 100644 index 000000000..8c36881b1 --- /dev/null +++ b/maxscale-system-test/load_balancing_galera.cpp @@ -0,0 +1,101 @@ +/** + * @file load_balancing_galera.cpp Checks how Maxscale balances load + * + * - also used for 'load_balancing_galera_pers1' and 'load_balancing_galera_pers10' tests (with 'persistpoolmax=1' and 'persistpoolmax=10' for all servers) + * + * - start two groups of threads: each group consists of 25 threads, each thread creates connections to RWSplit, + * threads from first group try to execute as many SELECTs as possible, from second group - one query per second + * - after 100 seconds all threads are stopped + * - check number of connections to every slave: test PASSED if COM_SELECT difference between slaves is not greater then 3 times and no + * more then 10% of quesries went to Master + */ + + + +#include "testconnections.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +#include "big_load.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + int master; + long int q; + int threads_num = 25; + + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + long int i1, i2; + + Test->set_timeout(20); + master = Test->find_master_maxadmin(Test->galera); + + if (master >= 0) + { + Test->tprintf("Master node is %d (server%d)\n", master, master + 1); + Test->set_timeout(20); + + if (Test->smoke) + { + threads_num = 15; + } + Test->galera->connect(); + for (int i = 0; i < Test->galera->N; i++) + { + execute_query(Test->galera->nodes[i], (char *) "set global max_connections = 300;"); + execute_query(Test->galera->nodes[i], (char *) "set global max_connect_errors = 100000;"); + } + Test->galera->close_connections(); + + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], threads_num, Test, &i1, &i2, 1, true, true); + + long int avr = (i1 + i2 ) / (Test->galera->N); + Test->tprintf("average number of quries per node %ld\n", avr); + long int min_q = avr / 3; + long int max_q = avr * 3; + Test->tprintf("Acceplable value for every node from %ld until %ld\n", min_q, max_q); + + for (int i = 0; i < Test->galera->N; i++) + { + if ( i != master) + { + q = new_selects[i] - selects[i]; + if ((q > max_q) || (q < min_q)) + { + Test->add_result(1, "number of queries for node %d is %ld\n", i + 1, q); + } + } + } + + if ((new_selects[master] - selects[master]) > avr / 3 ) + { + Test->add_result(1, + "number of queries for master greater then 30%% of averange number of queries per node\n"); + } + + Test->tprintf("Restoring nodes\n"); + Test->galera->connect(); + for (int i = 0; i < Test->galera->N; i++) + { + execute_query(Test->galera->nodes[i], (char *) "flush hosts;"); + execute_query(Test->galera->nodes[i], (char *) "set global max_connections = 151;"); + } + Test->galera->close_connections(); + + Test->check_maxscale_alive(); + } + else + { + Test->add_result(1, "Master is not found\n"); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/local_tests/cleanup_db.sh b/maxscale-system-test/local_tests/cleanup_db.sh new file mode 100755 index 000000000..37f0270db --- /dev/null +++ b/maxscale-system-test/local_tests/cleanup_db.sh @@ -0,0 +1,16 @@ +set -x + +dir=`pwd` + + +#cp ~/build-scripts/test/multiple_servers.cnf $dir +sudo killall mysqld +sudo killall mysql_install_db +sleep 10 +rm -rf /data/mysql/mysql$1 +rm -rf /var/log/mysql/* +mkdir -p /data/mysql/mysql$1 +chown mysql:mysql -R /data +chown mysql:mysql -R /var/run/mysqld + +mysql_install_db --defaults-file=$dir/local_tests/multiple_servers.cnf --user=mysql --datadir=/data/mysql/mysql$1 diff --git a/maxscale-system-test/local_tests/create_repl_user.sql b/maxscale-system-test/local_tests/create_repl_user.sql new file mode 100644 index 000000000..91e4a71a1 --- /dev/null +++ b/maxscale-system-test/local_tests/create_repl_user.sql @@ -0,0 +1,4 @@ +#create user repl@'%' identified by 'repl'; +grant replication slave on *.* to repl@'%' identified by 'repl'; + +FLUSH PRIVILEGES; diff --git a/maxscale-system-test/local_tests/create_skysql_user.sql b/maxscale-system-test/local_tests/create_skysql_user.sql new file mode 100644 index 000000000..32cac7019 --- /dev/null +++ b/maxscale-system-test/local_tests/create_skysql_user.sql @@ -0,0 +1,18 @@ +create user skysql@'%' identified by 'skysql'; +create user skysql@'localhost' identified by 'skysql'; +GRANT ALL PRIVILEGES ON *.* TO skysql@'%' WITH GRANT OPTION; +GRANT ALL PRIVILEGES ON *.* TO skysql@'localhost' WITH GRANT OPTION; + +create user maxuser@'%' identified by 'maxpwd'; +create user maxuser@'localhost' identified by 'maxpwd'; +GRANT ALL PRIVILEGES ON *.* TO maxuser@'%' WITH GRANT OPTION; +GRANT ALL PRIVILEGES ON *.* TO maxuser@'localhost' WITH GRANT OPTION; + +create user maxskysql@'%' identified by 'skysql'; +create user maxskysql@'localhost' identified by 'skysql'; +GRANT ALL PRIVILEGES ON *.* TO maxskysql@'%' WITH GRANT OPTION; +GRANT ALL PRIVILEGES ON *.* TO maxskysql@'localhost' WITH GRANT OPTION; + + +FLUSH PRIVILEGES; +CREATE DATABASE IF NOT EXISTS test; diff --git a/maxscale-system-test/local_tests/multiple_servers.cnf b/maxscale-system-test/local_tests/multiple_servers.cnf new file mode 100644 index 000000000..87bf29365 --- /dev/null +++ b/maxscale-system-test/local_tests/multiple_servers.cnf @@ -0,0 +1,79 @@ + +# +# These groups are read by MariaDB server. +# Use it for options that only the server (but not clients) should see +# +# See the examples of server my.cnf files in /usr/share/mysql/ +# + +# this is read by the standalone daemon and embedded servers +[server] + +# this is only for the mysqld standalone daemon +[mysqld1] +log-bin=mar-bin +binlog-format=row +#max_long_data_size=1000000000 +#innodb_log_file_size=2000000000 +slave-skip-errors=all +server_id=1 +user = mysql +pid-file = /var/run/mysqld/mysqld1.pid +socket = /var/run/mysqld/mysqld1.sock +port = 3301 +datadir = /data/mysql/mysql1 + +[mysqld2] +log-bin=mar-bin +binlog-format=row +#max_long_data_size=1000000000 +#innodb_log_file_size=2000000000 +slave-skip-errors=all +server_id=2 +user = mysql +pid-file = /var/run/mysqld/mysqld2.pid +socket = /var/run/mysqld/mysqld2.sock +port = 3302 +datadir = /data/mysql/mysql2 + +[mysqld3] +log-bin=mar-bin +binlog-format=row +#max_long_data_size=1000000000 +#innodb_log_file_size=2000000000 +slave-skip-errors=all +server_id=3 +user = mysql +pid-file = /var/run/mysqld/mysqld3.pid +socket = /var/run/mysqld/mysqld3.sock +port = 3303 +datadir = /data/mysql/mysql3 + +[mysqld4] +log-bin=mar-bin +binlog-format=row +#max_long_data_size=1000000000 +#innodb_log_file_size=2000000000 +slave-skip-errors=all +server_id=4 +user = mysql +pid-file = /var/run/mysqld/mysqld4.pid +socket = /var/run/mysqld/mysqld4.sock +port = 3304 +datadir = /data/mysql/mysql4 + +# this is only for embedded server +[embedded] + +# This group is only read by MariaDB-5.5 servers. +# If you use the same .cnf file for MariaDB of different versions, +# use this group for options that older servers don't understand +[mysqld-5.5] + +# These two groups are only read by MariaDB servers, not by MySQL. +# If you use the same .cnf file for MySQL and MariaDB, +# you can put MariaDB-only options here +[mariadb] + +[mariadb-5.5] + diff --git a/maxscale-system-test/local_tests/set_env_local.sh b/maxscale-system-test/local_tests/set_env_local.sh new file mode 100644 index 000000000..16e4afb48 --- /dev/null +++ b/maxscale-system-test/local_tests/set_env_local.sh @@ -0,0 +1,93 @@ +set -x +echo $* +export config_name="$1" +if [ -z $1 ] ; then + config_name="local1" +fi + +export curr_dir=`pwd` + +export new_dirs="yes" + +export maxscale_binlog_dir="/var/lib/maxscale/Binlog_Service" +export maxdir="/usr/bin/" +export maxdir_bin="/usr/bin/" +export maxscale_cnf="/etc/maxscale.cnf" +export maxscale_log_dir="/var/log/maxscale/" +export maxscale_sshkey=$maxscale_keyfile + +cd $mdbci_dir + +# Number of nodes +export node_N=4 + +export maxscale_IP=127.0.0.1 +export maxscale_network=127.0.0.1 +export maxscale_keyfile=$HOME/.ssh/id_rsa + +# User name and Password for Master/Slave replication setup (should have all PRIVILEGES) +export node_user="skysql" +export node_password="skysql" + +# User name and Password for Galera setup (should have all PRIVILEGES) +#export galera_user="skysql" +#export galera_password="skysql" + +export maxscale_user="skysql" +export maxscale_password="skysql" + +export maxadmin_password="mariadb" + +#for prefix in "node" "galera" +for prefix in "node" +do + N_var="$prefix"_N + Nx=${!N_var} + N=`expr $Nx - 1` + for i in $(seq 0 $N) + do + num=`printf "%03d" $i` + username=`whoami` + eval 'export "$prefix"_"$num"_network=127.0.0.1' + eval 'export "$prefix"_"$num"_private_ip=127.0.0.1' + eval 'export "$prefix"_"$num"_hostname="$prefix""$num"' + eval 'export "$prefix"_"$num"_whoami="$username"' + eval 'export "$prefix"_"$num"_keyfile="$HOME"/.ssh/id_rsa' + j=`expr $i + 1` + eval 'export "$prefix"_"$num"_socket=/var/run/mysqld/mysqld"$j".sock' + + mariadbport=`expr $i + 3301` + eval 'export "$prefix"_"$num"_port="$mariadbport"' + eval 'export "$prefix"_"$num"_access_sudo=sudo' + + start_cmd_var="$prefix"_"$num"_start_db_command + stop_cmd_var="$prefix"_"$num"_stop_db_command + GRN=`expr $i + 1` + eval 'export $start_cmd_var="mysqld_multi --defaults-file=$HOME/maxscale-system-test/local_tests/multiple_servers.cnf start $GRN"' + eval 'export $stop_cmd_var="mysqld_multi --defaults-file=$HOME/maxscale-system-test/local_tests/multiple_servers.cnf stop $GRN"' + + start_cmd_var="$prefix"_"$num"_cleanup_db_command + GRN=`expr $i + 1` + eval 'export $start_cmd_var="$HOME/maxscale-system-test/local_tests/cleanup_db.sh $GRN"' + +# cd .. + done +done + +cd $mdbci_dir +export maxscale_access_user=`whoami` +export maxscale_whoami=`whoami` +export maxscale_access_sudo="sudo " + +# Sysbench directory (should be sysbench >= 0.5) +export sysbench_dir="$HOME/sysbench_deb7/sysbench/" + +export ssl=true + +#export use_snapshots=yes +export take_snapshot_command="echo Snapshots are not supported in the local config" +export revert_snapshot_command="echo Snapshots are not supported in the local config" + +export smoke=yes +cd $curr_dir +set +x diff --git a/maxscale-system-test/local_tests/start_multiple_mariadb.sh b/maxscale-system-test/local_tests/start_multiple_mariadb.sh new file mode 100755 index 000000000..52e59d1b6 --- /dev/null +++ b/maxscale-system-test/local_tests/start_multiple_mariadb.sh @@ -0,0 +1,34 @@ +set -x + +servers=4; +dir=`pwd` + +#cp ~/build-scripts/test/multiple_servers.cnf $dir +sudo rm -rf /data/mysql/* +sudo rm -rf /var/log/mysql/* +sudo mkdir -p /data/mysql +sudo chown mysql:mysql -R /data +sudo mkdir -p /var/run/mysqld +sudo chown mysql:mysql -R /var/run/mysqld +sudo killall mysqld +sudo killall mysql_install_db +sleep 20 + +for i in `seq 1 $servers`; +do + sudo mysql_install_db --defaults-file=$dir/multiple_servers.cnf --user=mysql --datadir=/data/mysql/mysql$i +done + +sudo mysqld_multi --defaults-file=$dir/multiple_servers.cnf start & + +running_servers=0 +while [ $running_servers != $servers ] ; do + running_servers=`mysqld_multi --defaults-file=$dir/multiple_servers.cnf report | grep "is running" | wc -l` +done + + +for i in `seq 1 $servers`; +do + sudo mysql --socket=/var/run/mysqld/mysqld$i.sock < $dir/create_repl_user.sql + sudo mysql --socket=/var/run/mysqld/mysqld$i.sock < $dir/create_skysql_user.sql +done diff --git a/maxscale-system-test/long_insert.sh b/maxscale-system-test/long_insert.sh new file mode 100755 index 000000000..17e146d78 --- /dev/null +++ b/maxscale-system-test/long_insert.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` +$test_dir/non_native_setup $test_name +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +IP=$Maxscale_IP + +mysql -h $IP -P 4006 -u $node_user -p$node_password $ssl_options < $test_dir/long_insert_sql/test_init.sql + +echo "RWSplit router:" +for ((i=0 ; i<1000 ; i++)) ; do + echo "iteration: $i" + mysql -h $IP -P 4006 -u $node_user -p$node_password $ssl_options < $test_dir/long_insert_sql/test_query.sql +done + +echo "ReadConn router (master):" +for ((i=0 ; i<1000 ; i++)) ; do + echo "iteration: $i" + mysql -h $IP -P 4008 -u $node_user -p$node_uassword $ssl_options < $test_dir/long_insert_sql/test_query.sql +done + + +res=$? + +$test_dir/copy_logs.sh long_insert +exit $res diff --git a/maxscale-system-test/long_insert_sql/test.sql b/maxscale-system-test/long_insert_sql/test.sql new file mode 100644 index 000000000..a87df8faa --- /dev/null +++ b/maxscale-system-test/long_insert_sql/test.sql @@ -0,0 +1,4 @@ +USE test; +DROP TABLE IF EXISTS test_bind_fetch; +CREATE TABLE test_bind_fetch(c1 char(10), c2 text); +INSERT INTO test_bind_fetch VALUES ('1234567890', 'A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1'); diff --git a/maxscale-system-test/long_insert_sql/test_init.sql b/maxscale-system-test/long_insert_sql/test_init.sql new file mode 100644 index 000000000..f73e0d4d3 --- /dev/null +++ b/maxscale-system-test/long_insert_sql/test_init.sql @@ -0,0 +1,4 @@ +CREATE DATABASE IF NOT EXISTS test; +USE test; +DROP TABLE IF EXISTS test_bind_fetch; +CREATE TABLE test_bind_fetch(c1 char(10), c2 text); diff --git a/maxscale-system-test/long_insert_sql/test_query.sql b/maxscale-system-test/long_insert_sql/test_query.sql new file mode 100644 index 000000000..14e072497 --- /dev/null +++ b/maxscale-system-test/long_insert_sql/test_query.sql @@ -0,0 +1,2 @@ +USE test; +INSERT INTO test_bind_fetch VALUES ('1234567890', 'A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1'); diff --git a/maxscale-system-test/long_sysbench.cpp b/maxscale-system-test/long_sysbench.cpp new file mode 100644 index 000000000..b63559db3 --- /dev/null +++ b/maxscale-system-test/long_sysbench.cpp @@ -0,0 +1,92 @@ +/** + * @file long_sysbanch.cpp Run 'sysbench' for long long execution (long load test) + * + * - start sysbanch test + * - repeat for all services + * - DROP sysbanch tables + * - check if Maxscale is alive + */ + + +#include "testconnections.h" +#include "sysbench_commands.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + char sys1[4096]; + int port[3]; + int current_port; + + port[0] = Test->rwsplit_port; + port[1] = Test->readconn_master_port; + port[2] = Test->readconn_slave_port; + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + + sprintf(&sys1[0], sysbench_prepare, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP); + + Test->tprintf("Preparing sysbench tables\n%s\n", sys1); + Test->set_timeout(10000); + Test->add_result(system(sys1), "Error executing sysbench prepare\n"); + + char *readonly; + char *ro_on = (char *) "on"; + char *ro_off = (char *) "off"; + + Test->stop_timeout(); + + current_port = port[0]; + + Test->tprintf("Trying test with port %d\n", current_port); + + if (current_port == Test->readconn_slave_port ) + { + readonly = ro_on; + } + else + { + readonly = ro_off; + } + + sprintf(&sys1[0], sysbench_command_long, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP, + current_port, readonly); + Test->set_log_copy_interval(300); + Test->tprintf("Executing sysbench \n%s\n", sys1); + if (system(sys1) != 0) + { + Test->tprintf("Error executing sysbench test\n"); + } + + Test->connect_maxscale(); + + printf("Dropping sysbanch tables!\n"); + fflush(stdout); + + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest1"); + if (!Test->smoke) + { + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest2"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest3"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest4"); + } + + //global_result += execute_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest"); + + printf("closing connections to MaxScale!\n"); + fflush(stdout); + + Test->close_maxscale_connections(); + + Test->tprintf("Checking if MaxScale is still alive!\n"); + fflush(stdout); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + fflush(stdout); + Test->tprintf("Logs copied!\n"); + fflush(stdout); + return rval; +} diff --git a/maxscale-system-test/longblob.cpp b/maxscale-system-test/longblob.cpp new file mode 100644 index 000000000..8809e4e32 --- /dev/null +++ b/maxscale-system-test/longblob.cpp @@ -0,0 +1,86 @@ +/** + * @file longblob.cpp - trying to use LONGBLOB + * - try to insert large BLOB, MEDIUMBLOB and LONGBLOB via RWSplit, ReadConn Master and directly to backend + */ + + +#include "testconnections.h" +#include "blob_test.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + + Test->repl->execute_query_all_nodes( (char *) "set global max_allowed_packet=10000000"); + + /*Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("LONGBLOB: Trying send data directly to Master\n"); + test_longblob(Test, Test->repl->nodes[0], (char *) "LONGBLOB", 1000000, 20, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections();*/ + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("LONGBLOB: Trying send data via RWSplit\n"); + test_longblob(Test, Test->conn_rwsplit, (char *) "LONGBLOB", 1000000, 20, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("LONGBLOB: Trying send data via ReadConn master\n"); + test_longblob(Test, Test->conn_master, (char *) "LONGBLOB", 1000000, 20, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + + + /*Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("BLOB: Trying send data directly to Master\n"); + test_longblob(Test, Test->repl->nodes[0], (char *) "BLOB", 1000, 8, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections();*/ + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("BLOB: Trying send data via RWSplit\n"); + test_longblob(Test, Test->conn_rwsplit, (char *) "BLOB", 1000, 8, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("BLOB: Trying send data via ReadConn master\n"); + test_longblob(Test, Test->conn_master, (char *) "BLOB", 1000, 8, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + + /*Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("MEDIUMBLOB: Trying send data directly to Master\n"); + test_longblob(Test, Test->repl->nodes[0], (char *) "MEDIUMBLOB", 1000000, 2, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections();*/ + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("MEDIUMBLOB: Trying send data via RWSplit\n"); + test_longblob(Test, Test->conn_rwsplit, (char *) "MEDIUMBLOB", 1000000, 2, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("MEDIUMBLOB: Trying send data via ReadConn master\n"); + test_longblob(Test, Test->conn_master, (char *) "MEDIUMBLOB", 1000000, 2, 1); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/lots_of_rows.cpp b/maxscale-system-test/lots_of_rows.cpp new file mode 100644 index 000000000..67de39828 --- /dev/null +++ b/maxscale-system-test/lots_of_rows.cpp @@ -0,0 +1,39 @@ +/** + * @file lots_of_row.cpp INSERT extremelly big number of rows + * - do INSERT of 100 rows in the loop 2000 times + * - do SELECT * + */ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + char sql[10240]; + + Test->connect_maxscale(); + create_t1(Test->conn_rwsplit); + + Test->tprintf("INSERTing data\n"); + for (int i = 0; i < 2000; i++) + { + Test->set_timeout(20); + create_insert_string(sql, 100, i); + Test->try_query(Test->conn_rwsplit, sql); + } + Test->tprintf("done, sleeping\n"); + Test->stop_timeout(); + sleep(20); + Test->tprintf("Trying SELECT\n"); + Test->set_timeout(30); + Test->try_query(Test->conn_rwsplit, (char *) "SELECT * FROM t1"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/macros.cmake b/maxscale-system-test/macros.cmake new file mode 100644 index 000000000..c304a2b53 --- /dev/null +++ b/maxscale-system-test/macros.cmake @@ -0,0 +1,70 @@ +macro(set_maxscale_version) + + #MaxScale-test version number + set(MAXSCALE_VERSION_MAJOR "1") + set(MAXSCALE_VERSION_MINOR "3") + set(MAXSCALE_VERSION_PATCH "0") + set(MAXSCALE_VERSION_NUMERIC "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}") + set(MAXSCALE_VERSION "${MAXSCALE_VERSION_MAJOR}.${MAXSCALE_VERSION_MINOR}.${MAXSCALE_VERSION_PATCH}-beta") + +endmacro() + +macro(check_deps) + + find_library(MYSQL_CLIENT mariadbclient mysqlclient PATH_SUFFIXES mysql mariadb) + + # Check for libraries MaxScale depends on + set(MAXSCALE_DEPS z crypt nsl m pthread ssl crypto dl rt jansson) + foreach(lib ${MAXSCALE_DEPS}) + find_library(lib${lib} ${lib}) + if((DEFINED lib${lib}) AND (${lib${lib}} MATCHES "NOTFOUND")) + set(DEPS_ERROR TRUE) + set(FAILED_DEPS "${FAILED_DEPS} lib${lib}") + elseif(DEBUG_OUTPUT) + message(STATUS "Library was found at: ${lib${lib}}") + endif() + endforeach() + + if(DEPS_ERROR) + set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") + message(FATAL_ERROR "Cannot find dependencies: ${FAILED_DEPS}") + endif() + if(DEFINED MYSQL_CLIENT MATCHES "NOTFOUND") + set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") + message(FATAL_ERROR "Cannot find dependencies: mariadbclient or mysqlclient") + endif() + +endmacro() + +macro(check_dirs) + + # This variable is used to prevent redundant checking of dependencies + set(DEPS_OK TRUE CACHE BOOL "If all the dependencies were found.") + + # Find the MySQL headers if they were not defined + + if(DEFINED MYSQL_DIR) + if(DEBUG_OUTPUT) + message(STATUS "Searching for MySQL headers at: ${MYSQL_DIR}") + endif() + find_path(MYSQL_DIR_LOC mysql.h PATHS ${MYSQL_DIR} PATH_SUFFIXES mysql mariadb NO_DEFAULT_PATH) + else() + find_path(MYSQL_DIR_LOC mysql.h PATH_SUFFIXES mysql mariadb) + endif() + + if(DEBUG_OUTPUT) + message(STATUS "Search returned: ${MYSQL_DIR_LOC}") + endif() + + if(${MYSQL_DIR_LOC} MATCHES "NOTFOUND") + set(DEPS_OK FALSE CACHE BOOL "If all the dependencies were found.") + message(FATAL_ERROR "Fatal Error: MySQL headers were not found.") + else() + set(MYSQL_DIR ${MYSQL_DIR_LOC} CACHE PATH "Path to MySQL headers" FORCE) + message(STATUS "Using MySQL headers found at: ${MYSQL_DIR}") + endif() + + unset(MYSQL_DIR_LOC) + +endmacro() + diff --git a/maxscale-system-test/manage_mrm.sh b/maxscale-system-test/manage_mrm.sh new file mode 100755 index 000000000..72f1bd3db --- /dev/null +++ b/maxscale-system-test/manage_mrm.sh @@ -0,0 +1,182 @@ +#!/bin/bash + +function do_ssh() { + ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $maxscale_access_user@$maxscale_IP +} + +function do_scp() { + scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet $1 $maxscale_access_user@$maxscale_IP:$2 +} + +function create_config() { + + if [ "$1" == "3" ] + then + nodelist=$node_000_network:3306,$node_001_network:3306,$node_002_network:3306 + elif [ "$1" == "2" ] + then + nodelist=$node_000_network:3306,$node_001_network:3306 + else + nodelist=$node_000_network:3306,$node_001_network:3306,$node_002_network:3306,$node_003_network:3306 + fi + + if [ -n "$new_replication_manager" ] + then + default_section="[Default]" + fi + + cat < config.toml +# config.toml +# Example replication-manager configuration file + +$default_section +hosts = "$nodelist" +user = "skysql:skysql" +rpluser = "skysql:skysql" +title = "Cluster01" +connect-timeout = 1 +prefmaster = "$node_000_network:3306" +interactive = false +log-level=1 +# LOG +# --- + +logfile = "/var/log/replication-manager.log" +verbose = true + +# TOPOLOGY +# -------- + + +# Automatically rejoin a failed server to the current master +# Slaves will re enter with read-only + +readonly = true +failover-event-scheduler = false +failover-event-status = false + +# FAILOVER +# -------- + +# Timeout in seconds between consecutive monitoring +# check type can be tcp or agent +monitoring-ticker = 1 +check-type = "tcp" +check-replication-filters = true +check-binlog-filters = true +check-replication-state = true + +# Failover after N failures detection +# Reset number of failure if server auto recover after N seconds +failcount = 1 +failcount-reset-time = 300 + +# Cancel failover if already N failover +# Cancel failover if last failover was N seconds before +# Cancel failover in semi-sync when one slave is not in sync +# Cancel failover when replication delay is more than N seconds +failover-limit = 100 +failover-time-limit = 1 +failover-at-sync = false +switchover-at-sync = false +maxdelay = 30 + +# SWITCHOVER +# ---------- + +# In switchover Wait N milliseconds before killing long running transactions +# Cancel switchover if transaction running more than N seconds +# Cancel switchover if write query running more than N seconds +# Cancel switchover if one of the slaves is not synced based on GTID equality +wait-kill = 5000 +wait-trx = 10 +wait-write-query = 10 +gtidcheck = true + +EOF + +} + +function install_mrm() { + + # new_replication_manager means that we're using a custom build and it's already installed on the system + if [ -z "$new_replication_manager" ] + then + do_ssh < /dev/null || sudo yum -y install wget +wget -q https://github.com/tanji/replication-manager/releases/download/1.0.2/replication-manager-1.0.2_1_g8faf64d-8faf64d.x86_64.rpm +sudo yum -y install ./replication-manager-1.0.2_1_g8faf64d-8faf64d.x86_64.rpm +sudo systemctl daemon-reload +rm ./replication-manager-1.0.2_1_g8faf64d-8faf64d.x86_64.rpm +EOF + fi + + create_config $1 + do_scp './config.toml' '~/config.toml' + + do_ssh < /dev/null || sudo yum -y install wget +test -f go1.8.linux-amd64.tar.gz || wget -q https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz +sudo su - +cd /home/vagrant/ +sudo tar -axf go1.8.linux-amd64.tar.gz -C /usr +sudo echo 'export GOROOT=/usr/go/' > /etc/profile.d/go.sh +sudo echo 'export GOPATH=/usr/' >> /etc/profile.d/go.sh +sudo echo 'export PATH=/$PATH:/usr/go/bin/' >> /etc/profile.d/go.sh +source /etc/profile +go get github.com/tanji/replication-manager +go install github.com/tanji/replication-manager +cp /usr/src/github.com/tanji/replication-manager/service/replication-manager.service /etc/systemd/system/ +exit +EOF +} + +function remove_mrm() { + do_ssh < +#include + +int set_ssl(MYSQL * conn) +{ + char client_key[1024]; + char client_cert[1024]; + char ca[1024]; + char * test_dir; + test_dir = getenv("test_dir"); + if (test_dir == NULL) + { + sprintf(client_key, "./ssl-cert/client-key.pem"); + sprintf(client_cert, "./ssl-cert/client-cert.pem"); + sprintf(ca, "./ssl-cert/ca.pem"); + } + else + { + sprintf(client_key, "%s/ssl-cert/client-key.pem", test_dir); + sprintf(client_cert, "%s/ssl-cert/client-cert.pem", test_dir); + sprintf(ca, "%s/ssl-cert/ca.pem", test_dir); + } + return mysql_ssl_set(conn, client_key, client_cert, ca, NULL, NULL); +} + +MYSQL * open_conn_db_flags(int port, const char* ip, const char* db, const char* User, const char* Password, + unsigned long flag, bool ssl) +{ + MYSQL * conn = mysql_init(NULL); + + if (conn == NULL) + { + fprintf(stdout, "Error: can't create MySQL-descriptor\n"); + return NULL; + } + + if (ssl) + { + set_ssl(conn); + } + + if (!mysql_real_connect(conn, + ip, + User, + Password, + db, + port, + NULL, + flag + )) + { + //printf("Error: can't connect to database, error is %s:\n", mysql_error(conn)); + return conn; + } + + return conn; +} + +MYSQL * open_conn_db_timeout(int port, const char* ip, const char* db, const char* User, const char* Password, + unsigned long timeout, bool ssl) +{ + MYSQL * conn = mysql_init(NULL); + + if (conn == NULL) + { + fprintf(stdout, "Error: can't create MySQL-descriptor\n"); + return NULL; + } + + unsigned int conn_timeout = timeout; + unsigned int read_timeout = timeout; + unsigned int write_timeout = timeout; + mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &conn_timeout); + mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &read_timeout); + mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &write_timeout); + + if (ssl) + { + if (ssl) + { + set_ssl(conn); + } + } + + if (!mysql_real_connect(conn, + ip, + User, + Password, + db, + port, + NULL, + CLIENT_MULTI_STATEMENTS + )) + { + //printf("Error: can't connect to database, error is %s:\n", mysql_error(conn)); + return conn; + } + + return conn; +} +MYSQL* open_conn_db_timeout(int port, const std::string& ip, const std::string& db, + const std::string& user, const std::string& password, + unsigned long timeout, bool ssl) +{ + return open_conn_db_timeout(port, ip.c_str(), db.c_str(), user.c_str(), password.c_str(), timeout, ssl); +} + +MYSQL * open_conn_db(int port, const char* ip, const char* db, const char* User, const char* Password, + bool ssl) +{ + return open_conn_db_flags(port, ip, db, User, Password, CLIENT_MULTI_STATEMENTS, ssl); +} + +MYSQL * open_conn(int port, const char* ip, const char* User, const char* Password, bool ssl) +{ + return open_conn_db(port, ip, "test", User, Password, ssl); +} + +MYSQL * open_conn_no_db(int port, const char* ip, const char*User, const char*Password, bool ssl) +{ + return open_conn_db_flags(port, ip, NULL, User, Password, CLIENT_MULTI_STATEMENTS, ssl); +} + +int execute_query(MYSQL *conn, const char *format, ...) +{ + va_list valist; + + va_start(valist, format); + int message_len = vsnprintf(NULL, 0, format, valist); + va_end(valist); + + char sql[message_len + 1]; + + va_start(valist, format); + vsnprintf(sql, sizeof(sql), format, valist); + va_end(valist); + + return execute_query1(conn, sql, false); +} + +int execute_query_from_file(MYSQL *conn, FILE *file) +{ + int rc = -1; + char buf[4096]; + + if (fgets(buf, sizeof(buf), file)) + { + char *nul = strchr(buf, '\0') - 1; + + while (isspace(*nul)) + { + *nul-- = '\0'; + } + + char *ptr = buf; + + while (isspace(*ptr)) + { + ptr++; + } + + if (*ptr) + { + rc = execute_query1(conn, buf, false); + } + + } + else if (!feof(file)) + { + printf("Failed to read file: %d, %s", errno, strerror(errno)); + rc = 1; + } + + return rc; +} + +int execute_query_silent(MYSQL *conn, const char *sql) +{ + return execute_query1(conn, sql, true); +} + +int execute_query1(MYSQL *conn, const char *sql, bool silent) +{ + MYSQL_RES *res; + if (conn != NULL) + { + if (mysql_query(conn, sql) != 0) + { + if (!silent) + { + int len = strlen(sql); + printf("Error: can't execute SQL-query: %.*s\n", len < 60 ? len : 60, sql); + printf("%s\n\n", mysql_error(conn)); + } + return 1; + } + else + { + do + { + res = mysql_store_result(conn); + mysql_free_result(res); + } + while ( mysql_next_result(conn) == 0 ); + return 0; + } + } + else + { + if (!silent) + { + printf("Connection is broken\n"); + } + return 1; + } +} + +int execute_query_check_one(MYSQL *conn, const char *sql, const char *expected) +{ + int r = 1; + + if (conn != NULL) + { + const int n_attempts = 3; + + for (int i = 0; i < n_attempts && r != 0; i++) + { + if (i > 0) + { + sleep(1); + } + + if (mysql_query(conn, sql) != 0) + { + printf("Error: can't execute SQL-query: %s\n", sql); + printf("%s\n\n", mysql_error(conn)); + break; + } + else + { + do + { + MYSQL_RES *res = mysql_store_result(conn); + + if (res) + { + if (mysql_num_rows(res) == 1) + { + MYSQL_ROW row = mysql_fetch_row(res); + + if (row[0] != NULL) + { + if (strcmp(row[0], expected) == 0) + { + r = 0; + printf("First field is '%s' as expected\n", row[0]); + } + else + { + printf("First field is '%s, but expected %s'\n", row[0], expected); + } + } + else + { + printf("First field is NULL\n"); + } + } + else + { + printf("Number of rows is not 1, it is %llu\n", mysql_num_rows(res)); + } + + mysql_free_result(res); + } + } + while (mysql_next_result(conn) == 0); + } + } + } + else + { + printf("Connection is broken\n"); + } + + return r; +} + +int execute_query_affected_rows(MYSQL *conn, const char *sql, my_ulonglong * affected_rows) +{ + MYSQL_RES *res; + if (conn != NULL) + { + if (mysql_query(conn, sql) != 0) + { + printf("Error: can't execute SQL-query: %s\n", sql); + printf("%s\n\n", mysql_error(conn)); + return 1; + } + else + { + do + { + *affected_rows = mysql_affected_rows(conn); + res = mysql_store_result(conn); + mysql_free_result(res); + } + while ( mysql_next_result(conn) == 0 ); + return 0; + } + } + else + { + printf("Connection is broken\n"); + return 1; + } +} + +int execute_query_num_of_rows(MYSQL *conn, const char *sql, my_ulonglong num_of_rows[], + unsigned long long * i) +{ + MYSQL_RES *res; + my_ulonglong N; + + + printf("%s\n", sql); + if (conn != NULL) + { + if (mysql_query(conn, sql) != 0) + { + printf("Error: can't execute SQL-query: %s\n", sql); + printf("%s\n\n", mysql_error(conn)); + * i = 0; + return 1; + } + else + { + *i = 0; + do + { + res = mysql_store_result(conn); + if (res != NULL) + { + N = mysql_num_rows(res); + mysql_free_result(res); + } + else + { + N = 0; + } + num_of_rows[*i] = N; + *i = *i + 1; + } + while ( mysql_next_result(conn) == 0 ); + return 0; + } + } + else + { + printf("Connection is broken\n"); + * i = 0; + return 1; + } +} + +int execute_stmt_num_of_rows(MYSQL_STMT * stmt, my_ulonglong num_of_rows[], unsigned long long * i) +{ + my_ulonglong N; + + /* This is debug hack; compatible only with t1 from t1_sql.h + my_ulonglong k; + MYSQL_BIND bind[2]; + my_ulonglong x1; + my_ulonglong fl; + + unsigned long length[2]; + my_bool is_null[2]; + my_bool error[2]; + + memset(bind, 0, sizeof(bind)); + bind[0].buffer = &x1; + bind[0].buffer_type = MYSQL_TYPE_LONG; + bind[0].length = &length[0]; + bind[0].is_null = &is_null[0]; + bind[0].error = &error[0]; + + bind[1].buffer = &fl; + bind[1].buffer_type = MYSQL_TYPE_LONG; + bind[1].length = &length[0]; + bind[1].is_null = &is_null[0]; + bind[1].error = &error[0]; + */ + + if (mysql_stmt_execute(stmt) != 0) + { + printf("Error: can't execute prepared statement\n"); + printf("%s\n\n", mysql_stmt_error(stmt)); + * i = 0; + return 1; + } + else + { + *i = 0; + do + { + mysql_stmt_store_result(stmt); + N = mysql_stmt_num_rows(stmt); + /* This is debug hack; compatible only with t1 from t1_sql.h + mysql_stmt_bind_result(stmt, bind); + for (k = 0; k < N; k++) + { + mysql_stmt_fetch(stmt); + printf("%04llu: x1 %llu, fl %llu\n", k, x1, fl); + } + */ + num_of_rows[*i] = N; + *i = *i + 1; + + } + while ( mysql_stmt_next_result(stmt) == 0 ); + return 0; + } + return 1; +} + +int execute_query_count_rows(MYSQL *conn, const char *sql) +{ + int rval = -1; + + unsigned long long num_of_rows[1024]; + unsigned long long total; + + if (execute_query_num_of_rows(conn, sql, num_of_rows, &total) == 0) + { + rval = 0; + + for (int i = 0; i < total && i < 1024; i++) + { + rval += num_of_rows[i]; + } + } + + return rval; +} + +int get_conn_num(MYSQL *conn, char * ip, char *hostname, char * db) +{ + MYSQL_RES *res; + MYSQL_ROW row; + unsigned long long int num_fields; + //unsigned long long int row_i=0; + unsigned long long int rows; + unsigned long long int i; + unsigned int conn_num = 0; + char * hostname_internal; + if (strcmp(ip, "127.0.0.1") == 0) + { + hostname_internal = (char*) "localhost"; + } + else + { + hostname_internal = hostname; + } + + if (conn != NULL) + { + if (mysql_query(conn, "show processlist;") != 0) + { + printf("Error: can't execute SQL-query: show processlist\n"); + printf("%s\n\n", mysql_error(conn)); + conn_num = 0; + } + else + { + res = mysql_store_result(conn); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + conn_num = -1; + } + else + { + num_fields = mysql_num_fields(res); + rows = mysql_num_rows(res); + for (i = 0; i < rows; i++) + { + row = mysql_fetch_row(res); + if ( (row[2] != NULL ) && (row[3] != NULL) ) + { + if ((strstr(row[2], ip) != NULL) && (strstr(row[3], db) != NULL)) + { + conn_num++; + } + if ((strstr(row[2], hostname_internal) != NULL) && (strstr(row[3], db) != NULL)) + { + conn_num++; + } + } + } + } + mysql_free_result(res); + } + } + if (strcmp(ip, "127.0.0.1") == 0) + { + // one extra connection i svisible in the processlist + // output in case of local test + // (when maxscale is on the same machine as backends) + conn_num--; + } + return conn_num; +} + +int find_field(MYSQL *conn, const char *sql, const char *field_name, char * value) +{ + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL_FIELD *field; + unsigned long long int num_fields; + unsigned int ret = 1; + unsigned long long int filed_i = 0; + unsigned long long int i = 0; + + if (conn != NULL ) + { + if (mysql_query(conn, sql) != 0) + { + printf("Error: can't execute SQL-query: %s\n", sql); + printf("%s\n\n", mysql_error(conn)); + } + else + { + res = mysql_store_result(conn); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + } + else + { + num_fields = mysql_num_fields(res); + + while ((field = mysql_fetch_field(res)) && ret != 0) + { + if (strstr(field->name, field_name) != NULL) + { + filed_i = i; + ret = 0; + } + i++; + } + if (mysql_num_rows(res) > 0) + { + row = mysql_fetch_row(res); + sprintf(value, "%s", row[filed_i]); + } + } + mysql_free_result(res); + do + { + res = mysql_store_result(conn); + mysql_free_result(res); + } + while ( mysql_next_result(conn) == 0 ); + } + } + return ret; +} + +unsigned int get_seconds_behind_master(MYSQL *conn) +{ + char SBM_str[16]; + unsigned int SBM = 0; + if (find_field( + conn, (char *) "show slave status;", + (char *) "Seconds_Behind_Master", &SBM_str[0] + ) != 1) + { + sscanf(SBM_str, "%u", &SBM); + } + return SBM; +} + +int read_log(char * name, char ** err_log_content_p) +{ + FILE *f; + *err_log_content_p = NULL; + char * err_log_content; + f = fopen(name, "rb"); + if (f != NULL) + { + + int prev = ftell(f); + fseek(f, 0L, SEEK_END); + long int size = ftell(f); + fseek(f, prev, SEEK_SET); + err_log_content = (char *)malloc(size + 2); + if (err_log_content != NULL) + { + fread(err_log_content, 1, size, f); + for (int i = 0; i < size; i++) + { + if (err_log_content[i] == 0) + { + //printf("null detected at position %d\n", i); + err_log_content[i] = '\n'; + } + } + //printf("s=%ld\n", strlen(err_log_content)); + err_log_content[size] = '\0'; + //printf("s=%ld\n", strlen(err_log_content)); + * err_log_content_p = err_log_content; + return 0; + } + else + { + printf("Error allocationg memory for the log\n"); + return 1; + } + } + else + { + printf ("Error reading log %s \n", name); + return 1; + } + +} + +int get_int_version(const std::string& version) +{ + std::istringstream str(version); + int major = 0; + int minor = 0; + int patch = 0; + char dot; + + str >> major >> dot >> minor >> dot >> patch; + return major * 10000 + minor * 100 + patch; +} + +int get_int_version(const char* version) +{ + return get_int_version(std::string(version)); +} diff --git a/maxscale-system-test/mariadb_func.h b/maxscale-system-test/mariadb_func.h new file mode 100644 index 000000000..3090dda9d --- /dev/null +++ b/maxscale-system-test/mariadb_func.h @@ -0,0 +1,235 @@ +#ifndef MARIADB_FUNC_H +#define MARIADB_FUNC_H + + +/** + * @file mariadb_func.h - basic DB interaction routines + * + * @verbatim + * Revision History + * + * Date Who Description + * 17/11/14 Timofey Turenko Initial implementation + * + * @endverbatim + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Opens connection to DB: wropper over mysql_real_connect + * + * @param port DB server port + * @param ip DB server IP address + * @param db name of DB to connect + * @param User User name + * @param Password Password + * @param flag Connections flags + * @param ssl true if ssl should be used + * @return MYSQL struct or NULL in case of error + */ +MYSQL * open_conn_db_flags(int port, const char* ip, const char* db, const char* User, const char* Password, + unsigned long flag, bool ssl); + + +/** + * Opens connection to DB: wropper over mysql_real_connect + * + * @param port DB server port + * @param ip DB server IP address + * @param db name of DB to connect + * @param User User name + * @param Password Password + * @param timeout timeout on seconds + * @param ssl true if ssl should be used + * @return MYSQL struct or NULL in case of error + */ +MYSQL * open_conn_db_timeout(int port, const char* ip, const char* db, const char* User, const char* Password, + unsigned long timeout, bool ssl); + +MYSQL* open_conn_db_timeout(int port, const std::string& ip, const std::string& db, + const std::string& user, const std::string& password, + unsigned long timeout, bool ssl); + +/** + * Opens connection to DB with default flags + * + * @param port DB server port + * @param ip DB server IP address + * @param db name of DB to connect + * @param User User name + * @param Password Password + * @param ssl true if ssl should be used + * @return MYSQL struct or NULL in case of error + */ +MYSQL * open_conn_db(int port, const char* ip, const char* db, const char* User, const char* Password, + bool ssl); + + +/** + * Opens connection to 'test' with default flags + * + * @param port DB server port + * @param ip DB server IP address + * @param User User name + * @param Password Password + * @param ssl true if ssl should be used + * @return MYSQL struct or NULL in case of error + */ +MYSQL * open_conn(int port, const char* ip, const char* User, const char* Password, bool ssl); + +/** + * Opens connection to with default flags without defning DB name (just conecto server) + * + * @param port DB server port + * @param ip DB server IP address + * @param User User name + * @param Password Password + * @param ssl true if ssl should be used + * @return MYSQL struct or NULL in case of error + */ +MYSQL * open_conn_no_db(int port, const char* ip, const char* User, const char* Password, bool ssl); + +/** + * @brief set_ssl Configure SSL for given connection + * Function assumes that certificates are in test_dir/ssl-cert/ directory + * @param conn MYSQL handler + * @return return of mysql_ssl_set() (always 0, see mysql_ssl_set() documentation) + */ +int set_ssl(MYSQL * conn); + +/** + * @brief Executes SQL query. Function also executes mysql_store_result() and mysql_free_result() to clean up returns + * @param conn MYSQL connection + * @param format SQL string with printf style formatting + * @param ... Parameters for @c format + * @return 0 in case of success + */ +int execute_query(MYSQL *conn, const char *format, ...); + +/** + * @brief execute_query_from_file Read a line from a file, trim leading and trailing whitespace and execute it. + * @param conn MYSQL handler + * @param file file handler + * @return 0 in case of success + */ +int execute_query_from_file(MYSQL *conn, FILE *file); + +/** + * @brief Executes SQL query. Function also executes mysql_store_result() and mysql_free_result() to clean up returns + * @param conn MYSQL connection struct + * @param sql SQL string + * @return 0 in case of success + */ +int execute_query_silent(MYSQL *conn, const char *sql); + +/** + * @brief Executes SQL query. Function also executes mysql_store_result() and mysql_free_result() to clean up returns + * This function do not support 'printf' format for sql (in compare with execute_query() + * @param conn MYSQL connection struct + * @param sql SQL string + * @param silent if true function do not produce any printing + * @return 0 in case of success + */ +int execute_query1(MYSQL *conn, const char *sql, bool silent); + +/** + * @brief Executes SQL query and store 'affected rows' number in affectet_rows parameter + * @param conn MYSQL connection struct + * @param sql SQL string + * @param affected_rows pointer to variabe to store number of affected rows + * @return 0 in case of success + */ +int execute_query_affected_rows(MYSQL *conn, const char *sql, my_ulonglong * affected_rows); + +/** +* @brief A more convenient form of execute_query_affected_rows() +* +* @param conn Connection to use for the query +* @param sql The SQL statement to execute +* @return Number of rows or -1 on error +*/ +int execute_query_count_rows(MYSQL *conn, const char *sql); + +/** + * @brief Executes SQL query and get number of rows in the result + * This function does not check boudaries of 'num_of_rows' array. This + * array have to be big enough to store all results + * @param conn MYSQL connection struct + * @param sql SQL string + * @param num_of_rows pointer to array to store number of result rows + * @param i pointer to variable to store number of result sets + * @return 0 in case of success + */ +int execute_query_num_of_rows(MYSQL *conn, const char *sql, my_ulonglong num_of_rows[], + unsigned long long *i); + +/** + * @brief Executes perared statement and get number of rows in the result + * This function does not check boudaries of 'num_of_rows' array. This + * array have to be big enough to store all results + * @param stmt MYSQL_STMT statetement struct (from mysql_stmt_init()) + * @param num_of_rows pointer to array to store number of result rows + * @param i pointer to variable to store number of result sets + * @return 0 in case of success + */ +int execute_stmt_num_of_rows(MYSQL_STMT *stmt, my_ulonglong num_of_rows[], unsigned long long * i); + +/** + * @brief execute_query_check_one Executes query and check if first field of first row is equal to 'expected' + * @param conn MYSQL handler + * @param sql query SQL query to execute + * @param expected Expected result + * @return 0 in case of success + */ +int execute_query_check_one(MYSQL *conn, const char *sql, const char *expected); + +/** + * @brief Executes 'show processlist' and calculates number of connections from defined host to defined DB + * @param conn MYSQL connection struct + * @param ip connections from this IP address are counted + * @param db name of DB to which connections are counted + * @return number of connections + */ +int get_conn_num(MYSQL *conn, char * ip, char * hostname, char * db); + +/** + * @brief Find given filed in the SQL query reply + * Function checks only firs row from the table + * @param conn MYSQL connection struct + * @param sql SQL query to execute + * @param filed_name name of field to find + * @param value pointer to variable to store value of found field + * @return 0 in case of success + */ +int find_field(MYSQL *conn, const char * sql, const char * field_name, char * value); + +/** + * @brief Return the value of SECONDS_BEHIND_MASTER + * @param conn MYSQL connection struct + * @return value of SECONDS_BEHIND_MASTER + */ +unsigned int get_seconds_behind_master(MYSQL *conn); + + +/** + * @brief Read MaxScale log file + * @param name Name of log file (full path) + * @param err_log_content pointer to the buffer to store log file content + * @return 0 in case of success, 1 in case of error + */ +int read_log(char * name, char **err_log_content_p); + +int get_int_version(const std::string& version); +int get_int_version(const char* version); + +#endif // MARIADB_FUNC_H diff --git a/maxscale-system-test/mariadb_nodes.cpp b/maxscale-system-test/mariadb_nodes.cpp new file mode 100644 index 000000000..43fe57237 --- /dev/null +++ b/maxscale-system-test/mariadb_nodes.cpp @@ -0,0 +1,1369 @@ +/** + * @file mariadb_nodes.cpp - backend nodes routines + * + * @verbatim + * Revision History + * + * Date Who Description + * 17/11/14 Timofey Turenko Initial implementation + * + * @endverbatim + */ + +#include "mariadb_nodes.h" +#include "sql_const.h" +#include +#include +#include + +Mariadb_nodes::Mariadb_nodes(const char *pref, const char *test_cwd, bool verbose): + v51(false), use_ipv6(false) +{ + strcpy(prefix, pref); + memset(this->nodes, 0, sizeof(this->nodes)); + memset(blocked, 0, sizeof(blocked)); + no_set_pos = false; + this->verbose = verbose; + strcpy(test_dir, test_cwd); + read_env(); + truncate_mariadb_logs(); + flush_hosts(); + close_active_connections(); +} + +Mariadb_nodes::~Mariadb_nodes() +{ + for (int i = 0; i < N; i++) + { + if (blocked[i]) + { + unblock_node(i); + } + } +} + +int Mariadb_nodes::connect() +{ + int res = 0; + + for (int i = 0; i < N; i++) + { + if (nodes[i] == NULL || mysql_ping(nodes[i]) != 0) + { + if (nodes[i]) + { + mysql_close(nodes[i]); + } + nodes[i] = open_conn_db_timeout(port[i], IP[i], "test", user_name, password, 50, ssl); + } + + if ((nodes[i] != NULL) && (mysql_errno(nodes[i]) != 0)) + { + res++; + } + } + + return res; +} + +void Mariadb_nodes::close_connections() +{ + for (int i = 0; i < N; i++) + { + if (nodes[i] != NULL) + { + mysql_close(nodes[i]); + nodes[i] = NULL; + } + } +} + +int Mariadb_nodes::read_env() +{ + char * env; + char env_name[64]; + + sprintf(env_name, "%s_N", prefix); + env = getenv(env_name); + if (env != NULL) + { + sscanf(env, "%d", &N); + } + else + { + N = 0; + } + + sprintf(env_name, "%s_user", prefix); + env = getenv(env_name); + if (env != NULL) + { + sscanf(env, "%s", user_name); + } + else + { + sprintf(user_name, "skysql"); + } + sprintf(env_name, "%s_password", prefix); + env = getenv(env_name); + if (env != NULL) + { + sscanf(env, "%s", password); + } + else + { + sprintf(password, "skysql"); + } + + ssl = false; + sprintf(env_name, "%s_ssl", prefix); + env = getenv(env_name); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + ssl = true; + } + + + if ((N > 0) && (N < 255)) + { + for (int i = 0; i < N; i++) + { + //reading IPs + sprintf(env_name, "%s_%03d_network", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(IP[i], "%s", env); + } + + //reading private IPs + sprintf(env_name, "%s_%03d_private_ip", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(IP_private[i], "%s", env); + } + else + { + sprintf(IP_private[i], "%s", IP[i]); + } + + //reading IPv6 + sprintf(env_name, "%s_%03d_network6", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(IP6[i], "%s", env); + } + else + { + sprintf(IP6[i], "%s", IP[i]); + } + + //reading ports + sprintf(env_name, "%s_%03d_port", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sscanf(env, "%d", &port[i]); + } + else + { + port[i] = 3306; + } + //reading sockets + sprintf(env_name, "%s_%03d_socket", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(socket[i], "%s", env); + sprintf(socket_cmd[i], "--socket=%s", env); + } + else + { + sprintf(socket[i], " "); + sprintf(socket_cmd[i], " "); + } + //reading sshkey + sprintf(env_name, "%s_%03d_keyfile", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(sshkey[i], "%s", env); + } + else + { + sprintf(sshkey[i], "vagrant.pem"); + } + + //reading start_db_command + sprintf(env_name, "%s_%03d_start_db_command", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(start_db_command[i], "%s", env); + } + else + { + sprintf(start_db_command[i], "%s", "service mysql start"); + } + + //reading stop_db_command + sprintf(env_name, "%s_%03d_stop_db_command", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(stop_db_command[i], "%s", env); + } + else + { + sprintf(start_db_command[i], "%s", "service mysql stop"); + } + + //reading cleanup_db_command + sprintf(env_name, "%s_%03d_cleanup_db_command", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(cleanup_db_command[i], "%s", env); + } + else + { + sprintf(cleanup_db_command[i], " "); + } + + sprintf(env_name, "%s_%03d_whoami", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(access_user[i], "%s", env); + } + else + { + sprintf(access_user[i], "root"); + } + + sprintf(env_name, "%s_%03d_access_sudo", prefix, i); + env = getenv(env_name); + if (env != NULL) + { + sprintf(access_sudo[i], "%s", env); + } + else + { + sprintf(access_sudo[i], " "); + } + + if (strcmp(access_user[i], "root") == 0) + { + sprintf(access_homedir[i], "/%s/", access_user[i]); + } + else + { + sprintf(access_homedir[i], "/home/%s/", access_user[i]); + } + + } + } +} + +int Mariadb_nodes::print_env() +{ + for (int i = 0; i < N; i++) + { + printf("%s node %d \t%s\tPort=%d\n", prefix, i, IP[i], port[i]); + printf("%s Access user %s\n", prefix, access_user[i]); + } + printf("%s User name %s\n", prefix, user_name); + printf("%s Password %s\n", prefix, password); + +} + +int Mariadb_nodes::find_master() +{ + char str[255]; + char master_IP[256]; + int i = 0; + int found = 0; + int master_node = 255; + while ((found == 0) && (i < N)) + { + if (find_field( + nodes[i], (char *) "show slave status;", + (char *) "Master_Host", &str[0] + ) == 0 ) + { + found = 1; + strcpy(master_IP, str); + } + i++; + } + if (found == 1) + { + found = 0; + i = 0; + while ((found == 0) && (i < N)) + { + if (strcmp(IP[i], master_IP) == 0) + { + found = 1; + master_node = i; + } + i++; + } + } + return master_node; +} + +int Mariadb_nodes::change_master(int NewMaster, int OldMaster) +{ + int i; + //int OldMaster = FindMaster(); + char log_file[256]; + char log_pos[256]; + char str[1024]; + + for (i = 0; i < N; i++) + { + if (i != OldMaster) + { + execute_query(nodes[i], (char *) "stop slave;"); + } + } + execute_query(nodes[NewMaster], create_repl_user); + execute_query(nodes[OldMaster], (char *) "reset master;"); + find_field(nodes[NewMaster], (char *) "show master status", (char *) "File", &log_file[0]); + find_field(nodes[NewMaster], (char *) "show master status", (char *) "Position", &log_pos[0]); + for (i = 0; i < N; i++) + { + if (i != NewMaster) + { + sprintf(str, setup_slave, IP[NewMaster], log_file, log_pos, port[NewMaster]); + execute_query(nodes[i], str); + } + } + //for (i = 0; i < N; i++) {if (i != NewMaster) {execute_query(nodes[i], (char *) "start slave;"); }} +} + +int Mariadb_nodes::stop_node(int node) +{ + return ssh_node(node, stop_db_command[node], true); +} + +int Mariadb_nodes::start_node(int node, char * param) +{ + char cmd[1024]; + if (v51) + { + sprintf(cmd, "%s %s --report-host", start_db_command[node], param); + } + else + { + sprintf(cmd, "%s %s", start_db_command[node], param); + } + return ssh_node(node, cmd, true); +} + +int Mariadb_nodes::stop_nodes() +{ + int i; + int local_result = 0; + connect(); + for (i = 0; i < N; i++) + { + printf("Stopping node %d\n", i); + fflush(stdout); + local_result += execute_query(nodes[i], (char *) "stop slave;"); + fflush(stdout); + local_result += stop_node(i); + fflush(stdout); + } + return local_result; +} + +int Mariadb_nodes::stop_slaves() +{ + int i; + int global_result = 0; + connect(); + for (i = 0; i < N; i++) + { + printf("Stopping slave %d\n", i); + fflush(stdout); + global_result += execute_query(nodes[i], (char *) "stop slave;"); + } + close_connections(); + return global_result; +} + +int Mariadb_nodes::cleanup_db_node(int node) +{ + return ssh_node(node, cleanup_db_command[node], true); +} + +int Mariadb_nodes::cleanup_db_nodes() +{ + int i; + int local_result = 0; + + for (i = 0; i < N; i++) + { + printf("Cleaning node %d\n", i); + fflush(stdout); + local_result += cleanup_db_node(i); + fflush(stdout); + } + return local_result; +} + + + +int Mariadb_nodes::start_replication() +{ + char str[1024]; + char dtr[1024]; + int local_result = 0; + + // Start all nodes + for (int i = 0; i < N; i++) + { + local_result += start_node(i, (char *) ""); + sprintf(str, + "mysql -u root %s -e \"STOP SLAVE; RESET SLAVE; RESET SLAVE ALL; RESET MASTER; SET GLOBAL read_only=OFF;\"", + socket_cmd[i]); + ssh_node(i, str, true); + } + + sprintf(str, "%s/create_user.sh", test_dir); + sprintf(dtr, "%s", access_homedir[0]); + copy_to_node(str, dtr , 0); + sprintf(str, "export node_user=\"%s\"; export node_password=\"%s\"; %s/create_user.sh %s", + user_name, password, access_homedir[0], socket_cmd[0]); + printf("cmd: %s\n", str); + ssh_node(0, str, false); + + // Create a database dump from the master and distribute it to the slaves + sprintf(str, + "mysqldump --all-databases --add-drop-database --flush-privileges --master-data=1 --gtid %s > /tmp/master_backup.sql", + socket_cmd[0]); + ssh_node(0, str, true); + sprintf(str, "%s/master_backup.sql", test_dir); + copy_from_node("/tmp/master_backup.sql", str, 0); + + for (int i = 1; i < N; i++) + { + // Reset all nodes by first loading the dump and then starting the replication + printf("Starting node %d\n", i); + fflush(stdout); + copy_to_node(str, "/tmp/master_backup.sql", i); + sprintf(dtr, + "mysql -u root %s < /tmp/master_backup.sql", + socket_cmd[i]); + ssh_node(i, dtr, true); + char query[512]; + + sprintf(query, "mysql -u root %s -e \"CHANGE MASTER TO MASTER_HOST=\\\"%s\\\", MASTER_PORT=%d, " + "MASTER_USER=\\\"repl\\\", MASTER_PASSWORD=\\\"repl\\\";" + "START SLAVE;\"", socket_cmd[i], IP_private[0], port[0]); + ssh_node(i, query, true); + } + + return local_result; +} + +int Galera_nodes::start_galera() +{ + char sys1[4096]; + char str[1024]; + int i; + int local_result = 0; + local_result += stop_nodes(); + + // Remove the grastate.dat file + ssh_node(0, "rm -f /var/lib/mysql/grastate.dat", true); + + printf("Starting new Galera cluster\n"); + fflush(stdout); + ssh_node(0, "echo [mysqld] > cluster_address.cnf", false); + ssh_node(0, "echo wsrep_cluster_address=gcomm:// >> cluster_address.cnf", false); + ssh_node(0, "cp cluster_address.cnf /etc/my.cnf.d/", true); + local_result += start_node(0, (char *) " --wsrep-cluster-address=gcomm://"); + + sprintf(str, "%s/create_user_galera.sh", test_dir); + copy_to_node(str, "~/", 0); + + sprintf(str, "export galera_user=\"%s\"; export galera_password=\"%s\"; ./create_user_galera.sh %s", user_name, + password, socket_cmd[0]); + ssh_node(0, str, false); + + for (i = 1; i < N; i++) + { + printf("Starting node %d\n", i); + fflush(stdout); + ssh_node(i, "echo [mysqld] > cluster_address.cnf", true); + sprintf(str, "echo wsrep_cluster_address=gcomm://%s >> cluster_address.cnf", IP_private[0]); + ssh_node(i, str, true); + ssh_node(i, "cp cluster_address.cnf /etc/my.cnf.d/", true); + + sprintf(&sys1[0], " --wsrep-cluster-address=gcomm://%s", IP_private[0]); + if (this->verbose) + { + printf("%s\n", sys1); + fflush(stdout); + } + local_result += start_node(i, sys1); + fflush(stdout); + } + sleep(5); + + local_result += connect(); + local_result += execute_query(nodes[0], create_repl_user); + + close_connections(); + return local_result; +} + +int Mariadb_nodes::clean_iptables(int node) +{ + char sys1[1024]; + int local_result = 0; + + local_result += ssh_node(node, (char *) "echo \"#!/bin/bash\" > clean_iptables.sh", false); + sprintf(sys1, + "echo \"while [ \\\"\\$(iptables -n -L INPUT 1|grep '%d')\\\" != \\\"\\\" ]; do iptables -D INPUT 1; done\" >> clean_iptables.sh", + port[node]); + local_result += ssh_node(node, (char *) sys1, false); + sprintf(sys1, + "echo \"while [ \\\"\\$(ip6tables -n -L INPUT 1|grep '%d')\\\" != \\\"\\\" ]; do ip6tables -D INPUT 1; done\" >> clean_iptables.sh", + port[node]); + local_result += ssh_node(node, (char *) sys1, false); + + local_result += ssh_node(node, (char *) "chmod a+x clean_iptables.sh", false); + local_result += ssh_node(node, (char *) "./clean_iptables.sh", true); + return local_result; +} + +int Mariadb_nodes::block_node(int node) +{ + char sys1[1024]; + int local_result = 0; + local_result += clean_iptables(node); + sprintf(&sys1[0], "iptables -I INPUT -p tcp --dport %d -j REJECT", port[node]); + if (this->verbose) + { + printf("%s\n", sys1); + fflush(stdout); + } + local_result += ssh_node(node, sys1, true); + + sprintf(&sys1[0], "ip6tables -I INPUT -p tcp --dport %d -j REJECT", port[node]); + if (this->verbose) + { + printf("%s\n", sys1); + fflush(stdout); + } + local_result += ssh_node(node, sys1, true); + + blocked[node] = true; + return local_result; +} + +int Mariadb_nodes::unblock_node(int node) +{ + char sys1[1024]; + int local_result = 0; + local_result += clean_iptables(node); + sprintf(&sys1[0], "iptables -I INPUT -p tcp --dport %d -j ACCEPT", port[node]); + if (this->verbose) + { + printf("%s\n", sys1); + fflush(stdout); + } + local_result += ssh_node(node, sys1, true); + sprintf(&sys1[0], "ip6tables -I INPUT -p tcp --dport %d -j ACCEPT", port[node]); + if (this->verbose) + { + printf("%s\n", sys1); + fflush(stdout); + } + local_result += ssh_node(node, sys1, true); + + blocked[node] = false; + return local_result; +} + + +int Mariadb_nodes::unblock_all_nodes() +{ + int rval = 0; + for (int i = 0; i < this->N; i++) + { + rval += this->unblock_node(i); + } + return rval; +} + +int Mariadb_nodes::check_node_ssh(int node) +{ + int res = 0; + printf("Checking node %d\n", node); + fflush(stdout); + + if (ssh_node(0, (char *) "ls > /dev/null", false) != 0) + { + printf("Node %d is not available\n", node); + fflush(stdout); + res = 1; + } + else + { + printf("Node %d is OK\n", node); + fflush(stdout); + } + return res; +} + +int Mariadb_nodes::check_nodes() +{ + int res = 0; + for (int i = 0; i < N; i++) + { + res += check_node_ssh(i); + } + return res; +} + +bool Mariadb_nodes::check_master_node(MYSQL *conn) +{ + bool rval = true; + + if (mysql_query(conn, "SHOW SLAVE HOSTS")) + { + printf("%s\n", mysql_error(conn)); + rval = false; + } + else + { + MYSQL_RES *res = mysql_store_result(conn); + + if (res) + { + int rows = mysql_num_rows(res); + + if (rows != N - 1) + { + if (!v51) + { + printf("Number of slave hosts is %d when it should be %d\n", rows, N - 1); + rval = false; + } + } + } + mysql_free_result(res); + } + + if (mysql_query(conn, "SHOW SLAVE STATUS")) + { + printf("%s\n", mysql_error(conn)); + rval = false; + } + else + { + MYSQL_RES *res = mysql_store_result(conn); + + if (res) + { + if (mysql_num_rows(res) > 0) + { + printf("The master is configured as a slave\n"); + rval = false; + } + mysql_free_result(res); + } + } + + char output[512]; + find_field(conn, "SHOW VARIABLES LIKE 'read_only'", "Value", output); + + if (strcmp(output, "OFF")) + { + printf("The master is in read-only mode\n"); + rval = false; + } + + return rval; +} + +static bool bad_slave_thread_status(MYSQL *conn, const char *field, int node) +{ + char str[1024] = ""; + bool rval = false; + + for (int i = 0; i < 2; i++) + { + if (find_field(conn, "SHOW SLAVE STATUS;", field, str) != 0) + { + printf("Node %d: %s not found in SHOW SLAVE STATUS\n", node, field); + fflush(stdout); + break; + } + else if (strcmp(str, "Yes") == 0 || strcmp(str, "No") == 0) + { + break; + } + + /** Any other state is transient and we should try again */ + sleep(1); + } + + if (strcmp(str, "Yes") != 0) + { + printf("Node %d: %s is '%s'\n", node, field, str); + fflush(stdout); + rval = true; + } + return rval; +} + +int Mariadb_nodes::check_replication() +{ + int master = 0; + int res = 0; + char str[1024]; + + if (verbose) + { + printf("Checking Master/Slave setup\n"); + fflush(stdout); + } + + if (this->connect()) + { + return 1; + } + + res = get_versions(); + + for (int i = 0; i < N && res == 0; i++) + { + if (i == master) + { + if (!check_master_node(nodes[i])) + { + res = 1; + } + } + else if (bad_slave_thread_status(nodes[i], "Slave_IO_Running", i) || + bad_slave_thread_status(nodes[i], "Slave_SQL_Running", i)) + { + res = 1; + } + } + + return res; +} + +bool Mariadb_nodes::fix_replication() +{ + if (check_replication()) + { + unblock_all_nodes(); + + if (check_nodes()) + { + printf("****** VMS ARE BROKEN! Exiting *****\n"); + return false; + } + + int attempts = 2; + int attempts_with_cleanup = 2; + + while (check_replication() && attempts > 0) + { + if (attempts != 2) + { + stop_nodes(); + } + + start_replication(); + close_connections(); + + attempts--; + + if (attempts == 0 && check_replication()) + { + if (attempts_with_cleanup > 0) + { + printf("****** BACKEND IS STILL BROKEN! Trying to cleanup all nodes *****\n"); + stop_nodes(); + cleanup_db_nodes(); + attempts_with_cleanup--; + attempts = 2; + sleep(30); + start_replication(); + sleep(30); + } + else + { + printf("****** BACKEND IS STILL BROKEN! Exiting *****\n"); + return false; + } + } + } + flush_hosts(); + } + + return true; +} + +int Galera_nodes::check_galera() +{ + int res1 = 0; + + if (verbose) + { + printf("Checking Galera\n"); + fflush(stdout); + } + + if (this->nodes[0] == NULL) + { + this->connect(); + } + + res1 = get_versions(); + + for (int i = 0; i < N; i++) + { + MYSQL *conn = open_conn(port[i], IP[i], user_name, password, ssl); + if (conn == NULL || mysql_errno(conn) != 0) + { + printf("Error connectiong node %d: %s\n", i, mysql_error(conn)); + res1 = 1; + } + else + { + char str[1024] = ""; + + if (find_field(conn, (char *) "SHOW STATUS WHERE Variable_name='wsrep_cluster_size';", (char *) "Value", + str) != 0) + { + printf("wsrep_cluster_size is not found in SHOW STATUS LIKE 'wsrep%%' results\n"); + fflush(stdout); + res1 = 1; + } + else + { + int cluster_size; + sscanf(str, "%d", &cluster_size); + if (cluster_size != N) + { + printf("wsrep_cluster_size is not %d, it is %d\n", N, cluster_size); + fflush(stdout); + res1 = 1; + } + } + } + mysql_close(conn); + } + + return res1; +} + +int Mariadb_nodes::set_slave(MYSQL * conn, char master_host[], int master_port, char log_file[], + char log_pos[]) +{ + char str[1024]; + + sprintf(str, setup_slave, master_host, log_file, log_pos, master_port); + if (no_set_pos) + { + sprintf(str, setup_slave_no_pos, master_host, master_port); + } + + if (this->verbose) + { + printf("Setup slave SQL: %s\n", str); + } + return execute_query(conn, str); +} + +int Mariadb_nodes::set_repl_user() +{ + int global_result = 0; + global_result += connect(); + for (int i = 0; i < N; i++) + { + global_result += execute_query(nodes[i], create_repl_user); + } + close_connections(); + return global_result; +} + +int Mariadb_nodes::get_server_id(int index) +{ + int id = -1; + char str[1024]; + + if (find_field(this->nodes[index], "SELECT @@server_id", "@@server_id", (char*) str) == 0) + { + id = atoi(str); + } + else + { + printf("find_field failed for %s:%d\n", this->IP[index], this->port[index]); + } + + return id; +} + +void Mariadb_nodes::generate_ssh_cmd(char *cmd, int node, const char *ssh, bool sudo) +{ + if (strcmp(IP[node], "127.0.0.1") == 0) + { + if (sudo) + { + sprintf(cmd, "%s %s", + access_sudo[node], ssh); + } + else + { + sprintf(cmd, "%s", + ssh); + + } + } + else + { + + if (sudo) + { + sprintf(cmd, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s '%s %s\'", + sshkey[node], access_user[node], IP[node], access_sudo[node], ssh); + } + else + { + sprintf(cmd, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s '%s'", + sshkey[node], access_user[node], IP[node], ssh); + } + } +} + +char * Mariadb_nodes::ssh_node_output(int node, const char *ssh, bool sudo, int *exit_code) +{ + char sys[strlen(ssh) + 1024]; + generate_ssh_cmd(sys, node, ssh, sudo); + FILE *output = popen(sys, "r"); + if (output == NULL) + { + printf("Error opening ssh %s\n", strerror(errno)); + return NULL; + } + char buffer[1024]; + size_t rsize = sizeof(buffer); + char* result = (char*)calloc(rsize, sizeof(char)); + + while (fgets(buffer, sizeof(buffer), output)) + { + result = (char*)realloc(result, sizeof(buffer) + rsize); + rsize += sizeof(buffer); + strcat(result, buffer); + } + int code = pclose(output); + if (WIFEXITED(code)) + { + * exit_code = WEXITSTATUS(code); + } + else + { + * exit_code = 256; + } + return result; +} + +int Mariadb_nodes::ssh_node(int node, const char *ssh, bool sudo) +{ + char sys[strlen(ssh) + 1024]; + generate_ssh_cmd(sys, node, ssh, sudo); + int return_code = system(sys); + if (WIFEXITED(return_code)) + { + return WEXITSTATUS(return_code); + } + else + { + return 256; + } +} + +int Mariadb_nodes::flush_hosts() +{ + + if (this->nodes[0] == NULL && this->connect()) + { + return 1; + } + + int local_result = 0; + + for (int i = 0; i < N; i++) + { + if (mysql_query(nodes[i], "FLUSH HOSTS")) + { + local_result++; + } + + if (mysql_query(nodes[i], "SET GLOBAL max_connections=10000")) + { + local_result++; + } + + if (mysql_query(nodes[i], "SELECT CONCAT('\\'', user, '\\'@\\'', host, '\\'') FROM mysql.user WHERE user = ''") == 0) + { + MYSQL_RES *res = mysql_store_result(nodes[i]); + + if (res) + { + std::vector users; + MYSQL_ROW row; + + while ((row = mysql_fetch_row(res))) + { + users.push_back(row[0]); + } + + mysql_free_result(res); + + if (users.size() > 0) + { + printf("Detected anonymous users, dropping them.\n"); + + for (auto& s: users) + { + std::string query = "DROP USER "; + query += s; + printf("%s\n", query.c_str()); + mysql_query(nodes[i], query.c_str()); + } + } + } + } + else + { + printf("Failed to query for anonymous users: %s\n", mysql_error(nodes[i])); + local_result++; + } + } + + return local_result; +} + +int Mariadb_nodes::execute_query_all_nodes(const char* sql) +{ + int local_result = 0; + connect(); + for (int i = 0; i < N; i++) + { + local_result += execute_query(nodes[i], sql); + } + close_connections(); + return local_result; +} + +int Mariadb_nodes::get_versions() +{ + int local_result = 0; + char * str; + v51 = false; + + for (int i = 0; i < N; i++) + { + if ((local_result += find_field(nodes[i], (char *) "SELECT @@version", (char *) "@@version", version[i]))) + { + printf("Failed to get version: %s\n", mysql_error(nodes[i])); + } + strcpy(version_number[i], version[i]); + str = strchr(version_number[i], '-'); + if (str != NULL) + { + str[0] = 0; + } + strcpy(version_major[i], version_number[i]); + if (strstr(version_major[i], "5.") == version_major[i]) + { + version_major[i][3] = 0; + } + if (strstr(version_major[i], "10.") == version_major[i]) + { + version_major[i][4] = 0; + } + + if (verbose) + { + printf("Node %s%d: %s\t %s \t %s\n", prefix, i, version[i], version_number[i], version_major[i]); + } + } + + for (int i = 0; i < N; i++) + { + if (strcmp(version_major[i], "5.1") == 0) + { + v51 = true; + } + } + + return local_result; +} + +std::string Mariadb_nodes::get_lowest_version() +{ + std::string rval; + get_versions(); + + int lowest = INT_MAX; + + for (int i = 0; i < N; i++) + { + int int_version = get_int_version(version[i]); + + if (lowest > int_version) + { + rval = version[i]; + lowest = int_version; + } + } + + return rval; +} + +int Mariadb_nodes::truncate_mariadb_logs() +{ + int local_result = 0; + for (int node = 0; node < N; node++) + { + char sys[1024]; + if (strcmp(IP[node], "127.0.0.1") !=0) + { + sprintf(sys, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s 'sudo truncate /var/lib/mysql/*.err --size 0;sudo rm -f /etc/my.cnf.d/binlog_enc*\' &", + sshkey[node], access_user[node], IP[node]); + local_result += system(sys); + } + } + return local_result; +} + +int Mariadb_nodes::configure_ssl(bool require) +{ + int local_result = 0; + char str[1024]; + + this->ssl = 1; + + for (int i = 0; i < N; i++) + { + printf("Node %d\n", i); + stop_node(i); + sprintf(str, "%s/ssl-cert", test_dir); + local_result += copy_to_node(str, (char *) "~/", i); + sprintf(str, "%s/ssl.cnf", test_dir); + local_result += copy_to_node(str, (char *) "~/", i); + local_result += ssh_node(i, (char *) "cp ~/ssl.cnf /etc/my.cnf.d/", true); + local_result += ssh_node(i, (char *) "cp -r ~/ssl-cert /etc/", true); + local_result += ssh_node(i, (char *) "chown mysql:mysql -R /etc/ssl-cert", true); + start_node(i, (char *) ""); + } + + if (require) + { + // Create DB user on first node + printf("Set user to require ssl: %s\n", str); + sprintf(str, "%s/create_user_ssl.sh", test_dir); + copy_to_node(str, (char *) "~/", 0); + + sprintf(str, "export node_user=\"%s\"; export node_password=\"%s\"; ./create_user_ssl.sh %s", + user_name, + password, + socket_cmd[0]); + printf("cmd: %s\n", str); + ssh_node(0, str, false); + } + + return local_result; +} + +int Mariadb_nodes::disable_ssl() +{ + int local_result = 0; + char str[1024]; + + local_result += connect(); + sprintf(str, "DROP USER %s; grant all privileges on *.* to '%s'@'%%' identified by '%s';", user_name, + user_name, password); + local_result += execute_query(nodes[0], (char *) ""); + close_connections(); + + for (int i = 0; i < N; i++) + { + stop_node(i); + local_result += ssh_node(i, (char *) "rm -f /etc/my.cnf.d/ssl.cnf", true); + start_node(i, (char *) ""); + } + + return local_result; +} + +int Mariadb_nodes::copy_to_node(const char* src, const char* dest, int i) +{ + if (i >= N) + { + return 1; + } + char sys[strlen(src) + strlen(dest) + 1024]; + + if (strcmp(IP[i], "127.0.0.1") == 0) + { + sprintf(sys, "cp %s %s", + src, dest); + } + else + { + sprintf(sys, "scp -q -r -i %s -o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no -o LogLevel=quiet %s %s@%s:%s", + sshkey[i], src, access_user[i], IP[i], dest); + } + if (verbose) + { + printf("%s\n", sys); + } + + return system(sys); +} + + +int Mariadb_nodes::copy_from_node(const char* src, const char* dest, int i) +{ + if (i >= N) + { + return 1; + } + char sys[strlen(src) + strlen(dest) + 1024]; + if (strcmp(IP[i], "127.0.0.1") == 0) + { + sprintf(sys, "cp %s %s", + src, dest); + } + else + { + sprintf(sys, "scp -q -r -i %s -o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s:%s %s", + sshkey[i], access_user[i], IP[i], src, dest); + } + if (verbose) + { + printf("%s\n", sys); + } + + return system(sys); +} + +static void wait_until_pos(MYSQL *mysql, int filenum, int pos) +{ + int slave_filenum = 0; + int slave_pos = 0; + + do + { + if (mysql_query(mysql, "SHOW SLAVE STATUS")) + { + printf("Failed to execute SHOW SLAVE STATUS: %s", mysql_error(mysql)); + break; + } + + MYSQL_RES *res = mysql_store_result(mysql); + + if (res) + { + MYSQL_ROW row = mysql_fetch_row(res); + + if (row && row[6] && row[21]) + { + char *file_suffix = strchr(row[5], '.') + 1; + slave_filenum = atoi(file_suffix); + slave_pos = atoi(row[21]); + } + mysql_free_result(res); + } + } + while (slave_filenum < filenum || slave_pos < pos); +} + +void Mariadb_nodes::sync_slaves(int node) +{ + if (this->nodes[node] == NULL) + { + this->connect(); + } + + if (mysql_query(this->nodes[node], "SHOW MASTER STATUS")) + { + printf("Failed to execute SHOW MASTER STATUS: %s", mysql_error(this->nodes[node])); + } + else + { + MYSQL_RES *res = mysql_store_result(this->nodes[node]); + + if (res) + { + MYSQL_ROW row = mysql_fetch_row(res); + if (row && row[node] && row[1]) + { + const char* file_suffix = strchr(row[node], '.') + 1; + int filenum = atoi(file_suffix); + int pos = atoi(row[1]); + + for (int i = 0; i < this->N; i++) + { + if (i != node) + { + wait_until_pos(this->nodes[i], filenum, pos); + } + } + } + mysql_free_result(res); + } + } +} + +void Mariadb_nodes::close_active_connections() +{ + if (this->nodes[0] == NULL) + { + this->connect(); + } + + const char *sql = + "select id from information_schema.processlist where id != @@pseudo_thread_id and user not in ('system user', 'repl')"; + + for (int i = 0; i < N; i++) + { + if (!mysql_query(nodes[i], sql)) + { + MYSQL_RES *res = mysql_store_result(nodes[i]); + if (res) + { + MYSQL_ROW row; + + while ((row = mysql_fetch_row(res))) + { + std::string q("KILL "); + q += row[0]; + execute_query_silent(nodes[i], q.c_str()); + } + mysql_free_result(res); + } + } + } +} diff --git a/maxscale-system-test/mariadb_nodes.h b/maxscale-system-test/mariadb_nodes.h new file mode 100644 index 000000000..bc01f34dc --- /dev/null +++ b/maxscale-system-test/mariadb_nodes.h @@ -0,0 +1,471 @@ +#ifndef MARIADB_NODES_H +#define MARIADB_NODES_H + +/** + * @file mariadb_nodes.h - backend nodes routines + * + * @verbatim + * Revision History + * + * Date Who Description + * 17/11/14 Timofey Turenko Initial implementation + * + * @endverbatim + */ + + +#include "mariadb_func.h" +#include + +/** + * @brief A class to handle backend nodes + * Contains references up to 256 nodes, info about IP, port, ssh key, use name and password for each node + * Node parameters should be defined in the enviromental variables in the follwing way: + * prefix_N - N number of nodes in the setup + * prefix_NNN - IP adress of the node (NNN 3 digits node index) + * prefix_port_NNN - MariaDB port number of the node + * prefix_User - User name to access backend setup (should have full access to 'test' DB with GRANT OPTION) + * prefix_Password - Password to access backend setup + */ +class Mariadb_nodes +{ +public: + /** + * @brief Constructor + * @param pref name of backend setup (like 'repl' or 'galera') + */ + Mariadb_nodes(const char *pref, const char *test_cwd, bool verbose); + + ~Mariadb_nodes(); + + /** + * @brief MYSQL structs for every backend node + */ + MYSQL *nodes[256]; + /** + * @brief IP address strings for every backend node + */ + char IP[256][1024]; + /** + * @brief private IP address strings for every backend node (for AWS) + */ + char IP_private[256][1024]; + /** + * @brief IP address strings for every backend node (IPv6) + */ + char IP6[256][1024]; + /** + * @brief use_ipv6 If true IPv6 addresses will be used to connect Maxscale and backed + * Also IPv6 addresses go to maxscale.cnf + */ + bool use_ipv6; + /** + * @brief MariaDB port for every backend node + */ + int port[256]; + /** + * @brief Unix socket to connecto to MariaDB + */ + char socket[256][1024]; + /** + * @brief 'socket=$socket' line + */ + char socket_cmd[256][1024]; + /** + * @brief Path to ssh key for every backend node + */ + char sshkey[256][4096]; + /** + * @brief Number of backend nodes + */ + int N; + /** + * @brief User name to access backend nodes + */ + char user_name[256]; + /** + * @brief Password to access backend nodes + */ + char password[256]; + /** + * @brief master index of node which was last configured to be Master + */ + int master; + /** + * @brief name of backend setup (like 'repl' or 'galera') + */ + char prefix[16]; + /** + * @brief start_db_command Command to start DB server + */ + char start_db_command[256][4096]; + + /** + * @brief stop_db_command Command to start DB server + */ + char stop_db_command[256][4096]; + + /** + * @brief cleanup_db_command Command to remove all + * data files and re-install DB with mysql_install_db + */ + char cleanup_db_command[256][4096]; + + /** + * @brief ssl if true ssl will be used + */ + int ssl; + + /** + * @brief access_user Unix users name to access nodes via ssh + */ + char access_user[256][256]; + + /** + * @brief access_sudo empty if sudo is not needed or "sudo " if sudo is needed. + */ + char access_sudo[256][64]; + + + /** + * @brief access_homedir home directory of access_user + */ + char access_homedir[256][256]; + + /** + * @brief no_set_pos if set to true setup_binlog function do not set log position + */ + bool no_set_pos; + + /** + * @brief Verbose command output + */ + bool verbose; + + /** + * @brief version Value of @@version + */ + char version[256][256]; + + /** + * @brief version major part of number value of @@version + */ + char version_major[256][256]; + + /** + * @brief version Number part of @@version + */ + char version_number[256][256]; + + /** + * @brief connect open connections to all nodes + * @return 0 in case of success + */ + + /** + * @brief v51 true indicates that one backed is 5.1 + */ + bool v51; + + /** + * @brief test_dir path to test application + */ + char test_dir[4096]; + + + /** + * @brief List of blocked nodes + */ + bool blocked[256]; + + /** + * @brief Open connctions to all backend nodes (to 'test' DB) + * @return 0 in case of success + */ + int connect(); + + /** + * @brief Close connections opened by connect() + * + * This sets the values of used @c nodes to NULL. + */ + void close_connections(); + + /** + * @brief reads IP, Ports, sshkeys for every node from enviromental variables as well as number of nodes (N) and User/Password + * @return 0 + */ + int read_env(); + /** + * @brief prints all nodes information + * @return 0 + */ + int print_env(); + + /** + * @brief find_master Tries to find Master node + * @return Index of Master node + */ + int find_master(); + /** + * @brief change_master set a new master node for Master/Slave setup + * @param NewMaster index of new Master node + * @param OldMaster index of current Master node + * @return 0 in case of success + */ + int change_master(int NewMaster, int OldMaster); + + /** + * @brief stop_nodes stops mysqld on all nodes + * @return 0 in case of success + */ + int stop_nodes(); + + /** + * @brief stop_slaves isues 'stop slave;' to all nodes + * @return 0 in case of success + */ + int stop_slaves(); + + /** + * @brief cleanup_db_node Removes all data files and reinstall DB + * with mysql_install_db + * @param node + * @return 0 in case of success + */ + int cleanup_db_node(int node); + + /** + * @brief cleanup_db_node Removes all data files and reinstall DB + * with mysql_install_db for all nodes + * @param node + * @return 0 in case of success + */ + int cleanup_db_nodes(); + + /** + * @brief configures nodes and starts Master/Slave replication + * @return 0 in case of success + */ + virtual int start_replication(); + + /** + * @brif BlockNode setup firewall on a backend node to block MariaDB port + * @param node Index of node to block + * @return 0 in case of success + */ + int block_node(int node); + + /** + * @brief UnblockNode setup firewall on a backend node to unblock MariaDB port + * @param node Index of node to unblock + * @return 0 in case of success + */ + int unblock_node(int node); + + + /** + * @brief Unblock all nodes for this cluster + * @return 0 in case of success + */ + int unblock_all_nodes(); + + /** + * @brief clean_iptables removes all itables rules connected to MariaDB port to avoid duplicates + * @param node Index of node to clean + * @return 0 in case of success + */ + int clean_iptables(int node); + + /** + * @brief Stop DB server on the node + * @param node Node index + * @return 0 if success + */ + int stop_node(int node); + + /** + * @brief Start DB server on the node + * @param node Node index + * @param param command line parameters for DB server start command + * @return 0 if success + */ + int start_node(int node, char * param); + + /** + * @brief Check node via ssh and restart it if it is not resposible + * @param node Node index + * @return 0 if node is ok, 1 if start failed + */ + int check_nodes(); + + /** + * @brief Check if all slaves have "Slave_IO_Running" set to "Yes" and master has N-1 slaves + * @param master Index of master node + * @return 0 if everything is ok + */ + virtual int check_replication(); + + /** + * @brief executes 'CHANGE MASTER TO ..' and 'START SLAVE' + * @param MYSQL conn struct of slave node + * @param master_host IP address of master node + * @param master_port port of master node + * @param log_file name of log file + * @param log_pos initial position + * @return 0 if everything is ok + */ + int set_slave(MYSQL * conn, char master_host[], int master_port, char log_file[], char log_pos[]); + + /** + * @brief Creates 'repl' user on all nodes + * @return 0 if everything is ok + */ + int set_repl_user(); + + /** + * @brief Get the server_id of the node + * @param index The index of the node whose server_id to retrieve + * @return Node id of the server or -1 on error + */ + int get_server_id(int index); + + /** + * @brief Generate command line to execute command on the node via ssh + * @param cmd result + * @param index index number of the node (index) + * @param ssh command to execute + * @param sudo if true the command is executed with root privelegues + */ + void generate_ssh_cmd(char * cmd, int node, const char *ssh, bool sudo); + + /** + * @brief executes shell command on the node using ssh + * @param index number of the node (index) + * @param ssh command to execute + * @param sudo if true the command is executed with root privelegues + * @param pointer to variable to store process exit code + * @return output of the command + */ + char *ssh_node_output(int node, const char *ssh, bool sudo, int *exit_code); + + /** + * @brief executes shell command on the node using ssh + * @param index number of the node (index) + * @param ssh command to execute + * @param sudo if true the command is executed with root privelegues + * @return exit code of the coomand + */ + int ssh_node(int node, const char *ssh, bool sudo); + + /** + * @brief Execute 'mysqladmin flush-hosts' on all nodes + * @return 0 in case of success + */ + int flush_hosts(); + + /** + * @brief Execute query on all nodes + * @param sql query to execute + * @return 0 in case of success + */ + int execute_query_all_nodes(const char* sql); + + /** + * @brief execute 'SELECT @@version' against all nodes and store result in 'version' fied + * @return 0 in case of success + */ + int get_versions(); + + + /** + * @brief Return lowest server version in the cluster + * @return The version string of the server with the lowest version number + */ + std::string get_lowest_version(); + + /** + * @brief truncate_mariadb_logs clean ups MariaDB logs on backend nodes + * @return 0 if success + */ + int truncate_mariadb_logs(); + + /** + * @brief configure_ssl Modifies my.cnf in order to enable ssl, redefine access user to require ssl + * @return 0 if success + */ + int configure_ssl(bool require); + + /** + * @brief disable_ssl Modifies my.cnf in order to get rid of ssl, redefine access user to allow connections without ssl + * @return 0 if success + */ + int disable_ssl(); + + /** + * @brief Copy a local file to the Node i machine + * @param src Source file on the local filesystem + * @param dest Destination file on the remote file system + * @param i Node index + * @return exit code of the system command or 1 in case of i > N + */ + int copy_to_node(const char* src, const char* dest, int i); + + /** + * @brief Copy a local file to the Node i machine + * @param src Source file on the remote filesystem + * @param dest Destination file on the local file system + * @param i Node index + * @return exit code of the system command or 1 in case of i > N + */ + int copy_from_node(const char* src, const char* dest, int i); + + /** + * @brief Synchronize slaves with the master + * + * Only works with master-slave replication and should not be used with Galera clusters. + * The function expects that the first node, @c nodes[0], is the master. + */ + void sync_slaves(int node = 0); + + /** + * @brief Close all connections to this node + * + * This will kill all connections that have been created to this node. + */ + void close_active_connections(); + + /** + * @brief Check and fix replication + */ + bool fix_replication(); + +private: + + int check_node_ssh(int node); + bool check_master_node(MYSQL *conn); +}; + +class Galera_nodes : public Mariadb_nodes +{ +public: + + Galera_nodes(const char *pref, const char *test_cwd, bool verbose) : + Mariadb_nodes(pref, test_cwd, verbose) { } + + int start_galera(); + + virtual int start_replication() + { + return start_galera(); + } + + int check_galera(); + + virtual int check_replication() + { + return check_galera(); + } +}; + +#endif // MARIADB_NODES_H diff --git a/maxscale-system-test/mariadb_tests_hartmut.sh b/maxscale-system-test/mariadb_tests_hartmut.sh new file mode 100755 index 000000000..ef8b6e64d --- /dev/null +++ b/maxscale-system-test/mariadb_tests_hartmut.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# This is required by one of the tests +# +# TODO: Don't test correctness of routing with mysqltest +# +./non_native_setup $1 +master_id=`echo "SELECT @@server_id" | mysql -u$node_user -p$node_password -h $node_000_network $ssl_options -P $node_000_port | tail -n1` +echo "--disable_query_log" > Hartmut_tests/maxscale-mysqltest/testconf.inc +echo "SET @TMASTER_ID=$master_id;" >> Hartmut_tests/maxscale-mysqltest/testconf.inc +echo "--enable_query_log" >> Hartmut_tests/maxscale-mysqltest/testconf.inc + +./mysqltest_driver.sh $1 Hartmut_tests/maxscale-mysqltest 4006 + +ret=$? +./copy_logs.sh $1 + +exit $ret diff --git a/maxscale-system-test/masking/README.TXT b/maxscale-system-test/masking/README.TXT new file mode 100644 index 000000000..5b9b59f7a --- /dev/null +++ b/maxscale-system-test/masking/README.TXT @@ -0,0 +1,4 @@ +Note that if modifications are made to any of the t/*-files and the r/*-files +are regenerated, the output MUST BE CHECKED MANUALLY to ensure that the produced +result indeed is the expected. Otherwise the mysqltest-based tests effectively +tests nothing. diff --git a/maxscale-system-test/masking/masking_mysqltest/masking_rules.json b/maxscale-system-test/masking/masking_mysqltest/masking_rules.json new file mode 100644 index 000000000..76ee2f24a --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/masking_rules.json @@ -0,0 +1,29 @@ +{ + "rules": [ + { + "replace": { + "column": "a" + }, + "with": { + "fill": "X" + } + }, + { + "replace": { + "column": "b" + }, + "with": { + "value": "012345-ABCD" + } + }, + { + "replace": { + "column": "c" + }, + "with": { + "value": "012345-ABCD", + "fill": "X" + } + } + ] +} diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result new file mode 100644 index 000000000..fb9cfd59f --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_column.result @@ -0,0 +1,57 @@ +drop database if exists maskingdb; +create database maskingdb; +use maskingdb; +create table masking (a TEXT, x TEXT, y TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +a x y +XXXXX hello hello +NULL hello hello +XXXXX NULL hello +XXXXX hello NULL +NULL NULL hello +XXXXX NULL NULL +NULL NULL NULL +drop table masking; +create table masking (x TEXT, a TEXT, y TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +x a y +hello XXXXX hello +NULL XXXXX hello +hello NULL hello +hello XXXXX NULL +NULL NULL hello +hello NULL NULL +NULL NULL NULL +drop table masking; +create table masking (x TEXT, y TEXT, a TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +x y a +hello hello XXXXX +NULL hello XXXXX +hello NULL XXXXX +hello hello NULL +NULL NULL XXXXX +hello NULL NULL +NULL NULL NULL +drop table masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result new file mode 100644 index 000000000..afbd3a9dd --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_replace.result @@ -0,0 +1,19 @@ +drop database if exists maskingdb; +create database maskingdb; +use maskingdb; +create table masking (a TEXT, b TEXT, c TEXT); +insert into masking values ("blah", "012345-ABC", "012345-ABC"); +select * from masking; +a b c +XXXX 012345-ABC XXXXXXXXXX +delete from masking; +insert into masking values ("blahblah", "221073-01AB", "012345-ABC"); +select * from masking; +a b c +XXXXXXXX 012345-ABCD XXXXXXXXXX +delete from masking; +insert into masking values ("221073-01AB", "221073-01AB", "221073-01AB"); +select * from masking; +a b c +XXXXXXXXXXX 012345-ABCD 012345-ABCD +delete from masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result b/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result new file mode 100644 index 000000000..ae84e1d18 --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/r/masking_smoke.result @@ -0,0 +1,123 @@ +drop database if exists maskingdb; +create database maskingdb; +use maskingdb; +create table masking_BINARY (a BINARY(3)); +create table masking_VARBINARY (a VARBINARY(8)); +create table masking_CHAR (a CHAR(3)); +create table masking_VARCHAR (a VARCHAR(13)); +create table masking_BLOB (a BLOB); +create table masking_TINYBLOB (a TINYBLOB); +create table masking_MEDIUMBLOB (a MEDIUMBLOB); +create table masking_LONGBLOB (a LONGBLOB); +create table masking_TEXT (a TEXT); +create table masking_TINYTEXT (a TINYTEXT); +create table masking_MEDIUMTEXT (a MEDIUMTEXT); +create table masking_LONGTEXT (a LONGTEXT); +create table masking_ENUM (a ENUM('aaa', 'bbb', 'ccc')); +create table masking_SET (a SET('aaa', 'bbb', 'ccc')); +insert into masking_BINARY values ("aaa"); +insert into masking_VARBINARY values ("aaa"); +insert into masking_CHAR values ("aaa"); +insert into masking_VARCHAR values ("aaa"); +insert into masking_BLOB values ("aaa"); +insert into masking_TINYBLOB values ("aaa"); +insert into masking_MEDIUMBLOB values ("aaa"); +insert into masking_LONGBLOB values ("aaa"); +insert into masking_TEXT values ("aaa"); +insert into masking_TINYTEXT values ("aaa"); +insert into masking_MEDIUMTEXT values ("aaa"); +insert into masking_LONGTEXT values ("aaa"); +insert into masking_ENUM values ("aaa"); +insert into masking_SET values ("aaa"); +select * from masking_BINARY; +a +XXX +select * from masking_VARBINARY; +a +XXX +select * from masking_CHAR; +a +XXX +select * from masking_VARCHAR; +a +XXX +select * from masking_BLOB; +a +XXX +select * from masking_TINYBLOB; +a +XXX +select * from masking_MEDIUMBLOB; +a +XXX +select * from masking_LONGBLOB; +a +XXX +select * from masking_TEXT; +a +XXX +select * from masking_TINYTEXT; +a +XXX +select * from masking_MEDIUMTEXT; +a +XXX +select * from masking_LONGTEXT; +a +XXX +select * from masking_ENUM; +a +XXX +select * from masking_SET; +a +XXX +create table masking_INT (a INT); +create table masking_REAL (a REAL(3, 2)); +create table masking_DECIMAL (a DECIMAL(3, 2)); +create table masking_FLOAT (a FLOAT(3, 2)); +create table masking_DOUBLE (a DOUBLE(3, 2)); +create table masking_DATE (a DATE); +create table masking_TIME (a TIME); +create table masking_DATETIME (a DATETIME); +create table masking_TIMESTAMP (a TIMESTAMP); +create table masking_YEAR (a YEAR); +insert into masking_INT values (4711); +insert into masking_REAL values (3.14); +insert into masking_DECIMAL values (3.14); +insert into masking_FLOAT values (3.14); +insert into masking_DOUBLE values (3.14); +insert into masking_DATE values ('2017-01-24'); +insert into masking_TIME values ('13:52:21'); +insert into masking_DATETIME values ('2017-01-24 13:52:21'); +insert into masking_TIMESTAMP values ('2017-01-24 13:52:21'); +insert into masking_YEAR values ('2001'); +select * from masking_INT; +a +4711 +select * from masking_REAL; +a +3.14 +select * from masking_DECIMAL; +a +3.14 +select * from masking_FLOAT; +a +3.14 +select * from masking_DOUBLE; +a +3.14 +select * from masking_DATE; +a +2017-01-24 +select * from masking_TIME; +a +13:52:21 +select * from masking_DATETIME; +a +2017-01-24 13:52:21 +select * from masking_TIMESTAMP; +a +2017-01-24 13:52:21 +select * from masking_YEAR; +a +2001 diff --git a/maxscale-system-test/masking/masking_mysqltest/t/masking_column.test b/maxscale-system-test/masking/masking_mysqltest/t/masking_column.test new file mode 100644 index 000000000..2c1fe27d3 --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/t/masking_column.test @@ -0,0 +1,75 @@ +# +# Masking column handling +# +# Whether the column to be masked is first, in the middle or +# last should not matter. +# +# See ../masking_rules.json + +--disable_warnings +drop database if exists maskingdb; +--enable_warnings + +create database maskingdb; +use maskingdb; + +create table masking (a TEXT, x TEXT, y TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +#a x y +#XXXXX hello hello +#NULL hello hello +#XXXXX NULL hello +#XXXXX hello NULL +#NULL NULL hello +#XXXXX NULL NULL +#NULL NULL NULL + +drop table masking; + +create table masking (x TEXT, a TEXT, y TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +#x a y +#hello XXXXX hello +#hello XXXXX hello +#NULL XXXXX hello +#hello NULL hello +#hello XXXXX NULL +#NULL NULL hello +#hello NULL NULL +#NULL NULL NULL + +drop table masking; + +create table masking (x TEXT, y TEXT, a TEXT); +insert into masking values ("hello", "hello", "hello"); +insert into masking values (NULL, "hello", "hello"); +insert into masking values ("hello", NULL, "hello"); +insert into masking values ("hello", "hello", NULL); +insert into masking values (NULL, NULL, "hello"); +insert into masking values ("hello", NULL, NULL); +insert into masking values (NULL, NULL, NULL); +select * from masking; +#x y a +#hello hello XXXXX +#NULL hello XXXXX +#hello NULL XXXXX +#hello hello NULL +#NULL NULL XXXXX +#hello NULL NULL +#NULL NULL NULL + +drop table masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test b/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test new file mode 100644 index 000000000..e95534f53 --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/t/masking_replace.test @@ -0,0 +1,83 @@ +# +# Masking Smoke +# +# We expect the masking rules to be as follows: +# +# { +# "rules": [ +# { +# "replace": { +# "column": "a" +# }, +# "with": { +# "fill": "X" +# } +# }, +# { +# "replace": { +# "column": "b" +# }, +# "with": { +# "value": "012345-ABCD" +# } +# }, +# { +# "replace": { +# "column": "c" +# }, +# "with": { +# "value": "012345-ABCD", +# "fill": "X" +# } +# } +# ] +# } + +--disable_warnings +drop database if exists maskingdb; +--enable_warnings + +create database maskingdb; +use maskingdb; + +# +# Each table contains a single column 'a' of a type subject +# to masking. +# +create table masking (a TEXT, b TEXT, c TEXT); + +# - a should be just "X...", +# - b should be unchanged as the length does not match the string of "value", and +# there is no catch all "fill". +# - c should be just "X..." as the length does not match, so "value" is not applied +# and has "fill", which is applied. +# +#a b c +#XXXX 012345-ABC XXXXXXXXXX +insert into masking values ("blah", "012345-ABC", "012345-ABC"); +select * from masking; +delete from masking; + +# - a should be just "X...", +# - b should be changed as the length matches the length of the string of "value" +# - c should be just "X..." as the length does not match, so "value" is not applied +# and has "fill", which is applied. +# +#a b c +#XXXXXXXX 012345-ABCD XXXXXXXXXX +insert into masking values ("blahblah", "221073-01AB", "012345-ABC"); +select * from masking; +delete from masking; + +# - a should be just "X...", +# - b should be changed as the length matches the length of the string of "value" +# - c should be chanched into a specific string as the length matches the string of +# "value" +# +#a b c +#a b c +#XXXXXXXXXXX 012345-ABCD 012345-ABCD +# a should still be just "X", b should be "012345-ABCD" and c should be "012345-ABCD" +insert into masking values ("221073-01AB", "221073-01AB", "221073-01AB"); +select * from masking; +delete from masking; diff --git a/maxscale-system-test/masking/masking_mysqltest/t/masking_smoke.test b/maxscale-system-test/masking/masking_mysqltest/t/masking_smoke.test new file mode 100644 index 000000000..f0db2040e --- /dev/null +++ b/maxscale-system-test/masking/masking_mysqltest/t/masking_smoke.test @@ -0,0 +1,126 @@ +# +# Masking Smoke +# +# We expect the masking rules to be as follows: +# +# { +# "rules": [ +# { +# "replace": { +# "column": "a" +# }, +# "with": { +# "fill": "X" +# } +# } +# ] +# } + +--disable_warnings +drop database if exists maskingdb; +--enable_warnings + +create database maskingdb; +use maskingdb; + +# +# Each table contains a single column 'a' of a type subject +# to masking. +# +create table masking_BINARY (a BINARY(3)); +create table masking_VARBINARY (a VARBINARY(8)); +create table masking_CHAR (a CHAR(3)); +create table masking_VARCHAR (a VARCHAR(13)); +create table masking_BLOB (a BLOB); +create table masking_TINYBLOB (a TINYBLOB); +create table masking_MEDIUMBLOB (a MEDIUMBLOB); +create table masking_LONGBLOB (a LONGBLOB); +create table masking_TEXT (a TEXT); +create table masking_TINYTEXT (a TINYTEXT); +create table masking_MEDIUMTEXT (a MEDIUMTEXT); +create table masking_LONGTEXT (a LONGTEXT); +create table masking_ENUM (a ENUM('aaa', 'bbb', 'ccc')); +create table masking_SET (a SET('aaa', 'bbb', 'ccc')); + +insert into masking_BINARY values ("aaa"); +insert into masking_VARBINARY values ("aaa"); +insert into masking_CHAR values ("aaa"); +insert into masking_VARCHAR values ("aaa"); +insert into masking_BLOB values ("aaa"); +insert into masking_TINYBLOB values ("aaa"); +insert into masking_MEDIUMBLOB values ("aaa"); +insert into masking_LONGBLOB values ("aaa"); +insert into masking_TEXT values ("aaa"); +insert into masking_TINYTEXT values ("aaa"); +insert into masking_MEDIUMTEXT values ("aaa"); +insert into masking_LONGTEXT values ("aaa"); +insert into masking_ENUM values ("aaa"); +insert into masking_SET values ("aaa"); + +# +# In masking_smoke.result, we should have: +# +# a +# XXX +# +# for each following select. +# +select * from masking_BINARY; +select * from masking_VARBINARY; +select * from masking_CHAR; +select * from masking_VARCHAR; +select * from masking_BLOB; +select * from masking_TINYBLOB; +select * from masking_MEDIUMBLOB; +select * from masking_LONGBLOB; +select * from masking_TEXT; +select * from masking_TINYTEXT; +select * from masking_MEDIUMTEXT; +select * from masking_LONGTEXT; +select * from masking_ENUM; +select * from masking_SET; + +# +# Each table contains a single column 'a' of a type NOT subject +# to masking. +# +create table masking_INT (a INT); +create table masking_REAL (a REAL(3, 2)); +create table masking_DECIMAL (a DECIMAL(3, 2)); +create table masking_FLOAT (a FLOAT(3, 2)); +create table masking_DOUBLE (a DOUBLE(3, 2)); +create table masking_DATE (a DATE); +create table masking_TIME (a TIME); +create table masking_DATETIME (a DATETIME); +create table masking_TIMESTAMP (a TIMESTAMP); +create table masking_YEAR (a YEAR); + +insert into masking_INT values (4711); +insert into masking_REAL values (3.14); +insert into masking_DECIMAL values (3.14); +insert into masking_FLOAT values (3.14); +insert into masking_DOUBLE values (3.14); +insert into masking_DATE values ('2017-01-24'); +insert into masking_TIME values ('13:52:21'); +insert into masking_DATETIME values ('2017-01-24 13:52:21'); +insert into masking_TIMESTAMP values ('2017-01-24 13:52:21'); +insert into masking_YEAR values ('2001'); + +# +# In masking_smoke.result, we should have: +# +# a +# +# +# for each following select. +# +select * from masking_INT; +select * from masking_REAL; +select * from masking_DECIMAL; +select * from masking_FLOAT; +select * from masking_DOUBLE; +select * from masking_DATE; +select * from masking_TIME; +select * from masking_DATETIME; +select * from masking_TIMESTAMP; +select * from masking_YEAR; diff --git a/maxscale-system-test/masking/masking_user/masking_rules.json b/maxscale-system-test/masking/masking_user/masking_rules.json new file mode 100644 index 000000000..110fc8f6f --- /dev/null +++ b/maxscale-system-test/masking/masking_user/masking_rules.json @@ -0,0 +1,76 @@ +{ + "rules": [ + { + "replace": { + "column": "a" + }, + "with": { + "fill": "X" + } + }, + { + "replace": { + "column": "b" + }, + "with": { + "fill": "X" + }, + "applies_to" : [ "'skysql'" ] + }, + { + "replace": { + "column": "c" + }, + "with": { + "fill": "X" + }, + "applies_to" : [ "'maxskysql'" ] + }, + { + "replace": { + "column": "d" + }, + "with": { + "fill": "X" + }, + "exempted" : [ "'skysql'" ] + }, + { + "replace": { + "column": "e" + }, + "with": { + "fill": "X" + }, + "exempted" : [ "'maxskysql'" ] + }, + { + "replace": { + "column": "f" + }, + "with": { + "fill": "X" + }, + "applies_to" : [ "'skysql'", "'maxskysql'" ] + }, + { + "replace": { + "column": "g" + }, + "with": { + "fill": "X" + }, + "exempted" : [ "'skysql'", "'maxskysql'" ] + }, + { + "replace": { + "column": "h" + }, + "with": { + "fill": "X" + }, + "applies_to" : [ "'skysql'" ], + "exempted" : [ "'maxskysql'" ] + } + ] +} diff --git a/maxscale-system-test/masking/masking_user/r/masking_user_maxskysql.result b/maxscale-system-test/masking/masking_user/r/masking_user_maxskysql.result new file mode 100644 index 000000000..cdfe6da23 --- /dev/null +++ b/maxscale-system-test/masking/masking_user/r/masking_user_maxskysql.result @@ -0,0 +1,8 @@ +drop database if exists maskingdb; +create database maskingdb; +use maskingdb; +create table masking (a TEXT, b TEXT, c TEXT, d TEXT, e TEXT, f TEXT, g TEXT, h TEXT); +insert into masking values ("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); +select * from masking; +a b c d e f g h +XXXXX hello XXXXX XXXXX hello XXXXX hello hello diff --git a/maxscale-system-test/masking/masking_user/r/masking_user_skysql.result b/maxscale-system-test/masking/masking_user/r/masking_user_skysql.result new file mode 100644 index 000000000..4aff09b95 --- /dev/null +++ b/maxscale-system-test/masking/masking_user/r/masking_user_skysql.result @@ -0,0 +1,8 @@ +drop database if exists maskingdb; +create database maskingdb; +use maskingdb; +create table masking (a TEXT, b TEXT, c TEXT, d TEXT, e TEXT, f TEXT, g TEXT, h TEXT); +insert into masking values ("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); +select * from masking; +a b c d e f g h +XXXXX XXXXX hello hello XXXXX XXXXX hello XXXXX diff --git a/maxscale-system-test/masking/masking_user/t/masking_user.test b/maxscale-system-test/masking/masking_user/t/masking_user.test new file mode 100644 index 000000000..7b5a74594 --- /dev/null +++ b/maxscale-system-test/masking/masking_user/t/masking_user.test @@ -0,0 +1,46 @@ +# +# Masking User +# +# See ../masking_rules.json + +drop database if exists maskingdb; + +create database maskingdb; +use maskingdb; + +# +# Each table contains a single column 'a' of a type subject +# to masking. +# +create table masking (a TEXT, b TEXT, c TEXT, d TEXT, e TEXT, f TEXT, g TEXT, h TEXT); + +insert into masking values ("hello", "hello", "hello", "hello", "hello", "hello", "hello", "hello"); +select * from masking; + +# With the user skysql +# +# a: XXXXX, because the rule applies to everyone. +# b: XXXXX, because the rule specifically applies to the user 'skysql' +# c: hello, because the rule specifically applies to the user 'maxskysql' +# d: hello, because the rule specifically excludes the user 'skysql' +# e: XXXXX, because the rule applies to everyone except to the user 'maxskysql' +# f: XXXXX, because the rule applies to the user 'skysql' and 'maxskysql' +# g: hello, because the rule specifically does not apply to the users 'skysql' and 'maxskysql' +# h: XXXXX, because the rule specifically applies to the user 'skysql' +# +#a b c d e f g h +#XXXXX XXXXX hello hello XXXXX XXXXX hello XXXXX + +# With the user maxskysql +# +# a: XXXXX, because the rule applies to everyone. +# b: hello, because the rule specifically applies to the user 'skysql' +# c: XXXXX, because the rule specifically applies to the user 'maxskysql' +# d: XXXXX, because the rule specifically excludes the user 'skysql' +# e: hello, because the rule applies to everyone except to the user 'maxskysql' +# f: XXXXX, because the rule applies to the user 'skysql' and 'maxskysql' +# g: hello, because the rule specifically does not apply to the users 'skysql' and 'maxskysql' +# h: XXXXX, because the rule specifically applies to the user 'skysql' +# +#a b c d e f g h +#XXXXX hello XXXXX XXXXX hello XXXXX hello hello diff --git a/maxscale-system-test/masking_mysqltest_driver.sh b/maxscale-system-test/masking_mysqltest_driver.sh new file mode 100755 index 000000000..676a3a24f --- /dev/null +++ b/maxscale-system-test/masking_mysqltest_driver.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +script=`basename "$0"` + +if [ $# -lt 1 ] +then + echo "usage: $script name [user] [password]" + echo "" + echo "name : The name of the test (from CMakeLists.txt) That selects the" + echo " configuration template to be used." + echo "user : The user using which the test should be run." + echo "password: The password of the user." + exit 1 +fi + +if [ "$maxscale_IP" == "" ] +then + echo "Error: The environment variable maxscale_IP must be set." + exit 1 +fi + +source=masking/$1/masking_rules.json +target=$maxscale_access_user@$maxscale_IP:/home/$maxscale_access_user/masking_rules.json + +if [ $maxscale_IP != "127.0.0.1" ] ; then + scp -i $maxscale_keyfile -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $source $target +else + cp $source /home/$maxscale_access_user/masking_rules.json +fi + +if [ $? -ne 0 ] +then + echo "error: Could not copy rules file to maxscale host." + exit 1 +fi + +echo $source copied to $target + +password= +if [ $# -ge 3 ] +then + password=$3 +fi + +user= +if [ $# -ge 2 ] +then + user=$2 +fi + +# [Read Connection Listener Master] in cnf/maxscale.maxscale.cnf.template.$1 +port=4008 + +./mysqltest_driver.sh $1 ./masking/$1 $port $user $password diff --git a/maxscale-system-test/masking_user.sh b/maxscale-system-test/masking_user.sh new file mode 100755 index 000000000..9f16ccea8 --- /dev/null +++ b/maxscale-system-test/masking_user.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +script=`basename "$0"` + +if [ $# -lt 1 ] +then + echo "usage: $script name" + echo "" + echo "name : The name of the test (from CMakeLists.txt) That selects the" + echo " configuration template to be used." + exit 1 +fi + +if [ "$maxscale_IP" == "" ] +then + echo "Error: The environment variable maxscale_IP must be set." + exit 1 +fi + +source=masking/$1/masking_rules.json +target=vagrant@$maxscale_IP:/home/$maxscale_access_user/masking_rules.json + +if [ $maxscale_IP != "127.0.0.1" ] ; then + scp -i $maxscale_keyfile -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $source $target +else + cp $source /home/$maxscale_access_user/masking_rules.json +fi + +if [ $? -ne 0 ] +then + echo "error: Could not copy rules file to maxscale host." + exit 1 +fi + +echo $source copied to $target + +test_dir=`pwd` + +$test_dir/non_native_setup $1 + +cd masking/$1 +[ -d log ] && rm -r log +mkdir log || exit 1 + +# [Read Connection Listener Master] in cnf/maxscale.maxscale.cnf.template.$1 +port=4008 +password=skysql + +user=skysql +test_name=masking_user +mysqltest --host=$maxscale_IP --port=$port \ + --user=$user --password=$password \ + --logdir=log \ + --test-file=t/$test_name.test \ + --result-file=r/"$test_name"_"$user".result \ + --silent +if [ $? -eq 0 ] +then + echo " OK" +else + echo " FAILED" + res=1 +fi + +user=maxskysql +test_name=masking_user +mysqltest --host=$maxscale_IP --port=$port \ + --user=$user --password=$password \ + --logdir=log \ + --test-file=t/$test_name.test \ + --result-file=r/"$test_name"_"$user".result \ + --silent +if [ $? -eq 0 ] +then + echo " OK" +else + echo " FAILED" + res=1 +fi + +echo + +# Copy logs from the VM +$test_dir/copy_logs.sh $1 + +exit $res diff --git a/maxscale-system-test/max_connections.cpp b/maxscale-system-test/max_connections.cpp new file mode 100644 index 000000000..0f93dcbca --- /dev/null +++ b/maxscale-system-test/max_connections.cpp @@ -0,0 +1,73 @@ +/** + * @file max_connections.cpp Creates a number of connections > max_connections setting + * - set global max_connections = 20 + * - create 20 connections, find on which iteration query start to fail + * - when limit is found close last 2 connections + * - in the loop: open two connections, expect first to succeed, second to fail, close them both and repeat + * - close all connections + */ + +#include "testconnections.h" + +#define CONNECTIONS 21 +#define ITER 25 + +int main(int argc, char** argv) +{ + MYSQL *mysql[CONNECTIONS]; + TestConnections * Test = new TestConnections(argc, argv); + Test->stop_timeout(); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 20;"); + sleep(5); + int limit = 0; + + for (int i = 0; i < CONNECTIONS - 1; i++) + { + Test->tprintf("Opening connection %d\n", i + 1); + Test->set_timeout(30); + mysql[i] = Test->open_rwsplit_connection(); + if (execute_query_silent(mysql[i], "select 1")) + { + /** Monitors and such take up some connections so we'll set the + * limit to the point where we know it'll start failing.*/ + Test->stop_timeout(); + limit = i; + mysql_close(mysql[limit]); + mysql_close(mysql[limit - 1]); + Test->tprintf("Found limit, %d connections\n", limit); + break; + } + Test->stop_timeout(); + sleep(1); + } + + sleep(5); + Test->tprintf("Opening two connections for %d times. One should succeed while the other should fail.\n", + ITER); + for (int i = 0; i < ITER; i++) + { + Test->set_timeout(30); + mysql[limit - 1] = Test->open_rwsplit_connection(); + mysql[limit] = Test->open_rwsplit_connection(); + Test->add_result(execute_query_silent(mysql[limit - 1], "select 1"), "Query should succeed\n"); + Test->add_result(!execute_query_silent(mysql[limit], "select 1"), "Query should fail\n"); + mysql_close(mysql[limit - 1]); + mysql_close(mysql[limit]); + sleep(2); + } + + Test->set_timeout(30); + for (int i = 0; i < limit - 1; i++) + { + mysql_close(mysql[i]); + } + + sleep(5); + Test->stop_timeout(); + Test->check_maxscale_alive(); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 100;"); + int rval = Test->global_result; + delete Test; + return rval; + +} diff --git a/maxscale-system-test/maxadmin_operations.cpp b/maxscale-system-test/maxadmin_operations.cpp new file mode 100644 index 000000000..83f9bee93 --- /dev/null +++ b/maxscale-system-test/maxadmin_operations.cpp @@ -0,0 +1,277 @@ +/* + * This file is distributed as part of MaxScale. It is free + * software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, + * version 2. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Copyright MariaDB Corporation Ab 2014 + */ + +#include "maxadmin_operations.h" + +int +connectMaxScale(char *hostname, char *port) +{ + struct sockaddr_in addr; + int so; + int keepalive = 1; + + if ((so = socket(AF_INET, SOCK_STREAM, 0)) < 0) + { + fprintf(stderr, "Unable to create socket: %s\n", + strerror(errno)); + return -1; + } + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + setipaddress(&addr.sin_addr, hostname); + addr.sin_port = htons(atoi(port)); + if (connect(so, (struct sockaddr *)&addr, sizeof(addr)) < 0) + { + fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n", + hostname, port, strerror(errno)); + close(so); + return -1; + } + if (setsockopt(so, SOL_SOCKET, + SO_KEEPALIVE, &keepalive , sizeof(keepalive ))) + { + perror("setsockopt"); + } + + return so; +} + + +int +setipaddress(struct in_addr *a, char *p) +{ +#ifdef __USE_POSIX + struct addrinfo *ai = NULL, hint; + int rc; + struct sockaddr_in * res_addr; + memset(&hint, 0, sizeof (hint)); + + hint.ai_socktype = SOCK_STREAM; + hint.ai_flags = AI_CANONNAME; + hint.ai_family = AF_INET; + + if ((rc = getaddrinfo(p, NULL, &hint, &ai)) != 0) + { + return 0; + } + + /* take the first one */ + if (ai != NULL) + { + res_addr = (struct sockaddr_in *)(ai->ai_addr); + memcpy(a, &res_addr->sin_addr, sizeof(struct in_addr)); + + freeaddrinfo(ai); + + return 1; + } +#else + struct hostent *h; + + spinlock_acquire(&tmplock); + h = gethostbyname(p); + spinlock_release(&tmplock); + + if (h == NULL) + { + if ((a->s_addr = inet_addr(p)) == -1) + { + return 0; + } + } + else + { + /* take the first one */ + memcpy(a, h->h_addr, h->h_length); + + return 1; + } +#endif + return 0; +} + +int +authMaxScale(int so, char *user, char *password) +{ + char buf[20]; + + if (read(so, buf, 4) != 4) + { + return 0; + } + write(so, user, strlen(user)); + if (read(so, buf, 8) != 8) + { + return 0; + } + write(so, password, strlen(password)); + if (read(so, buf, 6) != 6) + { + return 0; + } + + return strncmp(buf, "FAILED", 6); +} + +int +sendCommand(int so, char *cmd, char *buf) +{ + char buf1[80]; + int i, j, newline = 1; + int k = 0; + + if (write(so, cmd, strlen(cmd)) == -1) + { + return 0; + } + while (1) + { + if ((i = read(so, buf1, 80)) <= 0) + { + return 0; + } + for (j = 0; j < i; j++) + { + if (newline == 1 && buf1[j] == 'O') + { + newline = 2; + } + else if (newline == 2 && buf1[j] == 'K' && j == i - 1) + { + return 1; + } + else if (newline == 2) + { + buf[k] = 'O'; + k++; + buf[k] = buf1[j]; + k++; + newline = 0; + } + else if (buf1[j] == '\n' || buf1[j] == '\r') + { + buf[k] = buf1[j]; + k++; + newline = 1; + } + else + { + buf[k] = buf1[j]; + k++; + newline = 0; + } + } + } + return 1; +} + +int +get_maxadmin_param_tcp(char * hostname, char *user, char *password, char * cmd, char *param, char *result) +{ + + char buf[10240]; + char *port = (char *) "6603"; + int so; + + if ((so = connectMaxScale(hostname, port)) == -1) + { + return 1; + } + if (!authMaxScale(so, user, password)) + { + fprintf(stderr, "Failed to connect to MaxScale. " + "Incorrect username or password.\n"); + close(so); + return 1; + } + + sendCommand(so, cmd, buf); + + //printf("%s\n", buf); + + char * x = strstr(buf, param); + if (x == NULL ) + { + return 1; + } + //char f_field[100]; + int param_len = strlen(param); + int cnt = 0; + while (x[cnt + param_len] != '\n') + { + result[cnt] = x[cnt + param_len]; + cnt++; + } + result[cnt] = '\0'; + //sprintf(f_field, "%s %%s", param); + //sscanf(x, f_field, result); + close(so); + return 0; +} + +int +execute_maxadmin_command_tcp(char * hostname, char *user, char *password, char * cmd) +{ + + char buf[10240]; + char *port = (char *) "6603"; + int so; + + if ((so = connectMaxScale(hostname, port)) == -1) + { + return 1; + } + if (!authMaxScale(so, user, password)) + { + fprintf(stderr, "Failed to connect to MaxScale. " + "Incorrect username or password.\n"); + close(so); + return 1; + } + + sendCommand(so, cmd, buf); + + close(so); + return 0; +} + +int +execute_maxadmin_command_print_tcp(char * hostname, char *user, char *password, char * cmd) +{ + + char buf[10240]; + char *port = (char *) "6603"; + int so; + + if ((so = connectMaxScale(hostname, port)) == -1) + { + return 1; + } + if (!authMaxScale(so, user, password)) + { + fprintf(stderr, "Failed to connect to MaxScale. " + "Incorrect username or password.\n"); + close(so); + return 1; + } + + sendCommand(so, cmd, buf); + printf("%s\n", buf); + close(so); + return 0; +} diff --git a/maxscale-system-test/maxadmin_operations.h b/maxscale-system-test/maxadmin_operations.h new file mode 100644 index 000000000..a426d95fe --- /dev/null +++ b/maxscale-system-test/maxadmin_operations.h @@ -0,0 +1,106 @@ +#ifndef MAXADMIN_OPERATIONS_H +#define MAXADMIN_OPERATIONS_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#include + + +/** + * @brief Connect to the MaxScale server + * + * @param hostname The hostname to connect to + * @param port The port to use for the connection + * @return The connected socket or -1 on error + */ +int connectMaxScale(char *hostname, char *port); + +/** + * @brief Set IP address in socket structure in_addr + * + * @param a Pointer to a struct in_addr into which the address is written + * @param p The hostname to lookup + * @return 1 on success, 0 on failure + */ +int setipaddress(struct in_addr *a, char *p); + + +/** + * @brief Perform authentication using the maxscaled protocol conventions + * + * @param so The socket connected to MaxScale + * @param user The username to authenticate + * @param password The password to authenticate with + * @return Non-zero of succesful authentication + */ +int authMaxScale(int so, char *user, char *password); + +/** + * @brief Send a comamnd using the MaxScaled protocol, display the return data on standard output. + * + * Input terminates with a lien containing just the text OK + * + * @param so The socket connect to MaxScale + * @param cmd The command to send + * @return 0 if the connection was closed + */ +int sendCommand(int so, char *cmd, char *buf); + + +/** + * @brief Send a comamnd using the MaxScaled protocol, search for certain numeric parameter in MaxScaled output. + * + * Input terminates with a lien containing just the text OK + * + * @param user The username to authenticate + * @param password The password to authenticate with + * @param cmd The command to send + * @param param Parameter to find + * @param result Value of found parameter + * @return 0 if parameter is found + */ +int get_maxadmin_param_tcp(char *hostname, char *user, char *password, char *command, char *param, + char *result); + +/** + * @brief Send a comamnd using the MaxScaled protocol + * + * Input terminates with a line containing just the text OK + * + * @param user The username to authenticate + * @param password The password to authenticate with + * @param cmd The command to send + * @return 0 if parameter is found + */ +int execute_maxadmin_command_tcp(char * hostname, char *user, char *password, char * cmd); + +/** + * @brief Send a comamnd using the MaxScaled protocol, print results of stdout + * + * Input terminates with a line containing just the text OK + * + * @param user The username to authenticate + * @param password The password to authenticate with + * @param cmd The command to send + * @return 0 if parameter is found + */ +int execute_maxadmin_command_print_pcp(char * hostname, char *user, char *password, char * cmd); + +#endif // MAXADMIN_OPERATIONS_H diff --git a/maxscale-system-test/maxinfo.cpp b/maxscale-system-test/maxinfo.cpp new file mode 100644 index 000000000..fdf648cb5 --- /dev/null +++ b/maxscale-system-test/maxinfo.cpp @@ -0,0 +1,80 @@ +/** + * @file maxinfo.cpp maxinfo JSON listener test + * - sends 1000 'status' request to the listener + */ + + +#include +#include +#include "testconnections.h" + +#include +#include +#include "maxinfo_func.h" + +using namespace std; + +//const int N = 9; +//const char * resources[N] = {"variables", "status", "services", "listeners", "modules", "sessions", "clients", "servers", "eventTimes"}; +const int N = 8; +const char * resources[N] = {"variables", "status", "services", "listeners", "modules", "sessions", "clients", "servers"}; + +bool exit_flag = false; + +void *maxinfo_thread( void *ptr ); +int threads_num = 25; +TestConnections * Test; + +int main(int argc, char *argv[]) +{ + Test = new TestConnections(argc, argv); + int sleep_time = Test->smoke ? 30 : 1000; + + Test->set_timeout(sleep_time + 100); + + pthread_t thread1[threads_num]; + int iret1[threads_num]; + + int i; + for (i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, maxinfo_thread, NULL); + } + + sleep(sleep_time); + + exit_flag = true; + Test->set_timeout(120); + for (i = 0; i < threads_num; i++) + { + pthread_join(thread1[i], NULL); + } + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *maxinfo_thread( void *ptr ) +{ + char * result; + int ind; + + while (! exit_flag) + { + ind = rand() % N; + result = get_maxinfo(resources[ind], Test); + if (result != NULL) + { + Test->tprintf("Query %s, result: \n%s\n", resources[ind], result); + free(result); + } + else + { + Test->add_result(1, "Can't get result from maxinfo, query %s\n", resources[ind]); + } + } + +} diff --git a/maxscale-system-test/maxinfo.py b/maxscale-system-test/maxinfo.py new file mode 100755 index 000000000..fb4f18c51 --- /dev/null +++ b/maxscale-system-test/maxinfo.py @@ -0,0 +1,56 @@ +#!/usr/bin/python3 + +import http.client +import os +import json +import subprocess +import threading + +# Needs to be declared here to allow Python 3 modules to be used +def prepare_test(testname = "replication"): + subprocess.call(os.getcwd() + "/non_native_setup " + str(testname), shell=True) + +prepare_test("maxinfo.py") + +# Test all Maxinfo HTTP entry points +entry_points = ["/services", + "/listeners", + "/modules", + "/monitors", + "/sessions", + "/clients", + "/servers", + "/variables", + "/status", + "/event/times"] + +decoder = json.JSONDecoder() + +def test_thr(thrnum): + for r in range(0,10): + for i in entry_points: + data = "" + try: + conn = http.client.HTTPConnection(os.getenv("maxscale_network"), 8080) + conn.request("GET", i) + req = conn.getresponse() + data = req.read().decode('ascii') + json.loads(data) + except Exception as ex: + print("Thread", thrnum, "Exception (", ex, "):", data) + exit(1) + +thr = [] + +for i in range(0, 10): + thr.append(threading.Thread(target=test_thr, args=(i,))) + +print("Created", len(thr), "threads") + +for i in thr: + i.start() + +print("Started threads") + +for i in thr: + i.join() diff --git a/maxscale-system-test/maxinfo_func.cpp b/maxscale-system-test/maxinfo_func.cpp new file mode 100644 index 000000000..a41066132 --- /dev/null +++ b/maxscale-system-test/maxinfo_func.cpp @@ -0,0 +1,301 @@ + +#include +#include +#include "testconnections.h" + +#include +#include +#include +#include +#include +#include + +#include +#include "maxinfo_func.h" +#include +#include +#include + +using namespace std; + +#define PORT 8080 +#define USERAGENT "HTMLGET 1.1" + +int create_tcp_socket() +{ + int sock; + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) + { + perror("Can't create TCP socket"); + return 0; + } + return sock; +} + +char *get_ip(char *host) +{ + struct hostent *hent; + int iplen = 16; //XXX.XXX.XXX.XXX + char *ip = (char *)malloc(iplen + 1); + memset(ip, 0, iplen + 1); + if ((hent = gethostbyname(host)) == NULL) + { + herror("Can't get IP"); + return NULL; + } + if (inet_ntop(AF_INET, (void *)hent->h_addr_list[0], ip, iplen) == NULL) + { + perror("Can't resolve host"); + return NULL; + } + return ip; +} + +char *build_get_query(char *host, const char *page) +{ + char *query; + const char *getpage = page; + char *tpl = (char *) "GET /%s HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\n\r\n"; + if (getpage[0] == '/') + { + getpage = getpage + 1; + fprintf(stderr, "Removing leading \"/\", converting %s to %s\n", page, getpage); + } + // -5 is to consider the %s %s %s in tpl and the ending \0 + query = (char *)malloc(strlen(host) + strlen(getpage) + strlen(USERAGENT) + strlen(tpl) - 5); + sprintf(query, tpl, getpage, host, USERAGENT); + return query; +} + +char * get_maxinfo(const char * page, TestConnections* Test) +{ + struct sockaddr_in *remote; + int sock; + int tmpres; + char *ip; + char *get; + char buf[BUFSIZ + 1]; + + sock = create_tcp_socket(); + ip = get_ip(Test->maxscale_IP); + if (ip == NULL) + { + Test->add_result(1, "Can't get IP\n"); + return NULL; + } + remote = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in *)); + remote->sin_family = AF_INET; + tmpres = inet_pton(AF_INET, ip, (void *)(&(remote->sin_addr.s_addr))); + if ( tmpres < 0) + { + Test->add_result(1, "Can't set remote->sin_addr.s_addr\n"); + return NULL; + } + else if (tmpres == 0) + { + Test->add_result(1, "%s is not a valid IP address\n", ip); + return NULL; + } + remote->sin_port = htons(PORT); + + if (connect(sock, (struct sockaddr *)remote, sizeof(struct sockaddr)) < 0) + { + Test->add_result(1, "Could not connect\n"); + return NULL; + } + get = build_get_query(Test->maxscale_IP, page); + //Test->tprintf("Query is:\n<>\n%s<>\n", get); + + //Send the query to the server + int sent = 0; + while (sent < strlen(get)) + { + tmpres = send(sock, get + sent, strlen(get) - sent, 0); + if (tmpres == -1) + { + Test->add_result(1, "Can't send query\n"); + return NULL; + } + sent += tmpres; + } + //now it is time to receive the page + memset(buf, 0, sizeof(buf)); + + char* result = (char*)calloc(BUFSIZ, sizeof(char)); + size_t rsize = sizeof(buf); + while ((tmpres = recv(sock, buf, BUFSIZ, MSG_WAITALL)) > 0) + { + result = (char*)realloc(result, tmpres + rsize); + rsize += tmpres; + strcat(result, buf); + memset(buf, 0, tmpres); + } + if (tmpres < 0) + { + Test->add_result(1, "Error receiving data\n"); + return NULL; + } + + free(get); + free(remote); + free(ip); + close(sock); + + char * content = strstr(result, "["); + if (content == NULL) + { + Test->add_result(1, "Content not found\n"); + free(result); + return NULL; + } + + char * ret_content = (char*) calloc(strlen(content) + 1, sizeof(char)); + mempcpy(ret_content, content, strlen(content)); + free(result); + + return ret_content; + //return(result); +} + +char * read_sc(int sock) +{ + char buf[BUFSIZ + 1]; + int tmpres; + memset(buf, 0, sizeof(buf)); + + char *result = (char*)calloc(BUFSIZ, sizeof(char)); + size_t rsize = sizeof(buf); + while ((tmpres = recv(sock, buf, BUFSIZ, 0)) > 0) + { + result = (char*)realloc(result, tmpres + rsize); + rsize += tmpres; + //printf("%s", buf); + strcat(result, buf); + memset(buf, 0, tmpres); + } + return result; +} +int send_so(int sock, char * data) +{ + int tmpres; + int sent = 0; + while (sent < strlen(data)) + { + tmpres = send(sock, data + sent, strlen(data) - sent, 0); + if (tmpres == -1) + { + return -1; + } + sent += tmpres; + } + return 0; +} + +static char * bin2hex(const unsigned char *old, const size_t oldlen) +{ + char *result = (char*) malloc(oldlen * 2 + 1); + size_t i, j; + + for (i = j = 0; i < oldlen; i++) + { + result[j++] = hexconvtab[old[i] >> 4]; + result[j++] = hexconvtab[old[i] & 15]; + } + result[j] = '\0'; + return result; +} + +char * cdc_auth_srt(char * user, char * password) +{ + unsigned char sha1pass[20]; + char * str; + str = (char*) malloc(42 + strlen(user) * 2); + + unsigned char *password_u; + unsigned char *user_u; + password_u = (unsigned char*) malloc(strlen(password)); + user_u = (unsigned char*) malloc(strlen(user)); + memcpy((void*)password_u, (void*)password, strlen(password)); + memcpy((void*)user_u, (void*)user, strlen(user)); + + SHA1(password_u, strlen(password), sha1pass); + + //char * sha1pass_hex = (char *) "454ac34c2999aacfebc6bf5fe9fa1db9b596f625"; + + char * sha1pass_hex = bin2hex(sha1pass, 20); + printf("password %s, len %lu, password sha1: %s\n", password, strlen(password), sha1pass_hex); + + + char * user_hex = bin2hex(user_u, strlen(user)); + char * clmn_hex = bin2hex((unsigned char*)":", 1); + + sprintf(str, "%s%s%s", user_hex, clmn_hex, sha1pass_hex); + + free(clmn_hex); + free(user_hex); + free(sha1pass_hex); + free(user_u); + free(password_u); + + printf("%s\n", str); + return str; + +} + +int setnonblocking(int sock) +{ + int opts; + opts = fcntl(sock, F_GETFL); + if (opts < 0) + { + return -1; + } + opts = (opts | O_NONBLOCK); + if (fcntl(sock, F_SETFL, opts) < 0) + { + return -1; + } + return 0; +} + + +int get_x_fl_from_json(char * line, long long int * x1, long long int * fl) +{ + json_t *root; + json_error_t error; + + root = json_loads( line, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return 1; + } + + json_t * x_json = json_object_get(root, "x1"); + if (x_json == NULL) + { + return 1; + } + if ( !json_is_integer(x_json) ) + { + printf("x1 is not int, type is %d\n", json_typeof(x_json)); + return 1; + } + + *x1 = json_integer_value(x_json); + json_t * fl_json = json_object_get(root, "fl"); + if (fl_json == NULL) + { + return 1; + } + if ( !json_is_integer(fl_json) ) + { + printf("fl is not int\n"); + return 1; + } + + *fl = json_integer_value(fl_json); + json_decref(x_json); + json_decref(fl_json); + json_decref(root); +} diff --git a/maxscale-system-test/maxinfo_func.h b/maxscale-system-test/maxinfo_func.h new file mode 100644 index 000000000..a3a07a6ed --- /dev/null +++ b/maxscale-system-test/maxinfo_func.h @@ -0,0 +1,25 @@ +#ifndef MAXINFO_FUNC_H +#define MAXINFO_FUNC_H + +int create_tcp_socket(); +char *get_ip(char *host); +char *build_get_query(char *host, const char *page); + +/** +* @brief get_maxinfo does request to Maxinfo service and return response JSON +* @param page retrived info name +* @param Test TestConnection object +* @return response from Maxinfo +*/ +char * get_maxinfo(const char *page, TestConnections* Test); + +char * read_sc(int sock); +int send_so(int sock, char * data); +static char hexconvtab[] = "0123456789abcdef"; +static char* bin2hex(const unsigned char *old, const size_t oldlen); +char * cdc_auth_srt(char * user, char * password); +int setnonblocking(int sock); +int get_x_fl_from_json(char * line, long long int * x1, long long int * fl); + + +#endif // MAXINFO_FUNC_H diff --git a/maxscale-system-test/maxpython.py b/maxscale-system-test/maxpython.py new file mode 100644 index 000000000..254c13c32 --- /dev/null +++ b/maxscale-system-test/maxpython.py @@ -0,0 +1,89 @@ + +import sys +import subprocess +import os +import time +import jaydebeapi + +# Abstract SQL connection +class SQLConnection: + def __init__(self, port = '3306', host = '127.0.0.1', user = 'root', password = ''): + self.host = str(host) + self.port = str(port) + self.user = str(user) + self.password = str(password) + + # Connect to a server + def connect(self, options = ""): + try: + self.conn = jaydebeapi.connect("org.mariadb.jdbc.Driver", ["jdbc:mariadb://" + self.host + ":" + self.port + "/test?" + options, self.user, self.password],"./maxscale/java/mariadb-java-client-1.3.3.jar") + except Exception as ex: + print("Failed to connect to " + self.host + ":" + self.port + " as " + self.user + ":" + self.password) + print(unicode(ex)) + exit(1) + + # Start a transaction + def begin(self): + curs = self.conn.cursor() + curs.execute("BEGIN") + curs.close() + # Commit a transaction + def commit(self): + curs = self.conn.cursor() + curs.execute("COMMIT") + curs.close() + + # Query and test if the result matches the expected value if one is provided + def query(self, query, compare = None, column = 0): + curs = self.conn.cursor() + curs.execute(query) + return curs.fetchall() + + def query_and_compare(self, query, column): + data = self.query(query) + for row in data: + if str(row[column]) == compare: + return True + return False + + def disconnect(self): + self.conn.close() + + def query_and_close(self, query): + self.connect() + self.query(query) + self.disconnect() + +# Test environment abstraction +class MaxScaleTest: + def __init__(self, testname = "python_test"): + + self.testname = testname + prepare_test(testname) + + # MaxScale connections + self.maxscale = dict() + self.maxscale['rwsplit'] = SQLConnection(host = os.getenv("maxscale_IP"), port = "4006", user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.maxscale['rcmaster'] = SQLConnection(host = os.getenv("maxscale_IP"), port = "4008", user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.maxscale['rcslave'] = SQLConnection(host = os.getenv("maxscale_IP"), port = "4009", user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + + # Master-Slave nodes + self.repl = dict() + self.repl['node0'] = SQLConnection(host = os.getenv("node_000_network"), port = os.getenv("node_000_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.repl['node1'] = SQLConnection(host = os.getenv("node_001_network"), port = os.getenv("node_001_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.repl['node2'] = SQLConnection(host = os.getenv("node_002_network"), port = os.getenv("node_002_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.repl['node3'] = SQLConnection(host = os.getenv("node_003_network"), port = os.getenv("node_003_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + + # Galera nodes + self.galera = dict() + self.galera['node0'] = SQLConnection(host = os.getenv("galera_000_network"), port = os.getenv("galera_000_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.galera['node1'] = SQLConnection(host = os.getenv("galera_001_network"), port = os.getenv("galera_001_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.galera['node2'] = SQLConnection(host = os.getenv("galera_002_network"), port = os.getenv("galera_002_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + self.galera['node3'] = SQLConnection(host = os.getenv("galera_003_network"), port = os.getenv("galera_003_port"), user = os.getenv("maxscale_user"), password = os.getenv("maxscale_password")) + + def __del__(self): + subprocess.call(os.getcwd() + "/copy_logs.sh " + str(self.testname), shell=True) + +# Read test environment variables +def prepare_test(testname = "replication"): + subprocess.call(os.getcwd() + "/non_native_setup " + str(testname), shell=True) diff --git a/maxscale-system-test/maxscale-system-test.env b/maxscale-system-test/maxscale-system-test.env new file mode 100644 index 000000000..f8532fef2 --- /dev/null +++ b/maxscale-system-test/maxscale-system-test.env @@ -0,0 +1,232 @@ +# +# Run the following command in the terminal to configure your environment for testing: +# +# source ./maxscale-system-test.env +# + +# MySQL username (usually skysql) +export galera_password= +# MySQL password (usually skysql) +export galera_user= + +# The hostname of the node +export galera_000_hostname= +# SSH key to VM +export galera_000_keyfile= +# Command to kill VM +export galera_000_kill_vm_command= +# IP address to node +export galera_000_network= +# Port of MySQL instance +export galera_000_port= +# Private IP address if this is an AWS node, otherwise the external IP +export galera_000_private_ip= +# Command to start the database +export galera_000_start_db_command= +# Command to start VM +export galera_000_start_vm_command= +# Commadn to stop the database +export galera_000_stop_db_command= +# Username of the access user (usually vagrant) +export galera_000_whoami= + +# The hostname of the node +export galera_001_hostname= +# SSH key to VM +export galera_001_keyfile= +# Command to kill VM +export galera_001_kill_vm_command= +# IP address to node +export galera_001_network= +# Port of MySQL instance +export galera_001_port= +# Private IP address if this is an AWS node, otherwise the external IP +export galera_001_private_ip= +# Command to start the database +export galera_001_start_db_command= +# Command to start VM +export galera_001_start_vm_command= +# Commadn to stop the database +export galera_001_stop_db_command= +# Username of the access user (usually vagrant) +export galera_001_whoami= + +# The hostname of the node +export galera_002_hostname= +# SSH key to VM +export galera_002_keyfile= +# Command to kill VM +export galera_002_kill_vm_command= +# IP address to node +export galera_002_network= +# Port of MySQL instance +export galera_002_port= +# Private IP address if this is an AWS node, otherwise the external IP +export galera_002_private_ip= +# Command to start the database +export galera_002_start_db_command= +# Command to start VM +export galera_002_start_vm_command= +# Commadn to stop the database +export galera_002_stop_db_command= +# Username of the access user (usually vagrant) +export galera_002_whoami= + +# The hostname of the node +export galera_003_hostname= +# SSH key to VM +export galera_003_keyfile= +# Command to kill VM +export galera_003_kill_vm_command= +# IP address to node +export galera_003_network= +# Port of MySQL instance +export galera_003_port= +# Private IP address if this is an AWS node, otherwise the external IP +export galera_003_private_ip= +# Command to start the database +export galera_003_start_db_command= +# Command to start VM +export galera_003_start_vm_command= +# Commadn to stop the database +export galera_003_stop_db_command= +# Username of the access user (usually vagrant) +export galera_003_whoami= + +# MaxScale IP address +export maxscale_IP= +# Username for maxscale access (usually vagrant) +export maxscale_access_user= +# Location for binlog files +export maxscale_binlog_dir= +# Location of configuration file +export maxscale_cnf= +# MaxScale hostname +export maxscale_hostname= +# SSH key to maxscale +export maxscale_keyfile= +# Location of MaxScale logs +export maxscale_log_dir= +# MaxScale IP address +export maxscale_network= +# MySQL password for MaxScale (usually skysql) +export maxscale_password= +# MaxScale private IP address if this is an AWS node, otherwise the external IP +export maxscale_private_ip= +# SSH key to maxscale +export maxscale_sshkey= +# MySQL user for MaxScale (usually skysql) +export maxscale_user= +# MaxScale VM username (usually vagrant) +export maxscale_whoami= +# Location of the MDBCI tool +export mdbci_dir= + +# MySQL username (usually skysql) +export node_password= +# MySQL password (usually skysql) +export node_user= + +# The hostname of the node +export node_000_hostname= +# SSH key to VM +export node_000_keyfile= +# Command to kill VM +export node_000_kill_vm_command= +# IP address to node +export node_000_network= +# Port of MySQL instance +export node_000_port= +# Private IP address if this is an AWS node, otherwise the external IP +export node_000_private_ip= +# Command to start the database +export node_000_start_db_command= +# Command to start VM +export node_000_start_vm_command= +# Commadn to stop the database +export node_000_stop_db_command= +# Username of the access user (usually vagrant) +export node_000_whoami= + +# The hostname of the node +export node_001_hostname= +# SSH key to VM +export node_001_keyfile= +# Command to kill VM +export node_001_kill_vm_command= +# IP address to node +export node_001_network= +# Port of MySQL instance +export node_001_port= +# Private IP address if this is an AWS node, otherwise the external IP +export node_001_private_ip= +# Command to start the database +export node_001_start_db_command= +# Command to start VM +export node_001_start_vm_command= +# Commadn to stop the database +export node_001_stop_db_command= +# Username of the access user (usually vagrant) +export node_001_whoami= + +# The hostname of the node +export node_002_hostname= +# SSH key to VM +export node_002_keyfile= +# Command to kill VM +export node_002_kill_vm_command= +# IP address to node +export node_002_network= +# Port of MySQL instance +export node_002_port= +# Private IP address if this is an AWS node, otherwise the external IP +export node_002_private_ip= +# Command to start the database +export node_002_start_db_command= +# Command to start VM +export node_002_start_vm_command= +# Commadn to stop the database +export node_002_stop_db_command= +# Username of the access user (usually vagrant) +export node_002_whoami= + +# The hostname of the node +export node_003_hostname= +# SSH key to VM +export node_003_keyfile= +# Command to kill VM +export node_003_kill_vm_command= +# IP address to node +export node_003_network= +# Port of MySQL instance +export node_003_port= +# Private IP address if this is an AWS node, otherwise the external IP +export node_003_private_ip= +# Command to start the database +export node_003_start_db_command= +# Command to start VM +export node_003_start_vm_command= +# Commadn to stop the database +export node_003_stop_db_command= +# Username of the access user (usually vagrant) +export node_003_whoami= + +# Generic environment variables, no need to change these +export maxscale_access_sudo=sudo +export node_000_access_sudo=sudo +export node_001_access_sudo=sudo +export node_002_access_sudo=sudo +export node_003_access_sudo=sudo +export galera_000_access_sudo=sudo +export galera_001_access_sudo=sudo +export galera_002_access_sudo=sudo +export galera_003_access_sudo=sudo +export node_N=4 +export galera_N=4 +export revert_snapshot_command= +export smoke=yes +export ssl=no +export sysbench_dir=/tmp +export take_snapshot_command= +export maxadmin_password=mariadb +export new_dirs=yes diff --git a/maxscale-system-test/maxscale/java/CMakeLists.txt b/maxscale-system-test/maxscale/java/CMakeLists.txt new file mode 100644 index 000000000..e97461f6d --- /dev/null +++ b/maxscale-system-test/maxscale/java/CMakeLists.txt @@ -0,0 +1,30 @@ +# Function for declaring java tests +# +# name Name of the test +# src Test source files +# entry_point The entry point of the JAR file +# template The configuration template for this test +# +function(add_java_test name src entry_point template) + add_jar(${name} SOURCES ${src} ${MXS_JAR} + ENTRY_POINT ${entry_point} INCLUDE_JARS ${MXS_JAR} ${JDBC_JAR}) + add_test(NAME ${name} COMMAND java + -cp ${TEST_JARPATH}:${CMAKE_CURRENT_BINARY_DIR}/${name}.jar + ${entry_point} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + add_template(${name} ${template}) + add_dependencies(${name} maxscale_java) + set_property(TEST ${name} PROPERTY LABELS java) +endfunction() + +# Some constants that make changing the connector easier +set(JDBC_JAR ${CMAKE_CURRENT_SOURCE_DIR}/mariadb-java-client-1.5.4.jar CACHE INTERNAL "") +set(MXS_JAR ${CMAKE_CURRENT_BINARY_DIR}/maxscale_java.jar CACHE INTERNAL "") +set(TEST_JARPATH "${MXS_JAR}:${JDBC_JAR}" CACHE INTERNAL "") + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/MaxScaleConfiguration.java.in ${CMAKE_CURRENT_BINARY_DIR}/MaxScaleConfiguration.java @ONLY) +add_jar(maxscale_java SOURCES MaxScaleConnection.java MaxScaleConfiguration.java + INCLUDE_JARS mariadb-java-client-1.5.4.jar) +add_subdirectory(test1) +add_subdirectory(prep_stmt) +add_subdirectory(batch) diff --git a/maxscale-system-test/maxscale/java/MaxScaleConfiguration.java.in b/maxscale-system-test/maxscale/java/MaxScaleConfiguration.java.in new file mode 100644 index 000000000..4383b47c4 --- /dev/null +++ b/maxscale-system-test/maxscale/java/MaxScaleConfiguration.java.in @@ -0,0 +1,39 @@ +package maxscale.java; + +import java.io.File; + +/** + * MaxScale configuration class + * + * Configures MaxScale for testing + */ +public class MaxScaleConfiguration { + + private static final String TEST_DIR = "@CMAKE_SOURCE_DIR@"; + private static final String CONFIG_COMMAND = TEST_DIR + "/non_native_setup"; + private static final String LOGS_COMMAND = TEST_DIR + "/copy_logs.sh"; + private String test = null; + + public MaxScaleConfiguration(String test) throws Exception + { + this.test = test; + ProcessBuilder pb = new ProcessBuilder(CONFIG_COMMAND, test); + pb.inheritIO(); + pb.directory(new File(TEST_DIR)); + pb.environment().put("test_dir", TEST_DIR); + Process process = pb.start(); + process.waitFor(); + } + + public void close() { + try { + ProcessBuilder pb = new ProcessBuilder(LOGS_COMMAND, test); + pb.inheritIO(); + pb.directory(new File(TEST_DIR)); + Process process = pb.start(); + process.waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/maxscale-system-test/maxscale/java/MaxScaleConnection.java b/maxscale-system-test/maxscale/java/MaxScaleConnection.java new file mode 100644 index 000000000..a33842ad3 --- /dev/null +++ b/maxscale-system-test/maxscale/java/MaxScaleConnection.java @@ -0,0 +1,102 @@ +package maxscale.java; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * Simple MaxScale connection class + * + * Allows execution of queries to one of the MaxScale services configured for + * testing. + */ +public class MaxScaleConnection { + + private static String ip = null; + private static String user = null; + private static String password = null; + private static boolean smoke_test = false; + private Connection conn_rw = null, conn_master = null, conn_slave = null; + public static final int READWRITESPLIT_PORT = 4006; + public static final int READCONNROUTE_MASTER_PORT = 4008; + public static final int READCONNROUTE_SLAVE_PORT = 4009; + + public String getIp() { + return ip; + } + + public String getUser() { + return user; + } + + public String getPassword() { + return password; + } + + public Connection getConnRw() { + return conn_rw; + } + + public Connection getConnMaster() { + return conn_master; + } + + public Connection getConnSlave() { + return conn_slave; + } + + public void setConnRw(Connection conn) { + conn_rw = conn; + } + + public void setConnMaster(Connection conn) { + conn_master = conn; + } + + public void setConnSlave(Connection conn) { + conn_slave = conn; + } + + public MaxScaleConnection(String options) throws SQLException, Exception { + String s = System.getenv("smoke"); + smoke_test = (s != null && s.compareTo("yes") == 0); + + if ((ip = System.getenv("maxscale_IP")) == null || ip.length() == 0) { + throw new Exception("Missing environment variable 'maxscale_IP'."); + } + + if ((user = System.getenv("maxscale_user")) == null || user.length() == 0) { + throw new Exception("Missing environment variable 'maxscale_user'."); + } + + if ((password = System.getenv("maxscale_password")) == null || password.length() == 0) { + throw new Exception("Missing environment variable 'maxscale_password'."); + } + + System.out.println("IP: " + ip + " User: " + user + " Password: " + password); + + Class.forName("org.mariadb.jdbc.Driver"); + conn_rw = DriverManager.getConnection( + "jdbc:mariadb://" + ip + ":" + READWRITESPLIT_PORT + "/test?" + options, user, password); + + conn_master = DriverManager.getConnection( + "jdbc:mariadb://" + ip + ":" + READCONNROUTE_MASTER_PORT + "/test?" + options, user, password); + + conn_slave = DriverManager.getConnection( + "jdbc:mariadb://" + ip + ":" + READCONNROUTE_SLAVE_PORT + "/test?" + options, user, password); + } + + public MaxScaleConnection() throws SQLException, Exception { + this(""); + } + + public boolean isSmokeTest() { + return smoke_test; + } + + public void query(Connection connection, String query) throws SQLException { + Statement stmt = connection.createStatement(); + stmt.execute(query); + } +} diff --git a/maxscale-system-test/maxscale/java/batch/BatchInsert.java b/maxscale-system-test/maxscale/java/batch/BatchInsert.java new file mode 100644 index 000000000..567b9221a --- /dev/null +++ b/maxscale-system-test/maxscale/java/batch/BatchInsert.java @@ -0,0 +1,48 @@ +package maxscale.java.batch; + +import maxscale.java.MaxScaleConfiguration; +import maxscale.java.MaxScaleConnection; +import java.sql.Connection; +import java.sql.Statement; + +public class BatchInsert { + + public static void main(String[] args) { + boolean error = false; + try { + MaxScaleConfiguration config = new MaxScaleConfiguration("batchinsert"); + MaxScaleConnection maxscale = new MaxScaleConnection("useBatchMultiSendNumber=500"); + + try { + Connection connection = maxscale.getConnRw(); + Statement stmt = connection.createStatement(); + + stmt.execute("DROP TABLE IF EXISTS tt"); + stmt.execute("CREATE TABLE tt (d int)"); + + for (int i = 0; i < 150; i++) { + stmt.addBatch("INSERT INTO tt(d) VALUES (1)"); + + if (i % 3 == 0) { + stmt.addBatch("SET @test2='aaa'"); + } + } + + stmt.executeBatch(); + System.out.println("finished"); + + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + error = true; + } + config.close(); + + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + } + + if (error) { + System.exit(1); + } + } +} diff --git a/maxscale-system-test/maxscale/java/batch/CMakeLists.txt b/maxscale-system-test/maxscale/java/batch/CMakeLists.txt new file mode 100644 index 000000000..09f28b56a --- /dev/null +++ b/maxscale-system-test/maxscale/java/batch/CMakeLists.txt @@ -0,0 +1,2 @@ +add_java_test(batchinsert BatchInsert.java maxscale.java.batch.BatchInsert batchinsert) +set_tests_properties(batchinsert PROPERTIES TIMEOUT 300) diff --git a/maxscale-system-test/maxscale/java/mariadb-java-client-1.5.4.jar b/maxscale-system-test/maxscale/java/mariadb-java-client-1.5.4.jar new file mode 100644 index 0000000000000000000000000000000000000000..5f138e467a378b9e6c935413de057db83688e4e7 GIT binary patch literal 446997 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 z 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 z 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 threads = new ArrayList<>(); + + for (int i = 0; i < THREADS; i++) { + threads.add(new Thread(){ + public void run() { + + try { + Connection conn = DriverManager.getConnection(conn_str, user, password); + conn.setAutoCommit(false); + + for (int i = 0; i < test_iter; i++) { + conn.isValid(1); + + PreparedStatement ps_insert = conn.prepareStatement(PrepStmtTest.INSERT_SQL); + + ps_insert.setString(1, String.valueOf(i)); + ps_insert.executeUpdate(); + conn.commit(); + + conn.isValid(1); + + PreparedStatement ps_select = conn.prepareStatement(PrepStmtTest.SELECT_SQL); + ps_select.setInt(1, i); + ResultSet rset = ps_select.executeQuery(); + + conn.isValid(1); + + while (rset.next()) { + int r = rset.getInt(1); + String s = rset.getString(2); + if (i % 10 == 0) { + System.out.println("Result: " + String.valueOf(r) + " " + s); + } + } + } + conn.close(); + } catch (Exception ex) { + System.out.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + System.exit(1); + } + } + }); + } + + System.out.println("Starting " + String.valueOf(threads.size()) + " threads"); + + for (Thread a: threads) { + a.start(); + } + + for (Thread a: threads) { + a.join(); + } + + } catch (Exception ex) { + error = true; + System.out.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } + + config.close(); + } + catch (Exception ex) { + error = true; + System.out.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } + + if (error) { + System.exit(1); + } + } +} diff --git a/maxscale-system-test/maxscale/java/test1/CMakeLists.txt b/maxscale-system-test/maxscale/java/test1/CMakeLists.txt new file mode 100644 index 000000000..eba481941 --- /dev/null +++ b/maxscale-system-test/maxscale/java/test1/CMakeLists.txt @@ -0,0 +1 @@ +add_java_test(simplejavatest SimpleConnectorJTest.java maxscale.java.test1.SimpleConnectorJTest simplejavatest) diff --git a/maxscale-system-test/maxscale/java/test1/SimpleConnectorJTest.java b/maxscale-system-test/maxscale/java/test1/SimpleConnectorJTest.java new file mode 100644 index 000000000..429be2441 --- /dev/null +++ b/maxscale-system-test/maxscale/java/test1/SimpleConnectorJTest.java @@ -0,0 +1,69 @@ +package maxscale.java.test1; + +import maxscale.java.MaxScaleConfiguration; +import maxscale.java.MaxScaleConnection; + +public class SimpleConnectorJTest { + + public static final int RWSPLIT_PORT = 4006; + public static final int READCONN_MASTER = 4008; + public static final int READCONN_SLAVE = 4009; + public static final String DATABASE_NAME = "mytestdb"; + public static final String TABLE_NAME = "t1"; + public static final int ITERATIONS_NORMAL = 1500; + public static final int ITERATIONS_SMOKE = 150; + public static int test_rows = ITERATIONS_NORMAL; + + public static void main(String[] args) { + boolean error = false; + + try { + MaxScaleConfiguration config = new MaxScaleConfiguration("simplejavatest"); + MaxScaleConnection maxscale = new MaxScaleConnection(); + try { + + if (maxscale.isSmokeTest()) { + test_rows = ITERATIONS_SMOKE; + } + + System.out.println("Creating databases and tables.."); + maxscale.query(maxscale.getConnMaster(), "DROP DATABASE IF EXISTS " + DATABASE_NAME); + maxscale.query(maxscale.getConnMaster(), "CREATE DATABASE " + DATABASE_NAME); + maxscale.query(maxscale.getConnMaster(), "CREATE TABLE " + DATABASE_NAME + + "." + TABLE_NAME + "(id int primary key auto_increment, data varchar(128))"); + + System.out.println("Inserting " + test_rows + " values"); + for (int i = 0; i < test_rows; i++) { + maxscale.query(maxscale.getConnMaster(), + "INSERT INTO " + DATABASE_NAME + "." + TABLE_NAME + + "(data) VALUES (" + String.valueOf(System.currentTimeMillis()) + ")"); + } + + System.out.println("Querying " + test_rows / 10 + "rows " + test_rows + " times"); + for (int i = 0; i < test_rows; i++) { + maxscale.query(maxscale.getConnMaster(), + "SELECT * FROM " + DATABASE_NAME + "." + TABLE_NAME + + " LIMIT " + test_rows / 10); + } + + maxscale.query(maxscale.getConnMaster(), "DROP DATABASE IF EXISTS " + DATABASE_NAME); + + } catch (Exception ex) { + error = true; + System.out.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } + + config.close(); + + } catch (Exception ex) { + error = true; + System.out.println("Error: " + ex.getMessage()); + ex.printStackTrace(); + } + + if (error) { + System.exit(1); + } + } +} diff --git a/maxscale-system-test/maxscale_process_user.cpp b/maxscale-system-test/maxscale_process_user.cpp new file mode 100644 index 000000000..54b3fa10f --- /dev/null +++ b/maxscale-system-test/maxscale_process_user.cpp @@ -0,0 +1,30 @@ +/** + * @file maxscale_process_user.cpp bug143 maxscale_process_user check if Maxscale priocess is running as 'maxscale' + * + */ + + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(50); + char *user = Test->ssh_maxscale_output(false, "ps -FC maxscale|tail -n 1|cut -f 1 -d \" \""); + char *nl = user ? strchr(user, '\n') : NULL; + + if (nl) + { + *nl = '\0'; + } + + Test->tprintf("MaxScale is running as '%s'", user); + Test->add_result(strcmp(user, "maxscale"), "MaxScale process running as '%s' instead of 'maxscale'\n", user); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mm.cpp b/maxscale-system-test/mm.cpp new file mode 100644 index 000000000..0b61a64f6 --- /dev/null +++ b/maxscale-system-test/mm.cpp @@ -0,0 +1,170 @@ +/** + * @file mm test of multi master monitor + * + * - us 'mmmon' module as a monitor + * - reset master, stop slaves, stop all nodes + * - start 2 nodes + * - execute SET MASTER TO on node0 to point to node1 and on node1 to point to node0 + * - execute SET GLOBAL READ_ONLY=ON on node0 + * - check server status using maxadmin interface, expect Master on node1 and Slave on node0 + * - put data to DB using RWSplit, check data using RWSplit and directrly from backend nodes + * - block node0 (slave) + * - check server status using maxadmin interface, expect node0 Down + * - put data and check it + * - unblock node0 + * - block node1 (master) + * - check server status using maxadmin interface, expect node1 Down + * - execute SET GLOBAL READ_ONLY=OFF on node0 + * - unblock node0 + * - execute SET GLOBAL READ_ONLY=ON on node1 + * - check server status using maxadmin interface, expect Master on node0 and Slave on node1 + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int check_conf(TestConnections* Test, int blocked_node) +{ + int global_result = 0; + Test->set_timeout(60); + + Test->repl->connect(); + Test->connect_rwsplit(); + create_t1(Test->conn_rwsplit); + global_result += insert_into_t1(Test->conn_rwsplit, 4); + + printf("Sleeping to let replication happen\n"); + fflush(stdout); + Test->stop_timeout(); + sleep(30); + + for (int i = 0; i < 2; i++) + { + if ( i != blocked_node) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->set_timeout(100); + global_result += select_from_t1(Test->repl->nodes[i], 4); + } + } + Test->set_timeout(100); + printf("Checking data from rwsplit\n"); + fflush(stdout); + global_result += select_from_t1(Test->conn_rwsplit, 4); + global_result += execute_query(Test->conn_rwsplit, "DROP TABLE t1"); + + Test->repl->close_connections(); + mysql_close(Test->conn_rwsplit); + + Test->stop_timeout(); + return global_result; +} + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(120); + char maxadmin_result[1024]; + + Test->repl->set_repl_user(); + + Test->start_mm(); // first node - slave, second - master + + Test->set_timeout(120); + Test->get_maxadmin_param((char *) "show server server1", (char *) "Status:", maxadmin_result); + Test->tprintf("node0 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Slave, Running") == NULL ) + { + Test->add_result(1, "Node0 is not slave, status is %s\n", maxadmin_result); + } + Test->set_timeout(120); + Test->get_maxadmin_param((char *) "show server server2", (char *) "Status:", maxadmin_result); + Test->tprintf("node1 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Master, Running") == NULL ) + { + Test->add_result(1, "Node1 is not master, status is %s\n", maxadmin_result); + } + Test->set_timeout(120); + printf("Put some data and check\n"); + Test->add_result(check_conf(Test, 2), "Configuration broken\n"); + Test->set_timeout(120); + Test->tprintf("Block slave\n"); + Test->repl->block_node(0); + Test->stop_timeout(); + sleep(15); + Test->set_timeout(120); + Test->get_maxadmin_param((char *) "show server server1", (char *) "Status:", maxadmin_result); + printf("node0 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Down") == NULL ) + { + Test->add_result(1, "Node0 is not down, status is %s\n", maxadmin_result); + } + Test->set_timeout(120); + Test->tprintf("Put some data and check\n"); + Test->add_result(check_conf(Test, 0), "configuration broken\n"); + + Test->set_timeout(120); + Test->tprintf("Unlock slave\n"); + Test->repl->unblock_node(0); + sleep(15); + + Test->set_timeout(120); + Test->tprintf("Block master\n"); + Test->repl->block_node(1); + sleep(15); + Test->get_maxadmin_param((char *) "show server server2", (char *) "Status:", maxadmin_result); + printf("node1 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Down") == NULL ) + { + Test->add_result(1, "Node1 is not down, status is %s\n", maxadmin_result); + } + Test->tprintf("Make node 1 master\n"); + + Test->set_timeout(120); + Test->repl->connect(); + execute_query(Test->repl->nodes[0], (char *) "SET GLOBAL READ_ONLY=OFF"); + Test->repl->close_connections(); + + sleep(15); + Test->set_timeout(120); + Test->tprintf("Put some data and check\n"); + Test->add_result(check_conf(Test, 1), "configuration broken\n"); + + printf("Unlock slave\n"); + Test->repl->unblock_node(1); + sleep(15); + + Test->set_timeout(120); + printf("Make node 2 slave\n"); + Test->repl->connect(); + execute_query(Test->repl->nodes[1], (char *) "SET GLOBAL READ_ONLY=ON"); + Test->repl->close_connections(); + sleep(15); + + Test->set_timeout(120); + printf("Put some data and check\n"); + Test->add_result(check_conf(Test, 2), "Configuration broken\n"); + + Test->set_timeout(60); + Test->get_maxadmin_param((char *) "show server server2", (char *) "Status:", maxadmin_result); + printf("node1 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Slave, Running") == NULL ) + { + Test->add_result(1, "Node1 is not slave, status is %s\n", maxadmin_result); + } + Test->set_timeout(60); + Test->get_maxadmin_param((char *) "show server server1", (char *) "Status:", maxadmin_result); + Test->tprintf("node0 %s\n", maxadmin_result); + if (strstr(maxadmin_result, "Master, Running") == NULL ) + { + Test->add_result(1, "Node0 is not master, status is %s\n", maxadmin_result); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mm_mysqlmon.cpp b/maxscale-system-test/mm_mysqlmon.cpp new file mode 100644 index 000000000..e5e251808 --- /dev/null +++ b/maxscale-system-test/mm_mysqlmon.cpp @@ -0,0 +1,220 @@ +/** + * @file mm_mysqlmon.cpp MySQL Monitor Multi-master Test + * - Configure all servers into a multi-master ring with one slave + * - check status using Maxadmin 'show servers' and 'show monitor "MySQL Monitor"' + * - Set nodes 0 and 1 into read-only mode + * - repeat status check + * - Configure nodes 1 and 2 (server2 and server3) into a master-master pair, make node 0 a slave of node 1 and node 3 a slave of node 2 + * - repeat status check + * - Set node 1 into read-only mode + * - repeat status check + * - Create two distinct groups (server1 and server2 are masters for eache others and same for server3 and server4) + * - repeat status check + * - Set nodes 1 and 3 (server2 and server4) into read-only mode + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +void check_status(TestConnections *Test, const char *server, const char *status) +{ + char cmd[256]; + char maxadmin_result[1024]; + + sprintf(cmd, "show server %s", server); + Test->set_timeout(120); + Test->get_maxadmin_param(cmd, (char *) "Status:", maxadmin_result); + if (maxadmin_result == NULL) + { + Test->add_result(1, "maxadmin execution error\n"); + return; + } + if (strstr(maxadmin_result, status) == NULL ) + { + Test->add_result(1, "Test failed, server '%s' status is '%s', expected '%s'\n", server, maxadmin_result, + status); + } +} + +void check_group(TestConnections *Test, const char *server, const char *group) +{ + + char *output = Test->ssh_maxscale_output(true, "maxadmin show monitor MySQL-Monitor"); + + if (output == NULL) + { + Test->add_result(1, "maxadmin execution error\n"); + return; + } + + char *start = strstr(output, server); + if (start == NULL) + { + Test->add_result(1, "maxadmin execution error\n"); + return; + } + char *value = strstr(start, "Master group"); + if (value == NULL) + { + Test->add_result(1, "maxadmin execution error\n"); + return; + } + + value = strchr(value, ':') + 1; + while (isspace(*value)) + { + value++; + } + + char *end = value; + + while (!isspace(*end)) + { + end++; + } + + *end = '\0'; + + Test->add_result(strcmp(group, value), "Server '%s', expected group '%s', not '%s'", server, group, value); +} + +void change_master(TestConnections *Test, int slave, int master) +{ + execute_query(Test->repl->nodes[slave], "CHANGE MASTER TO master_host='%s', master_port=3306, " + "master_log_file='mar-bin.000001', master_log_pos=310, master_user='repl', master_password='repl';START SLAVE", + Test->repl->IP[master], Test->repl->user_name, Test->repl->password); +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->tprintf("Test 1 - Configure all servers into a multi-master ring with one slave"); + + Test->set_timeout(120); + Test->repl->execute_query_all_nodes("STOP SLAVE; RESET SLAVE ALL; RESET MASTER; SET GLOBAL read_only='OFF'"); + Test->repl->connect(); + change_master(Test, 0, 1); + change_master(Test, 1, 2); + change_master(Test, 2, 0); + change_master(Test, 3, 2); + + sleep(2); + + check_status(Test, "server1", "Master, Running"); + check_status(Test, "server2", "Master, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Slave, Running"); + check_group(Test, "server1", "1"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "1"); + check_group(Test, "server4", "0"); + + Test->tprintf("Test 2 - Set nodes 0 and 1 into read-only mode"); + + Test->set_timeout(120); + execute_query(Test->repl->nodes[0], "SET GLOBAL read_only='ON'"); + execute_query(Test->repl->nodes[1], "SET GLOBAL read_only='ON'"); + + sleep(2); + + check_status(Test, "server1", "Slave, Running"); + check_status(Test, "server2", "Slave, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Slave, Running"); + check_group(Test, "server1", "1"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "1"); + check_group(Test, "server4", "0"); + + Test->tprintf("Test 3 - Configure nodes 1 and 2 into a master-master pair, make node 0 " + "a slave of node 1 and node 3 a slave of node 2"); + + Test->set_timeout(120); + Test->repl->execute_query_all_nodes("STOP SLAVE; RESET SLAVE ALL; RESET MASTER;SET GLOBAL read_only='OFF'"); + Test->repl->connect(); + change_master(Test, 0, 1); + change_master(Test, 1, 2); + change_master(Test, 2, 1); + change_master(Test, 3, 2); + + sleep(2); + + check_status(Test, "server1", "Slave, Running"); + check_status(Test, "server2", "Master, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Slave, Running"); + check_group(Test, "server1", "0"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "1"); + check_group(Test, "server4", "0"); + + Test->tprintf("Test 4 - Set node 1 into read-only mode"); + + Test->set_timeout(120); + execute_query(Test->repl->nodes[1], "SET GLOBAL read_only='ON'"); + + sleep(2); + + check_status(Test, "server1", "Slave, Running"); + check_status(Test, "server2", "Slave, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Slave, Running"); + check_group(Test, "server1", "0"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "1"); + check_group(Test, "server4", "0"); + + Test->tprintf("Test 5 - Create two distinct groups"); + + Test->set_timeout(120); + Test->repl->execute_query_all_nodes("STOP SLAVE; RESET SLAVE ALL; RESET MASTER;SET GLOBAL read_only='OFF'"); + Test->repl->connect(); + change_master(Test, 0, 1); + change_master(Test, 1, 0); + change_master(Test, 2, 3); + change_master(Test, 3, 2); + + sleep(2); + + check_status(Test, "server1", "Master, Running"); + check_status(Test, "server2", "Master, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Master, Running"); + check_group(Test, "server1", "1"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "2"); + check_group(Test, "server4", "2"); + + + Test->tprintf("Test 6 - Set nodes 1 and 3 into read-only mode"); + + Test->set_timeout(120); + execute_query(Test->repl->nodes[1], "SET GLOBAL read_only='ON'"); + execute_query(Test->repl->nodes[3], "SET GLOBAL read_only='ON'"); + + sleep(2); + + check_status(Test, "server1", "Master, Running"); + check_status(Test, "server2", "Slave, Running"); + check_status(Test, "server3", "Master, Running"); + check_status(Test, "server4", "Slave, Running"); + check_group(Test, "server1", "1"); + check_group(Test, "server2", "1"); + check_group(Test, "server3", "2"); + check_group(Test, "server4", "2"); + + Test->repl->execute_query_all_nodes("STOP SLAVE; RESET SLAVE ALL; RESET MASTER;SET GLOBAL read_only='OFF';"); + Test->repl->connect(); + change_master(Test, 1, 0); + change_master(Test, 2, 0); + change_master(Test, 3, 0); + Test->repl->fix_replication(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mrm/config1.toml b/maxscale-system-test/mrm/config1.toml new file mode 100644 index 000000000..673e58dde --- /dev/null +++ b/maxscale-system-test/mrm/config1.toml @@ -0,0 +1,73 @@ +# config.toml +# Example replication-manager configuration file + +[Default] +hosts = "node-000:3306,node-001:3306" +user = "skysql:skysql" +rpluser = "skysql:skysql" +title = "Cluster01" +connect-timeout = 1 +prefmaster = "node-000:3306" +interactive = false +log-level=1 + +# +# These are the options that were changed, the rest are set to the default +# values of the replication-manager. +# +monitoring-ticker = 1 +autorejoin-semisync = false +failover-at-sync = false +switchover-at-sync = false + +# LOG +# --- + +logfile = "/var/log/replication-manager.log" +verbose = true + +# TOPOLOGY +# -------- + + +# Automatically rejoin a failed server to the current master +# Slaves will re enter with read-only + +readonly = true +failover-event-scheduler = false +failover-event-status = false + +# FAILOVER +# -------- + +# Timeout in seconds between consecutive monitoring +# check type can be tcp or agent +check-type = "tcp" +check-replication-filters = true +check-binlog-filters = true +check-replication-state = true + +# Failover after N failures detection +# Reset number of failure if server auto recover after N seconds +failcount = 1 +failcount-reset-time = 300 + +# Cancel failover if already N failover +# Cancel failover if last failover was N seconds before +# Cancel failover in semi-sync when one slave is not in sync +# Cancel failover when replication delay is more than N seconds +failover-limit = 0 +failover-time-limit = 0 +maxdelay = 30 + +# SWITCHOVER +# ---------- + +# In switchover Wait N milliseconds before killing long running transactions +# Cancel switchover if transaction running more than N seconds +# Cancel switchover if write query running more than N seconds +# Cancel switchover if one of the slaves is not synced based on GTID equality +wait-kill = 5000 +wait-trx = 10 +wait-write-query = 10 +gtidcheck = true diff --git a/maxscale-system-test/mrm/config2.toml b/maxscale-system-test/mrm/config2.toml new file mode 100644 index 000000000..ed5af2d08 --- /dev/null +++ b/maxscale-system-test/mrm/config2.toml @@ -0,0 +1,81 @@ +# config.toml +# Example replication-manager configuration file + +[Default] +hosts = "node-000:3306,node-001:3306,node-002:3306" +user = "skysql:skysql" +rpluser = "skysql:skysql" +title = "Cluster01" +connect-timeout = 1 +prefmaster = "node-000:3306" +interactive = false +log-level=1 + +# +# These are the options that were changed, the rest are set to the default +# values of the replication-manager. +# +monitoring-ticker = 1 +autorejoin = true +autorejoin-mysqldump = true +autorejoin-backup-binlog = false +autorejoin-semisync = false +autorejoin-flashback = false +failover-at-sync = false +switchover-at-sync = false +failover-falsepositive-heartbeat = false + +# This needs to point to the 10.2.4 release candidate binaries +mariadb-binary-path = "/usr/mariadb-10.2/bin/" + +# LOG +# --- + +logfile = "/var/log/replication-manager.log" +verbose = true + +# TOPOLOGY +# -------- + + +# Automatically rejoin a failed server to the current master +# Slaves will re enter with read-only + +readonly = true +failover-event-scheduler = false +failover-event-status = false + +# FAILOVER +# -------- + +# Timeout in seconds between consecutive monitoring +# check type can be tcp or agent +check-type = "tcp" +check-replication-filters = true +check-binlog-filters = true +check-replication-state = true + +# Failover after N failures detection +# Reset number of failure if server auto recover after N seconds +failcount = 1 +failcount-reset-time = 300 + +# Cancel failover if already N failover +# Cancel failover if last failover was N seconds before +# Cancel failover in semi-sync when one slave is not in sync +# Cancel failover when replication delay is more than N seconds +failover-limit = 0 +failover-time-limit = 0 +maxdelay = 30 + +# SWITCHOVER +# ---------- + +# In switchover Wait N milliseconds before killing long running transactions +# Cancel switchover if transaction running more than N seconds +# Cancel switchover if write query running more than N seconds +# Cancel switchover if one of the slaves is not synced based on GTID equality +wait-kill = 5000 +wait-trx = 10 +wait-write-query = 10 +gtidcheck = true diff --git a/maxscale-system-test/mxs1045.cpp b/maxscale-system-test/mxs1045.cpp new file mode 100644 index 000000000..1437ed8b7 --- /dev/null +++ b/maxscale-system-test/mxs1045.cpp @@ -0,0 +1,34 @@ +/** + * @file mxs1045.cpp Regression case for the bug "Defunct processes after maxscale have executed script during failover" + * - configure monitor: + * @verbatim +script=/bin/sh -c "echo hello world!" +events=master_down,server_down + + * @endverbatim + * - block one node + * - Check that script execution doesn't leave zombie processes + */ + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + + test.tprintf("Block master"); + test.repl->block_node(0); + + test.tprintf("Wait for monitor to see it"); + sleep(10); + + test.tprintf("Check that there are no zombies"); + + int res = test.ssh_maxscale(false, + "if [ \"`ps -ef|grep defunct|grep -v grep`\" != \"\" ]; then exit 1; fi"); + test.add_result(res, "Zombie processes were found"); + + test.repl->unblock_node(0); + + return test.global_result; +} diff --git a/maxscale-system-test/mxs1071_maxrows.cpp b/maxscale-system-test/mxs1071_maxrows.cpp new file mode 100644 index 000000000..c52a295b1 --- /dev/null +++ b/maxscale-system-test/mxs1071_maxrows.cpp @@ -0,0 +1,555 @@ +/** + * @file mxs1071_maxrows.cpp Test of Maxrows filter + * Initial filter configuration + @verbatim +[MaxRows] +type=filter +module=maxrows +max_resultset_rows=20 +max_resultset_size=9000000 +debug=3 + @endverbatim + * All the tests executes statemet, prepared statement or stored procedure and checks + * number of rows in the result sets (multiple result sets possible) + * + * Test 1 - max_allowed_packet limit is not hit, simple SELECTs, small table + * Test 2 - same queries, but bigger table - limit is hit in some cases + * Test 3 - stored procedure, limit is not hit, single result set + * Test 4 - stored procedure, limit is not hit, multiple result sets + * Test 5 - stored procedure, limit is not hit, multiple result sets + * Test 6 - stored procedure, limit is hit, multiple result sets + * Test 7 - stored procedure, limit is not hit, long blobs, multiple result sets + * Test 8 - stored procedure, limit is hit, long blobs, multiple result sets + * Test 9 - query non-existant table, expect proper error + * Test 10 - stored procedure, limit could be hit if executed until the end, + * multiple result sets, query non-existant table, expect proper error + * and result sets generated before error + * Test 11 - SET @a=4 - empty result set + * Test 12 - prepared statement, using mysql_stmt_* functions, limit is hit + * Test 12 is repeated using mysql_query() function + * Test 13 - same as Test 12, but limit is not hit + * Test 14 - prepared statement inside of store procedure, multiple result sets + * limit is not hit + * Test 15 - prepared statement inside of store procedure, multiple result sets + * limit is hit + * Test 16 - SELECT '' as 'A' limit 1 (empty result) + * Test 17 - multiple result sets with empty result, limit is not hit + * Test 18 - multiple result sets with empty result, exactly as a limit (20) + * (expect 20 result sets) + * Test 19 - multiple result sets with empty result, limit is hit + * Test 20 - SELECT long blobs, limit is not hit + * Test 21 - change max_resultset_size to lower values, SELECT long blobs, + * max_resultset_size limit is hit + */ + +#include +#include "testconnections.h" +#include "sql_t1.h" +#include "mariadb_func.h" +#include "blob_test.h" + +using namespace std; + +const char * test03_sql = + " CREATE PROCEDURE multi()\n" + "BEGIN\n" + "SELECT x1 FROM t1 LIMIT 2;\n" + "END"; + +const char * test04_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1;\n" + "SELECT x1 FROM t1 LIMIT 2;\n" + "SELECT 1,2,3; \n" + "END"; + +const char * test05_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1;\n" + "SELECT x1 FROM t1 LIMIT 8;\n" + "SELECT 1,2,3; \n" + "SELECT 1;" + "END"; + +const char * test06_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1;\n" + "SELECT x1 FROM t1 LIMIT 18;\n" + "SELECT 2; \n" + "SELECT 2;" + "END"; + +const char * test07_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1,2,3,4;\n" + "SELECT id, b from long_blob_table order by id desc limit 1;\n" + "SELECT id, b from long_blob_table order by id desc limit 4;\n" + "SELECT id, b from long_blob_table order by id desc limit 1;\n" + "SELECT id, b from long_blob_table order by id desc;\n" + "SELECT id, b from long_blob_table order by id desc;\n" + "SELECT 1;\n" + "END"; + +const char * test08_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1,2,3;\n" + "SELECT id, b, b from long_blob_table order by id desc limit 1;\n" + "SELECT 2;\n" + "SELECT id, b from long_blob_table order by id desc limit 4;\n" + "SELECT id, b from long_blob_table order by id desc limit 2;\n" + "SELECT 1;\n" + "SELECT 1;\n" + "SELECT x1 FROM t1 LIMIT 8;\n" + "SELECT 1;\n" + "SELECT 1,2,3,4;\n" + "END"; + +const char * test10_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1;\n" + "SELECT x1 FROM t1 limit 4;\n" + "select * from dual;\n" + "set @a=4;\n" + "SELECT 2;\n" + "SELECT * FROM t1;\n" + "END"; + +const char * test14_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1,3;\n" + "SET @table = 't1';\n" + "SET @s = CONCAT('SELECT * FROM ', @table, ' LIMIT 18');\n" + "PREPARE stmt1 FROM @s;\n" + "EXECUTE stmt1;\n" + "DEALLOCATE PREPARE stmt1;\n" + "SELECT 2,4,5;\n" + "END"; + +const char * test15_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT 1,3;\n" + "SET @table = 't1';\n" + "SET @s = CONCAT('SELECT * FROM ', @table, ' LIMIT 100');\n" + "PREPARE stmt1 FROM @s;\n" + "EXECUTE stmt1;\n" + "DEALLOCATE PREPARE stmt1;\n" + "SELECT 2,4,5;\n" + "END"; + +const char * test17_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT '' as 'A' limit 1;\n" + "SELECT '' as 'A' limit 10;\n" + "SELECT '' as 'A';\n" + "END"; + +const char * test18_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT '' as 'A' limit 1;\n" + "SELECT '' as 'A' limit 10;\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A' limit 1;\n" + "SELECT '' as 'A' limit 10;\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "END"; + +const char * test19_sql = + "CREATE PROCEDURE multi() BEGIN\n" + "SELECT '' as 'A' limit 1;\n" + "SELECT '' as 'A' limit 10;\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A' limit 1;\n" + "SELECT '' as 'A' limit 10;\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "SELECT '' as 'A';\n" + "END"; + +/** + * @brief compare_expected Execute sql and compare number of rows in every result set with expected values + * If number if result sets differs from expected value or number of rows in any result sey differs from + * given expected value this function calls Test->add_result + * @param Test TestConnections object + * @param sql SQL query to execute + * @param exp_i Expected number of result sets + * @param exp_rows Array of expected numbers of rows for every result set + * @return 0 in case of lack of error + */ +int compare_expected(TestConnections * Test, const char * sql, my_ulonglong exp_i, my_ulonglong exp_rows[]) +{ + my_ulonglong *rows = new my_ulonglong[30]; + my_ulonglong i; + + Test->set_timeout(10); + execute_query_num_of_rows(Test->conn_rwsplit, sql, rows, &i); + + Test->tprintf("Result sets number is %llu\n", i); + + if ( i != exp_i) + { + Test->add_result(1, "Number of result sets is %llu instead of %llu\n", i, exp_i); + return 1; + } + + for (my_ulonglong j = 0; j < i; j++) + { + Test->tprintf("For result set %llu number of rows is %llu\n", j, rows[j]); + if (rows[j] != exp_rows[j]) + { + Test->add_result(1, "For result set %llu number of rows is %llu instead of %llu\n", j, rows[j], exp_rows[j]); + return 1; + } + } + return 0; +} + +/** + * @brief compare_stmt_expected Execute prepared statement and compare number of rows in every result set with expected values + * This function uses mysql_stmt-* functions (COM_STMT_EXECUTE, COM_STMT_FETCH) + * @param Test TestConnections object + * @param stmt MYSQL_STMT prepared statement handler + * @param exp_i Expected number of result sets + * @param exp_rows Array of expected numbers of rows for every result set + * @return 0 in case of lack of error + */ +int compare_stmt_expected(TestConnections * Test, MYSQL_STMT * stmt, my_ulonglong exp_i, + my_ulonglong exp_rows[]) +{ + my_ulonglong *rows = new my_ulonglong[30]; + my_ulonglong i; + + Test->set_timeout(10); + execute_stmt_num_of_rows(stmt, rows, &i); + + Test->tprintf("Result sets number is %llu\n", i); + + if ( i != exp_i) + { + Test->add_result(1, "Number of result sets is %llu instead of %llu\n", i, exp_i); + return 1; + } + + for (my_ulonglong j = 0; j < i; j++) + { + Test->tprintf("For result set %llu number of rows is %llu\n", j, rows[j]); + if (rows[j] != exp_rows[j]) + { + Test->add_result(1, "For result set %llu number of rows is %llu instead of %llu\n", j, rows[j], exp_rows[j]); + return 1; + } + } + return 0; +} + +/** + * @brief err_check Print mysql_error() and mysql_errno and compare mysql_errno with given expected value + * @param Test TestConnections object + * @param expected_err Expected error code + */ +void err_check(TestConnections* Test, unsigned int expected_err) +{ + Test->tprintf("Error text '%s'' error code %d\n", mysql_error(Test->conn_rwsplit), + mysql_errno(Test->conn_rwsplit)); + if (mysql_errno(Test->conn_rwsplit) != expected_err) + { + Test->add_result(1, "Error code is not %d, it is %d\n", expected_err, mysql_errno(Test->conn_rwsplit)); + } +} + +int main(int argc, char *argv[]) +{ + + my_ulonglong *exp_rows = new my_ulonglong[30]; + MYSQL_STMT * stmt; + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->connect_rwsplit(); + + create_t1(Test->conn_rwsplit); + insert_into_t1(Test->conn_rwsplit, 1); + Test->stop_timeout(); + sleep(5); + + + Test->tprintf("**** Test 1 ****\n"); + + + exp_rows[0] = 16; + compare_expected(Test, (char *) "select * from t1", 1, exp_rows); + + exp_rows[0] = 16; + compare_expected(Test, (char *) "select * from t1 where fl=0", 1, exp_rows); + + exp_rows[0] = 10; + compare_expected(Test, (char *) "select * from t1 limit 10", 1, exp_rows); + + Test->set_timeout(60); + create_t1(Test->conn_rwsplit); + insert_into_t1(Test->conn_rwsplit, 3); + Test->stop_timeout(); + sleep(5); + + Test->tprintf("**** Test 2 ****\n"); + exp_rows[0] = 0; + compare_expected(Test, (char *) "select * from t1", 1, exp_rows); + + exp_rows[0] = 16; + compare_expected(Test, (char *) "select * from t1 where fl=0", 1, exp_rows); + + exp_rows[0] = 10; + compare_expected(Test, (char *) "select * from t1 limit 10", 1, exp_rows); + + Test->tprintf("**** Test 3 ****\n"); + exp_rows[0] = 2; + exp_rows[1] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test03_sql); + compare_expected(Test, "CALL multi()", 2, exp_rows); + + Test->tprintf("**** Test 4 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 2; + exp_rows[2] = 1; + exp_rows[3] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test04_sql); + compare_expected(Test, "CALL multi()", 4, exp_rows); + + Test->tprintf("**** Test 5 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 8; + exp_rows[2] = 1; + exp_rows[3] = 1; + exp_rows[4] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test05_sql); + compare_expected(Test, "CALL multi()", 5, exp_rows); + + Test->tprintf("**** Test 6 ****\n"); + exp_rows[0] = 0; + + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test06_sql); + compare_expected(Test, "CALL multi()", 1, exp_rows); + + + Test->tprintf("LONGBLOB: Trying send data via RWSplit\n"); + Test->try_query(Test->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000"); + Test->stop_timeout(); + Test->repl->connect(); + //test_longblob(Test, Test->conn_rwsplit, (char *) "LONGBLOB", 512 * 1024 / sizeof(long int), 17 * 2, 25); + test_longblob(Test, Test->repl->nodes[0], (char *) "LONGBLOB", 512 * 1024 / sizeof(long int), 17 * 2, 5); + Test->repl->close_connections(); + + + Test->tprintf("**** Test 7 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 1; + exp_rows[2] = 4; + exp_rows[3] = 1; + exp_rows[4] = 5; + exp_rows[5] = 5; + exp_rows[6] = 1; + exp_rows[7] = 0; + + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test07_sql); + compare_expected(Test, "CALL multi()", 8, exp_rows); + + Test->tprintf("**** Test 8 ****\n"); + exp_rows[0] = 0; + + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test08_sql); + compare_expected(Test, "CALL multi()", 1, exp_rows); + + Test->tprintf("**** Test 9 ****\n"); + exp_rows[0] = 0; + + compare_expected(Test, "SELECT * FROM dual", 0, exp_rows); + err_check(Test, 1096); + + Test->tprintf("**** Test 10 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 4; + + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test10_sql); + compare_expected(Test, "CALL multi()", 2, exp_rows); + + err_check(Test, 1096); + + Test->tprintf("**** Test 11 ****\n"); + exp_rows[0] = 0; + + compare_expected(Test, "SET @a=4;", 1, exp_rows); + err_check(Test, 0); + + // Prepared statements + + Test->tprintf("**** Test 12 (C++) ****\n"); + exp_rows[0] = 0; + + stmt = mysql_stmt_init(Test->conn_rwsplit); + if (stmt == NULL) + { + Test->add_result(1, "stmt init error: %s\n", mysql_stmt_error(stmt)); + } + char *stmt1 = (char *) "SELECT * FROM t1"; + Test->add_result(mysql_stmt_prepare(stmt, stmt1, strlen(stmt1)), "Error preparing stmt: %s\n", + mysql_stmt_error(stmt)); + + compare_stmt_expected(Test, stmt, 1, exp_rows); + + mysql_stmt_close(stmt); + + + + Test->tprintf("**** Test 12 (MariaDB command line client) ****\n"); + exp_rows[0] = 0; + Test->try_query(Test->conn_rwsplit, "SET @table = 't1'"); + Test->try_query(Test->conn_rwsplit, "SET @s = CONCAT('SELECT * FROM ', @table)"); + Test->try_query(Test->conn_rwsplit, "PREPARE stmt1 FROM @s"); + compare_expected(Test, "EXECUTE stmt1", 1, exp_rows); + Test->try_query(Test->conn_rwsplit, "DEALLOCATE PREPARE stmt1"); + + + Test->tprintf("**** Test 13 (C++)****\n"); + exp_rows[0] = 10; + exp_rows[1] = 0; + stmt = mysql_stmt_init(Test->conn_rwsplit); + if (stmt == NULL) + { + Test->add_result(1, "stmt init error: %s\n", mysql_stmt_error(stmt)); + } + char *stmt2 = (char *) "SELECT * FROM t1 LIMIT 10"; + Test->add_result(mysql_stmt_prepare(stmt, stmt2, strlen(stmt2)), "Error preparing stmt: %s\n", + mysql_stmt_error(stmt)); + compare_stmt_expected(Test, stmt, 1, exp_rows); + mysql_stmt_close(stmt); + + Test->tprintf("**** Test 13 (MariaDB command line client) ****\n"); + Test->try_query(Test->conn_rwsplit, "SET @table = 't1'"); + Test->try_query(Test->conn_rwsplit, "SET @s = CONCAT('SELECT * FROM ', @table, ' LIMIT 10')"); + Test->try_query(Test->conn_rwsplit, "PREPARE stmt1 FROM @s"); + compare_expected(Test, "EXECUTE stmt1", 1, exp_rows); + Test->try_query(Test->conn_rwsplit, "DEALLOCATE PREPARE stmt1"); + + Test->tprintf("**** Test 14 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 18; + exp_rows[2] = 1; + exp_rows[3] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test14_sql); + compare_expected(Test, "CALL multi()", 4, exp_rows); + + Test->tprintf("**** Test 15 ****\n"); + exp_rows[0] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test15_sql); + compare_expected(Test, "CALL multi()", 1, exp_rows); + + Test->tprintf("**** Test 16 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 0; + compare_expected(Test, "SELECT '' as 'A' limit 1;", 1, exp_rows); + + Test->tprintf("**** Test 17 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 1; + exp_rows[2] = 1; + exp_rows[3] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test17_sql); + compare_expected(Test, "CALL multi()", 4, exp_rows); + + Test->tprintf("**** Test 18 ****\n"); + exp_rows[0] = 1; + exp_rows[1] = 1; + exp_rows[2] = 1; + exp_rows[3] = 1; + exp_rows[4] = 1; + exp_rows[5] = 1; + exp_rows[6] = 1; + exp_rows[7] = 1; + exp_rows[8] = 1; + exp_rows[9] = 1; + exp_rows[10] = 1; + exp_rows[11] = 1; + exp_rows[12] = 1; + exp_rows[13] = 1; + exp_rows[14] = 1; + exp_rows[15] = 1; + exp_rows[16] = 1; + exp_rows[17] = 1; + exp_rows[18] = 1; + exp_rows[19] = 1; + exp_rows[20] = 0; + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test18_sql); + compare_expected(Test, "CALL multi()", 21, exp_rows); + + Test->tprintf("**** Test 19 ****\n"); + exp_rows[0] = 0; + + Test->try_query(Test->conn_rwsplit, "DROP PROCEDURE IF EXISTS multi"); + Test->try_query(Test->conn_rwsplit, test19_sql); + compare_expected(Test, "CALL multi()", 1, exp_rows); + + Test->tprintf("**** Test 20 ****\n"); + exp_rows[0] = 2; + exp_rows[1] = 0; + Test->try_query(Test->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000"); + compare_expected(Test, "SELECT * FROM long_blob_table limit 2;", 1, exp_rows); + err_check(Test, 0); + + Test->close_rwsplit(); + + Test->ssh_maxscale(true, + "sed -i \"s/max_resultset_size=900000000/max_resultset_size=9000000/\" /etc/maxscale.cnf"); + Test->set_timeout(100); + Test->restart_maxscale(); + + Test->connect_rwsplit(); + + Test->tprintf("**** Test 21 ****\n"); + exp_rows[0] = 0; + Test->try_query(Test->conn_rwsplit, "SET GLOBAL max_allowed_packet=10000000000"); + compare_expected(Test, "SELECT * FROM long_blob_table limit 1;", 1, exp_rows); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + + return rval; + +} diff --git a/maxscale-system-test/mxs1073_binlog_enc.cpp b/maxscale-system-test/mxs1073_binlog_enc.cpp new file mode 100644 index 000000000..d8b9f640c --- /dev/null +++ b/maxscale-system-test/mxs1073_binlog_enc.cpp @@ -0,0 +1,247 @@ +/** + * @file mxs1073_binlog_enc.cpp Test binlog router setup with binlogs encryption + * - configure binlog router with follwoing options + @verbatim + encrypt_binlog=1,encryption_key_file=/etc/mariadb_binlog_keys.txt,encryption_algorithm=aes_cbc + @endverbatim + * - put some date to Master + * - check that all slave have the same data + * - 'maxbinlogcheck' against Maxscale binlog file + * - check 'maxbinlogcheck' output for lack of errors and presence of 'Decrypting binlog file with algorithm' message + * - try remote access to Maxscale binlog with 'mysqlbinlog' + * - copy Maxscale binlogs to Master and check output of 'show binary logs' before and after copying + * (expect same file name and size, but different checksums) + */ + + +#include +#include +#include "testconnections.h" +#include "test_binlog_fnc.h" + +/** + * @brief get_first_binlog_file Get name, size and checksum of first binlog file from 'show binary logs' output list + * @param Test TestConnections object + * @param name string for file name + * @param size variable for file size + * @param checksum Pointer to checksum string + * @return 0 in case of success + */ +int get_first_binlog_file(TestConnections * Test, char * name, long long unsigned *size, char ** checksum) +{ + char size_str[64]; + char cmd[256]; + int res = 0; + int exit_code; + res = find_field(Test->repl->nodes[0], (char *) "SHOW BINARY LOGS", (char *) "Log_name", name); + res += find_field(Test->repl->nodes[0], (char *) "SHOW BINARY LOGS", (char *) "File_size", size_str); + + sscanf(size_str, "%llu", size); + sprintf(cmd, "sha1sum /var/lib/mysql/%s | cut -f 1 -d \" \"", name); + + *checksum = Test->repl->ssh_node_output(0, cmd, true, &exit_code); + if (exit_code != 0) + { + res++; + } + + Test->tprintf("First master binlog file:\nname: '%s'\nsize: %llu\nchecksum: %s\n", + name, + *size, + *checksum + ); + + return res; +} + +int main(int argc, char *argv[]) +{ + + printf("ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!\n" + "ERROR! ERROR!\n" + "ERROR! This test require file key management plugin! ERROR!\n" + "ERROR! ERROR!\n" + "ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!ERROR!\n"); + return 1; + + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(1000); + char str1[1024]; + char str2[1024]; + char * alg; + char * alg1 = (char *) "aes_cbc"; + char * alg2 = (char *) "aes_ctr"; + + Test->tprintf("%s %s\n", Test->test_name, argv[1]); + + if (strcmp(Test->test_name, "mxs1073_binlog_enc_aes_ctr") == 0) + { + alg = alg2; + } + else + { + alg = alg1; + } + + int i; + + Test->repl->connect(); + Test->try_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1"); + + + Test->tprintf("Coping encription config .cnf files to all nodes \n"); + sprintf(str1, "%s/binlog_enc_%s.cnf", Test->test_dir, alg); + sprintf(str2, "%s/mariadb_binlog_keys.txt", Test->test_dir); + for (i = 0; i < Test->repl->N; i++) + { + Test->repl->copy_to_node(str1, (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/binlog_enc*.cnf /etc/my.cnf.d/", true); + + Test->repl->copy_to_node(str2, (char *) "~/", i); + Test->repl->ssh_node(i, (char *) "cp ~/mariadb_binlog_keys.txt /etc/", true); + } + + Test->copy_to_maxscale(str2, (char *) "~/"); + Test->ssh_maxscale(true, "cp ~/mariadb_binlog_keys.txt /etc/"); + + Test->start_binlog(); + + Test->repl->connect(); + + Test->tprintf("Put some data to DB\n"); + Test->set_timeout(100); + create_t1(Test->repl->nodes[0]); + Test->add_result(insert_into_t1(Test->repl->nodes[0], 4), "Data inserting to t1 failed\n"); + Test->stop_timeout(); + Test->tprintf("Sleeping to let replication happen\n"); + sleep(60); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->set_timeout(100); + Test->add_result(select_from_t1(Test->repl->nodes[i], 4), "Selecting from t1 failed\n"); + Test->stop_timeout(); + } + + Test->tprintf("Flush logs\n"); + execute_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + + Test->tprintf("Running 'maxbinlogcheck' against Maxscale binlog file\n"); + char * maxscale_binlogcheck_output = Test->ssh_maxscale_output(true, + "maxbinlogcheck -M -K /etc/mariadb_binlog_keys.txt -H /var/lib/maxscale/Binlog_Service/mar-bin.000001 --aes_algo=%s 2> 1", + alg); + //puts(maxscale_binlogcheck_output); + if (strstr(maxscale_binlogcheck_output, "error") != NULL) + { + Test->add_result(1, "Errors in the maxbinlogcheck output:\n%s\n", maxscale_binlogcheck_output); + } + + sprintf(str1, "Decrypting binlog file with algorithm: %s", alg); + if (strstr(maxscale_binlogcheck_output, str1) == NULL) + { + Test->add_result(1, "No '%s' in the maxbinlogcheck output:\n%s\n", str1, maxscale_binlogcheck_output); + } + + sprintf(str1, + "mysqlbinlog -R -h %s -P %d -u%s -p%s mar-bin.000001 --stop-position=60000", + Test->maxscale_IP, Test->binlog_port, Test->maxscale_user, Test->maxscale_password); + Test->tprintf("running mysqlbinlog on node_000 to connecto Maxscale: %s\n", str1); + int exit_code; + char * mysql_binlog_connect_output = Test->repl->ssh_node_output(0, str1, false, &exit_code); + Test->add_result(exit_code, "Remote access to Maxscale binlog failed"); + sprintf(str1, "LOGS/%s/mysql_binlog_connect_output", Test->test_name); + FILE *f = fopen(str1, "wt"); + fprintf(f, "%s", mysql_binlog_connect_output); + fclose(f); + + Test->tprintf("Checking binlog files on master\n"); + long long unsigned size_before; + long long unsigned size_after; + long long unsigned size_after_restart; + char * checksum_before; + char * checksum_after; + char * checksum_after_restart; + char name_before[256]; + char name_after[256]; + char name_after_restart[256]; + + Test->add_result(get_first_binlog_file(Test, name_before, &size_before, &checksum_before), + "Error getting binlog name and size\n"); + + Test->tprintf("Copying binlogs from Maxscale to Master\n"); + system("rm -rf binlogs"); + system("mkdir binlogs"); + Test->copy_from_maxscale((char *) "/var/lib/maxscale/Binlog_Service/*", (char *) "binlogs/"); + Test->repl->ssh_node(0, "rm -rf binlogs", true); + Test->repl->copy_to_node("-r binlogs", "./", 0); + Test->repl->ssh_node(0, "chown mysql:mysql binlogs/*", true); + + + //Test->repl->ssh_node(0, "rm /var/lib/mysql/mar-bin*", true); + Test->repl->ssh_node(0, "cp binlogs/* /var/lib/mysql/", true); + sleep(5); + Test->tprintf("Checking binlog files on master after copying binlogs from Maxscale\n"); + Test->add_result(get_first_binlog_file(Test, name_after, &size_after, &checksum_after), + "Error getting binlog name and size\n"); + + Test->repl->close_connections(); + + if (size_before != size_after) + { + Test->add_result(1, "Master binlog file size after copying Maxscale binlogs to Master is different\n"); + } + if (strcmp(name_before, name_after) != 0) + { + Test->add_result(1, "Master binlog file name after copying Maxscale binlogs to Master is different\n"); + } + if (strcmp(checksum_before, checksum_after) == 0) + { + Test->add_result(1, + "Master binlog file checksum after copying Maxscale binlogs to Master is the same. Probably binlog copying error different\n"); + } + + Test->repl->stop_node(0); + Test->repl->start_node(0, (char *) ""); + sleep(5); + + Test->tprintf("Checking binlog files on master after copying binlogs from Maxscale and Master restart\n"); + Test->repl->connect(); + Test->add_result(get_first_binlog_file(Test, name_after_restart, &size_after_restart, + &checksum_after_restart), + "Error getting binlog name and size\n"); + Test->repl->close_connections(); + + if (size_before != size_after_restart) + { + Test->add_result(1, + "Master binlog file size after copying Maxscale binlogs to Master and restart is different\n"); + } + if (strcmp(name_before, name_after_restart) != 0) + { + Test->add_result(1, + "Master binlog file name after copying Maxscale binlogs to Master and restart is different\n"); + } + if (strcmp(checksum_after_restart, checksum_after) != 0) + { + Test->add_result(1, "Master binlog file checksum after Master restart is different\n"); + } + + + // clean up + Test->tprintf("Cleaning up nodes and restart replication\n"); + Test->repl->stop_nodes(); + for (i = 0; i < Test->repl->N; i++) + { + Test->repl->ssh_node(i, (char *) "rm /etc/my.cnf.d/binlog_enc*.cnf", true); + Test->repl->ssh_node(i, (char *) "rm /etc/mariadb_binlog_keys.txt", true); + } + Test->repl->start_replication(); + + + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs1110_16mb.cpp b/maxscale-system-test/mxs1110_16mb.cpp new file mode 100644 index 000000000..7788b7116 --- /dev/null +++ b/maxscale-system-test/mxs1110_16mb.cpp @@ -0,0 +1,54 @@ +/** + * @file mxs1110_16mb.cpp - trying to use LONGBLOB with > 16 mb data blocks + * - try to insert large LONGBLOB via RWSplit in blocks > 16mb + * - read data via RWsplit, ReadConn master, ReadConn slave, compare with inserted data + */ + + +#include "testconnections.h" +#include "blob_test.h" +#include "fw_copy_rules.h" + +int main(int argc, char *argv[]) +{ + TestConnections::skip_maxscale_start(true); + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + int chunk_size = 2500000; + int chunk_num = 5; + + Test->copy_to_maxscale("./masking/masking_user/masking_rules.json", "~/"); + + Test->copy_to_maxscale("./cache/cache_basic/cache_rules.json", "~/"); + + copy_rules(Test, (char *) "rules2", "./fw/"); + + Test->start_maxscale(); + + Test->repl->execute_query_all_nodes( (char *) "set global max_allowed_packet=100000000"); + + Test->connect_maxscale(); + Test->repl->connect(); + Test->tprintf("LONGBLOB: Trying send data via RWSplit\n"); + test_longblob(Test, Test->conn_rwsplit, (char *) "LONGBLOB", chunk_size, chunk_num, 2); + Test->repl->close_connections(); + Test->close_maxscale_connections(); + + Test->connect_maxscale(); + Test->tprintf("Checking data via RWSplit\n"); + check_longblob_data(Test, Test->conn_rwsplit, chunk_size, chunk_num, 2); + Test->tprintf("Checking data via ReadConn master\n"); + check_longblob_data(Test, Test->conn_master, chunk_size, chunk_num, 2); + Test->tprintf("Checking data via ReadConn slave\n"); + check_longblob_data(Test, Test->conn_slave, chunk_size, chunk_num, 2); + Test->close_maxscale_connections(); + + MYSQL * conn_galera = open_conn(4016, Test->maxscale_IP, Test->maxscale_user, + Test->maxscale_password, Test->ssl); + mysql_close(conn_galera); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs1123.cpp b/maxscale-system-test/mxs1123.cpp new file mode 100644 index 000000000..19e32bbf6 --- /dev/null +++ b/maxscale-system-test/mxs1123.cpp @@ -0,0 +1,23 @@ +/** + * MXS-1123: connect_timeout setting causes frequent disconnects + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + test.connect_maxscale(); + + test.tprintf("Waiting one second between queries, all queries should succeed"); + + sleep(1); + test.try_query(test.conn_rwsplit, "select 1"); + sleep(1); + test.try_query(test.conn_master, "select 1"); + sleep(1); + test.try_query(test.conn_slave, "select 1"); + + test.close_maxscale_connections(); + return test.global_result; +} diff --git a/maxscale-system-test/mxs118.cpp b/maxscale-system-test/mxs118.cpp new file mode 100644 index 000000000..56db9e613 --- /dev/null +++ b/maxscale-system-test/mxs118.cpp @@ -0,0 +1,27 @@ +/** + * @file mxs118.cpp bug mxs118 regression case ("Two monitors loaded at the same time result into not working installation") + * + * - Configure two monitors using same backend serves + * - try to connect to maxscale + * - check logs for warning + */ + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->connect_maxscale(); + + Test->check_log_err((char *) "Multiple monitors are monitoring server", true); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs127.cpp b/maxscale-system-test/mxs127.cpp new file mode 100644 index 000000000..dffd54bda --- /dev/null +++ b/maxscale-system-test/mxs127.cpp @@ -0,0 +1,51 @@ +/** + * @file mxs127.cpp - bug mxs-127 regression case ("disable_sescmd_history causes MaxScale to crash under load") + * - execute set @test=%d 10000 times against RWSplit, ReadConn Master and ReadConn Slave + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + int i; + char sql[256]; + + Test->connect_maxscale(); + + Test->tprintf("RWSplit: Executing set @test=i 10000 times\n"); + for (i = 0; i < 10000; i++) + { + Test->set_timeout(5); + sprintf(sql, "set @test=%d", i); + Test->try_query(Test->conn_rwsplit, sql); + } + Test->tprintf("done!\n"); + + printf("ReadConn Master: Executing set @test=i 10000 times\n"); + for (i = 0; i < 10000; i++) + { + Test->set_timeout(5); + sprintf(sql, "set @test=%d", i); + Test->try_query(Test->conn_master, sql); + } + Test->tprintf("done!\n"); + + Test->tprintf("ReadConn Slave: Executing set @test=i 10000 times\n"); + for (i = 0; i < 10000; i++) + { + Test->set_timeout(5); + sprintf(sql, "set @test=%d", i); + Test->try_query(Test->conn_slave, sql); + } + Test->tprintf("done!\n"); + + Test->close_maxscale_connections(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs244_prepared_stmt_loop.cpp b/maxscale-system-test/mxs244_prepared_stmt_loop.cpp new file mode 100644 index 000000000..31294fe83 --- /dev/null +++ b/maxscale-system-test/mxs244_prepared_stmt_loop.cpp @@ -0,0 +1,60 @@ +/** + * @file mxs244_prepared_stmt_loop.cpp mxs244_prepared_stmt_loop executed following statements in the loop against all routers: + * @verbatim +SET NAMES "UTF8"; +PREPARE s1 FROM 'SHOW GLOBAL STATUS WHERE variable_name = ?'; +SET @a = "Com_stmt_prepare"; +EXECUTE s1 USING @a; +PREPARE s1 FROM 'SHOW GLOBAL STATUS WHERE variable_name = ?'; +SET @a = "Com_stmt_close"; +EXECUTE s1 USING @a; +@endverbatim + */ + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + long unsigned iterations = (Test->smoke) ? 1000 : 25000; + int r = (Test->smoke) ? 1 : 3; + + Test->set_timeout(5); + Test->repl->connect(); + Test->connect_maxscale(); + MYSQL * router[3]; + router[0] = Test->conn_rwsplit; + router[1] = Test->conn_master; + router[2] = Test->conn_slave; + + for (int ir = 0; ir < r; ir++) + { + Test->tprintf("Trying simple prepared statements in the loop, router %d\n", ir); + for (long unsigned i = 0; i < iterations; i++) + { + Test->set_timeout(10); + Test->try_query(router[ir], (char *) "SET NAMES \"UTF8\""); + Test->try_query(router[ir], (char *) "PREPARE s1 FROM 'SHOW GLOBAL STATUS WHERE variable_name = ?'"); + Test->try_query(router[ir], (char *) "SET @a = \"Com_stmt_prepare\""); + Test->try_query(router[ir], (char *) "EXECUTE s1 USING @a"); + Test->try_query(router[ir], (char *) "PREPARE s1 FROM 'SHOW GLOBAL STATUS WHERE variable_name = ?'"); + Test->try_query(router[ir], (char *) "SET @a = \"Com_stmt_close\""); + Test->try_query(router[ir], (char *) "EXECUTE s1 USING @a"); + if ((( i / 100) * 100) == i ) + { + Test->tprintf("Iterations = %lu\n", i); + } + } + } + + Test->set_timeout(20); + + Test->close_maxscale_connections(); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; + +} diff --git a/maxscale-system-test/mxs280_select_outfile.cpp b/maxscale-system-test/mxs280_select_outfile.cpp new file mode 100644 index 000000000..b00728e9b --- /dev/null +++ b/maxscale-system-test/mxs280_select_outfile.cpp @@ -0,0 +1,56 @@ +/** + * @file mxs280_select_outfile.cpp bug mxs280 regression case ("SELECT INTO OUTFILE query succeeds even if backed fails") + * + * - Create /tmp/t1.csv on all backends + * - creat t1 table, put some data into it + * - try SELECT * INTO OUTFILE '/tmp/t1.csv' FROM t1 and expect failure + */ + + +#include +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + int i; + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->connect_maxscale(); + + Test->tprintf("Create /tmp/t1.csv on all backend nodes\n"); + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(30); + Test->repl->ssh_node(i, (char *) "touch /tmp/t1.csv", true); + } + + Test->add_result(create_t1(Test->conn_rwsplit), "Error creating t1\n"); + Test->try_query(Test->conn_rwsplit, (char *) "INSERT INTO t1 (x1, fl) VALUES (0, 0), (1, 0)"); + + if ( (execute_query(Test->conn_rwsplit, (char *) "SELECT * INTO OUTFILE '/tmp/t1.csv' FROM t1;")) == 0 ) + { + Test->add_result(1, "SELECT INTO OUTFILE epected to fail, but it is OK\n"); + } + + + + Test->tprintf("Remove /tmp/t1.csv from all backend nodes\n"); + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(30); + Test->repl->ssh_node(i, (char *) "rm -rf /tmp/t1.csv", true); + } + + Test->set_timeout(30); + sleep(5); + Test->check_log_err((char *) "Failed to execute session command in", true); + Test->check_log_err((char *) "File '/tmp/t1.csv' already exists", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs314.cpp b/maxscale-system-test/mxs314.cpp new file mode 100644 index 000000000..55a103768 --- /dev/null +++ b/maxscale-system-test/mxs314.cpp @@ -0,0 +1,74 @@ +/** + * @file mx314.cpp regression case for bug MXS-314 ("Read Write Split Error with Galera Nodes") + * - try prepared stmt 'SELECT 1,1,1,1...." with different nu,ber of '1' + * - check if Maxscale alive + */ + + +#include +#include +#include +#include +#include "testconnections.h" + +using std::string; +using namespace std; + +int main(int argc, char *argv[]) +{ + MYSQL_STMT* stmt; + int start = 300, p = 0; + int iterations = 2000; + string query = "select 1"; + + TestConnections * Test = new TestConnections(argc, argv); + if (Test->smoke) + { + iterations = 500; + } + Test->set_timeout(50); + + Test->connect_maxscale(); + + stmt = mysql_stmt_init(Test->conn_rwsplit); + + for (int i = 0; i < start; i++) + { + query += ",1"; + } + + Test->tprintf("Query: %s\n", query.c_str()); + + for (int i = start; i < iterations; i++) + { + Test->set_timeout(30); + Test->tprintf("%d\t", i); + if (mysql_stmt_prepare(stmt, query.c_str(), query.length())) + { + Test->add_result(1, "Error: %s\n", mysql_error(Test->conn_rwsplit)); + Test->add_result(1, "Failed at %d\n", i); +// delete Test; +// return 1; + } + if (mysql_stmt_reset(stmt)) + { + Test->add_result(1, "Error: %s\n", mysql_error(Test->conn_rwsplit)); + Test->add_result(1, "Failed at %d\n", i); +// delete Test; +// return 1; + } + query += ",1"; + if (i - p > 5) + { + p = i; + cout << endl; + } + } + cout << endl; + Test->set_timeout(20); + mysql_stmt_close(stmt); + Test->close_maxscale_connections(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs321.cpp b/maxscale-system-test/mxs321.cpp new file mode 100644 index 000000000..76bf1670d --- /dev/null +++ b/maxscale-system-test/mxs321.cpp @@ -0,0 +1,114 @@ +/** + * @file mx321.cpp regression case for bug MXS-321 ("Incorrect number of connections in maxadmin list view") + * + * - Set max_connections to 100 + * - Create 200 connections + * - Close connections + * - Check that maxadmin list servers shows 0 connections + */ + + +#include +#include +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +using namespace std; + +#define CONNECTIONS 200 +int check_connection_count(TestConnections* test, int server) +{ + char result[1024]; + char cmd[1024]; + test->set_timeout(30); + sprintf(cmd, "show server server%d", server); + test->add_result(test->get_maxadmin_param(cmd, (char*) "Current no. of conns:", result), + "maxadmin command %s failed\n", cmd); + int result_d = 999; + sscanf(result, "%d", &result_d); + if (strlen(result) == 0) + { + test->add_result(1, "Empty Current no. of conns \n"); + } + test->tprintf("result %s\t result_d %d\n", result, result_d); + return result_d; +} + +void create_and_check_connections(TestConnections* test, int target) +{ + MYSQL* stmt[CONNECTIONS]; + + for (int i = 0; i < CONNECTIONS; i++) + { + test->set_timeout(20); + switch (target) + { + case 1: + stmt[i] = test->open_rwsplit_connection(); + break; + + case 2: + stmt[i] = test->open_readconn_master_connection(); + break; + + case 3: + stmt[i] = test->open_readconn_master_connection(); + break; + } + } + + for (int i = 0; i < CONNECTIONS; i++) + { + test->set_timeout(20); + if (stmt[i]) + { + mysql_close(stmt[i]); + } + } + + test->stop_timeout(); + sleep(10); + int result_d; + + for (int j = 1; j < test->repl->N + 1; j++) + { + if ((result_d = check_connection_count(test, j))) + { + test->tprintf("Waiting 5 seconds and testing again."); + sleep(5); + result_d = check_connection_count(test, j); + } + + test->add_result(result_d, "Expected 0 connections, but got %d\n", result_d); + } +} + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(50); + + Test->repl->execute_query_all_nodes((char *) "SET GLOBAL max_connections=100"); + Test->connect_maxscale(); + execute_query(Test->conn_rwsplit, "SET GLOBAL max_connections=100"); + Test->close_maxscale_connections(); + Test->stop_timeout(); + + /** Create connections to readwritesplit */ + create_and_check_connections(Test, 1); + + /** Create connections to readconnroute master */ + create_and_check_connections(Test, 2); + + /** Create connections to readconnroute slave */ + create_and_check_connections(Test, 3); + + Test->repl->flush_hosts(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs365.cpp b/maxscale-system-test/mxs365.cpp new file mode 100644 index 000000000..b2cbb7703 --- /dev/null +++ b/maxscale-system-test/mxs365.cpp @@ -0,0 +1,88 @@ +/** + * @file mxs365.cpp Load data with LOAD DATA LOCAL INFILE + * + * 1. Create a 50Mb test file + * 2. Load and read it through MaxScale + */ + + +#include "testconnections.h" + +void create_data_file(char* filename, size_t size) +{ + int fd, i = 0; + snprintf(filename, size, "local_infile_%u", i++); + while ((fd = open(filename, O_CREAT | O_RDWR | O_EXCL, 0755)) == -1) + { + snprintf(filename, size, "local_infile_%u", i++); + } + + const size_t maxsize = 1024 * 1024 * 50; + size_t filesize = 0; + i = 0; + + while (filesize < maxsize) + { + char buffer[1024]; + sprintf(buffer, "%d,'%x','%x'\n", i, i << 10 + i, i << 5 + i); + int written = write(fd, buffer, strlen(buffer)); + if (written <= 0) + { + break; + } + i++; + filesize += written; + } + + close(fd); +} + +int main(int argc, char *argv[]) +{ + + TestConnections * test = new TestConnections(argc, argv); + char filename[1024]; + test->tprintf("Generation file to load\n"); + test->set_timeout(30); + create_data_file(filename, sizeof (filename)); + + /** Set max packet size and create test table */ + test->set_timeout(20); + test->tprintf("Connect to Maxscale\n"); + test->connect_maxscale(); + test->tprintf("Setting max_allowed_packet, creating table\n"); + test->add_result(execute_query(test->conn_rwsplit, + "set global max_allowed_packet=(1048576 * 60)"), + "Setting max_allowed_packet failed."); + test->add_result(execute_query(test->conn_rwsplit, + "DROP TABLE IF EXISTS test.dump"), + "Dropping table failed."); + test->add_result(execute_query(test->conn_rwsplit, + "CREATE TABLE test.dump(a int, b varchar(80), c varchar(80))"), + "Creating table failed."); + test->tprintf("Closing connection to Maxscale\n"); + test->close_maxscale_connections(); + + /** Reconnect, load the data and then read it */ + test->tprintf("Re-connect to Maxscale\n"); + test->set_timeout(20); + test->connect_maxscale(); + char query[1024]; + snprintf(query, sizeof (filename), + "LOAD DATA LOCAL INFILE '%s' INTO TABLE test.dump FIELDS TERMINATED BY ','", + filename); + test->tprintf("Loading data\n"); + test->set_timeout(100); + test->add_result(execute_query(test->conn_rwsplit, query), "Loading data failed."); + test->tprintf("Reading data\n"); + test->set_timeout(100); + test->add_result(execute_query(test->conn_rwsplit, "SELECT * FROM test.dump"), + "Reading data failed."); + test->close_maxscale_connections(); + test->tprintf("Cecking if Maxscale alive\n"); + test->check_maxscale_alive(); + int rval = test->global_result; + delete test; + unlink(filename); + return rval; +} diff --git a/maxscale-system-test/mxs37_table_privilege.cpp b/maxscale-system-test/mxs37_table_privilege.cpp new file mode 100644 index 000000000..14227bc22 --- /dev/null +++ b/maxscale-system-test/mxs37_table_privilege.cpp @@ -0,0 +1,82 @@ +/** + * @file mxs37_table_privilege.cpp mxs37 (bug719) regression case ("mandatory SELECT privilege on db level?") + * - create user with only 'SELECT' priveledge + * - try to connecto to MAxscle with this user + */ + + +#include +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + + Test->connect_maxscale(); + + Test->tprintf("Create user with only SELECT priviledge to a table"); + + execute_query_silent(Test->conn_rwsplit, "DROP USER 'table_privilege'@'%'"); + execute_query_silent(Test->conn_rwsplit, "DROP TABLE test.t1"); + execute_query(Test->conn_rwsplit, "CREATE TABLE test.t1 (id INT)"); + execute_query(Test->conn_rwsplit, "CREATE USER 'table_privilege'@'%%' IDENTIFIED BY 'pass'"); + execute_query(Test->conn_rwsplit, "GRANT SELECT ON test.t1 TO 'table_privilege'@'%%'"); + + Test->stop_timeout(); + Test->repl->sync_slaves(); + + Test->tprintf("Trying to connect using this user\n"); + Test->set_timeout(20); + + bool error = true; + + /** + * Since this test is executed on both Galera and Master-Slave clusters, we + * need to try to connect multiple times as Galera user creation doesn't + * seem to apply instantly on all nodes. For Master-Slave clusters, the + * first connection should be OK and if it's not, it's highly likely that + * others will also fail. + */ + for (int i = 0; i < 5; i++) + { + MYSQL *conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, (char *) "test", + (char *) "table_privilege", (char *) "pass", Test->ssl); + if (mysql_errno(conn) != 0) + { + Test->tprintf("Failed to connect: %s", mysql_error(conn)); + } + else + { + Test->set_timeout(20); + Test->tprintf("Trying SELECT\n"); + if (execute_query(conn, (char *) "SELECT * FROM t1") == 0) + { + error = false; + break; + } + } + mysql_close(conn); + sleep(1); + } + + if (error) + { + Test->add_result(1, "Failed to connect."); + } + + Test->set_timeout(20); + execute_query_silent(Test->conn_rwsplit, "DROP USER 'table_privilege'@'%'"); + execute_query_silent(Test->conn_rwsplit, "DROP TABLE test.t1"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + + return rval; +} + diff --git a/maxscale-system-test/mxs419_lots_of_connections.cpp b/maxscale-system-test/mxs419_lots_of_connections.cpp new file mode 100644 index 000000000..817ed394a --- /dev/null +++ b/maxscale-system-test/mxs419_lots_of_connections.cpp @@ -0,0 +1,37 @@ +/** + * @file mxs419_lots_of_connections.cpp ("Socket creation failed due 24, Too many open files") + * Trying to create 500 connections to every router + * checks Maxscale is alive + */ + + + +#include "testconnections.h" +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int connections = 500; + + Test->set_timeout(30); + Test->tprintf("set global max_connections = 2000"); + Test->connect_maxscale(); + Test->try_query(Test->conn_rwsplit, (char *) "set global max_connections = 2000;\n"); + Test->close_maxscale_connections(); + + Test->tprintf("Trying %d connections\n", connections); + Test->add_result(Test->create_connections(connections, true, true, true, false), + "Error creating connections\n"); + + Test->set_timeout(30); + Test->tprintf("set global max_connections = 100"); + Test->connect_maxscale(); + Test->try_query(Test->conn_rwsplit, (char *) "set global max_connections = 100;\n"); + Test->close_maxscale_connections(); + + Test->tprintf("Checking if Maxscale alive\n"); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + + return rval; +} diff --git a/maxscale-system-test/mxs431.cpp b/maxscale-system-test/mxs431.cpp new file mode 100644 index 000000000..708f95ea5 --- /dev/null +++ b/maxscale-system-test/mxs431.cpp @@ -0,0 +1,66 @@ +/** + * @file mxs431.cpp Bug regression test case for MXS-431: ("Backend authentication fails with schemarouter") + * + * - Create database 'testdb' on one node + * - Connect repeatedly to MaxScale with 'testdb' as the default database and execute SELECT 1 + */ + + +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + char str[256]; + int iterations = Test->smoke ? 100 : 500; + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 600;"); + Test->set_timeout(30); + Test->repl->stop_slaves(); + Test->set_timeout(30); + Test->restart_maxscale(); + Test->set_timeout(30); + Test->repl->connect(); + Test->stop_timeout(); + + /** Create a database on each node */ + for (int i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(20); + sprintf(str, "DROP DATABASE IF EXISTS shard_db%d", i); + Test->tprintf("%s\n", str); + execute_query(Test->repl->nodes[i], str); + Test->set_timeout(20); + sprintf(str, "CREATE DATABASE shard_db%d", i); + Test->tprintf("%s\n", str); + execute_query(Test->repl->nodes[i], str); + Test->stop_timeout(); + } + + Test->repl->close_connections(); + + for (int j = 0; j < iterations && Test->global_result == 0; j++) + { + for (int i = 0; i < Test->repl->N; i++) + { + sprintf(str, "shard_db%d", i); + Test->set_timeout(15); + MYSQL *conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, + str, Test->maxscale_user, + Test->maxscale_password, Test->ssl); + Test->set_timeout(15); + Test->tprintf("Trying DB %d\n", i); + if (execute_query(conn, "SELECT 1")) + { + Test->add_result(1, "Failed at %d\n", j); + break; + } + mysql_close(conn); + } + } + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs47.cpp b/maxscale-system-test/mxs47.cpp new file mode 100644 index 000000000..b64121481 --- /dev/null +++ b/maxscale-system-test/mxs47.cpp @@ -0,0 +1,30 @@ +/** + * @file mxs47.cpp Regression test for bug MXS-47 ("Session freeze when small tail packet") + * - execute SELECT REPEAT('a',i), where 'i' is changing from 1 to 3000 with stride of 7 using readwritesplit + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + int iterations = 5000; + + test.tprintf("Executing `SELECT REPEAT('a', X );` for X = 0..%d with a stride of 7", iterations); + test.connect_maxscale(); + + for (int i = 1; i < iterations; i += 7) + { + char str[1024]; + sprintf(str, "SELECT REPEAT('a',%d)", i); + + test.set_timeout(15); + test.try_query(test.conn_rwsplit, str); + } + + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/mxs501_tee_usedb.cpp b/maxscale-system-test/mxs501_tee_usedb.cpp new file mode 100644 index 000000000..cbd9bfee4 --- /dev/null +++ b/maxscale-system-test/mxs501_tee_usedb.cpp @@ -0,0 +1,76 @@ +/** + * @file mxs501_tee_usedb.cpp mxs501 regression case ("USE hangs when Tee filter uses matching") + * @verbatim +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +router_options=slave_selection_criteria=LEAST_ROUTER_CONNECTIONS +max_slave_connections=1 +filters=QLA,duplicate + +[duplicate] +type=filter +module=tee +match=insert +service=Connection Router Master + + +[Read Connection Router Slave] +type=service +router=readconnroute +router_options= slave +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=QLA + +[Read Connection Router Master] +type=service +router=readconnroute +router_options=master +servers=server1,server2,server3,server4 +user=skysql +passwd=skysql +filters=QLA +@endverbatim + * + * try USE test command against all routers + */ + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + + Test->set_timeout(10); + Test->tprintf("Trying USE db against RWSplit\n"); + Test->try_query(Test->conn_rwsplit, (char *) "USE mysql"); + Test->try_query(Test->conn_rwsplit, (char *) "USE test"); + Test->set_timeout(10); + Test->tprintf("Trying USE db against ReadConn master\n"); + Test->try_query(Test->conn_master, (char *) "USE mysql"); + Test->try_query(Test->conn_master, (char *) "USE test"); + Test->set_timeout(10); + Test->tprintf("Trying USE db against ReadConn slave\n"); + Test->try_query(Test->conn_master, (char *) "USE mysql"); + Test->try_query(Test->conn_slave, (char *) "USE test"); + + Test->set_timeout(10); + Test->close_maxscale_connections(); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs548_short_session_change_user.cpp b/maxscale-system-test/mxs548_short_session_change_user.cpp new file mode 100644 index 000000000..6dd42d890 --- /dev/null +++ b/maxscale-system-test/mxs548_short_session_change_user.cpp @@ -0,0 +1,270 @@ +/** + * @file mxs548_short_session_change_user.cpp MXS-548 regression case ("Maxscale crash") + * - configure 2 backend servers (one Master, one Slave) + * - create 'user' with password 'pass2' + * - create load on Master (3 threads are inserting data into 't1' in the loop) + * - in 40 parallel threads open connection, execute change_user to 'user', execute change_user to default user, close connection + * - repeat test first only for RWSplit and second for all routers + * - check logs for lack of "Unable to write to backend 'server2' due to authentication failure" errors + * - check for lack of crashes in the log + */ + + +#include "testconnections.h" +#include "sql_t1.h" + +typedef struct +{ + int exit_flag; + int thread_id; + long i; + int rwsplit_only; + TestConnections * Test; + MYSQL * conn1; + MYSQL * conn2; + MYSQL * conn3; +} openclose_thread_data; + +void *query_thread1(void *ptr); +void *query_thread_master(void *ptr); + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->ssh_maxscale(true, "sysctl net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1 " + "net.core.somaxconn=10000 net.ipv4.tcp_max_syn_backlog=10000"); + Test->set_timeout(20); + + int threads_num = 40; + openclose_thread_data data[threads_num]; + + int master_load_threads_num = 3; + openclose_thread_data data_master[master_load_threads_num]; + + int i; + + int run_time = 300; + + if (Test->smoke) + { + run_time = 10; + } + + for (i = 0; i < threads_num; i++) + { + data[i].i = 0; + data[i].exit_flag = 0; + data[i].Test = Test; + data[i].rwsplit_only = 1; + data[i].thread_id = i; + data[i].conn1 = NULL; + data[i].conn2 = NULL; + data[i].conn3 = NULL; + } + + + for (i = 0; i < master_load_threads_num; i++) + { + data_master[i].i = 0; + data_master[i].exit_flag = 0; + data_master[i].Test = Test; + data_master[i].rwsplit_only = 1; + data_master[i].thread_id = i; + data_master[i].conn1 = NULL; + data_master[i].conn2 = NULL; + data_master[i].conn3 = NULL; + } + + pthread_t thread1[threads_num]; + int iret1[threads_num]; + + pthread_t thread_master[master_load_threads_num]; + int iret_master[master_load_threads_num]; + + Test->repl->connect(); + Test->connect_maxscale(); + create_t1(Test->conn_rwsplit); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 2000;"); + Test->repl->sync_slaves(); + + Test->tprintf("Creating user 'user' \n"); + execute_query(Test->conn_rwsplit, (char *) "DROP USER user@'%%'"); + execute_query(Test->conn_rwsplit, (char *) "CREATE USER user@'%%' IDENTIFIED BY 'pass2'"); + execute_query(Test->conn_rwsplit, (char *) "GRANT SELECT ON test.* TO user@'%%'"); + execute_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS test.t1"); + execute_query(Test->conn_rwsplit, (char *) "CREATE TABLE test.t1 (x1 int, fl int)"); + Test->repl->sync_slaves(); + + /* Create independent threads each of them will create some load on Master */ + for (i = 0; i < master_load_threads_num; i++) + { + iret_master[i] = pthread_create(&thread_master[i], NULL, query_thread_master, &data_master[i]); + } + + /* Create independent threads each of them will execute function */ + for (i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, query_thread1, &data[i]); + } + + Test->tprintf("Threads are running %d seconds \n", run_time); + + for (i = 0; i < threads_num; i++) + { + data[i].rwsplit_only = 1; + } + + Test->set_timeout(run_time + 60); + sleep(run_time); + + Test->repl->flush_hosts(); + + Test->tprintf("all routers are involved, threads are running %d seconds more\n", run_time); + Test->set_timeout(run_time + 100); + + for (i = 0; i < threads_num; i++) + { + data[i].rwsplit_only = 0; + } + + sleep(run_time); + Test->set_timeout(120); + Test->tprintf("Waiting for all threads exit\n"); + + for (i = 0; i < threads_num; i++) + { + data[i].exit_flag = 1; + pthread_join(thread1[i], NULL); + } + + Test->tprintf("Waiting for all master load threads exit\n"); + + for (i = 0; i < master_load_threads_num; i++) + { + data_master[i].exit_flag = 1; + pthread_join(thread_master[i], NULL); + } + + Test->tprintf("Flushing backend hosts\n"); + Test->set_timeout(60); + Test->repl->flush_hosts(); + + Test->tprintf("Dropping tables and users\n"); + Test->set_timeout(60); + execute_query(Test->conn_rwsplit, (char *) "DROP TABLE test.t1;"); + execute_query(Test->conn_rwsplit, (char *) "DROP USER user@'%%'"); + Test->close_maxscale_connections(); + + Test->set_timeout(160); + Test->tprintf("Trying to connect Maxscale\n"); + Test->connect_maxscale(); + Test->tprintf("Closing Maxscale connections\n"); + Test->close_maxscale_connections(); + Test->tprintf("Checking if Maxscale alive\n"); + Test->check_maxscale_alive(); + Test->tprintf("Checking log for unwanted errors\n"); + Test->check_log_err((char *) "due to authentication failure", false); + Test->check_log_err((char *) "fatal signal 11", false); + Test->check_log_err((char *) "due to handshake failure", false); + + // We need to wait for the TCP connections in TIME_WAIT state so that + // later tests don't fail due to a lack of file descriptors + Test->tprintf("Waiting for network connections to die"); + sleep(30); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *query_thread1(void *ptr) +{ + openclose_thread_data * data = (openclose_thread_data *) ptr; + + while (data->exit_flag == 0) + { + data->conn1 = data->Test->open_rwsplit_connection(); + + if (data->conn1 != NULL) + { + if (mysql_errno(data->conn1) == 0) + { + mysql_change_user(data->conn1, (char *) "user", (char *) "pass2", (char *) "test"); + mysql_change_user(data->conn1, data->Test->repl->user_name, data->Test->repl->password, (char *) "test"); + } + } + if (data->rwsplit_only == 0) + { + data->conn2 = data->Test->open_readconn_master_connection(); + + if (data->conn2 != NULL) + { + if (mysql_errno(data->conn2) == 0) + { + mysql_change_user(data->conn2, (char *) "user", (char *) "pass2", (char *) "test"); + mysql_change_user(data->conn2, data->Test->repl->user_name, data->Test->repl->password, (char *) "test"); + } + } + + data->conn3 = data->Test->open_readconn_slave_connection(); + + if (data->conn3 != NULL) + { + if (mysql_errno(data->conn3) == 0) + { + mysql_change_user(data->conn3, (char *) "user", (char *) "pass2", (char *) "test"); + mysql_change_user(data->conn3, data->Test->repl->user_name, data->Test->repl->password, (char *) "test"); + } + } + } + + if (data->conn1 != NULL) + { + mysql_close(data->conn1); + data->conn1 = NULL; + } + + if (data->rwsplit_only == 0) + { + if (data->conn2 != NULL) + { + mysql_close(data->conn2); + data->conn2 = NULL; + } + + if (data->conn3 != NULL) + { + mysql_close(data->conn3); + data->conn3 = NULL; + } + } + + data->i++; + } + + return NULL; +} + +void *query_thread_master(void *ptr) +{ + openclose_thread_data * data = (openclose_thread_data *) ptr; + char sql[1000000]; + data->conn1 = open_conn(data->Test->repl->port[0], data->Test->repl->IP[0], + data->Test->repl->user_name, data->Test->repl->password, false); + create_insert_string(sql, 5000, 2); + + if (data->conn1 != NULL) + { + while (data->exit_flag == 0) + { + data->Test->try_query(data->conn1, sql); + data->i++; + } + } + else + { + data->Test->add_result(1, "Error creating MYSQL struct for Master conn\n"); + } + + return NULL; +} diff --git a/maxscale-system-test/mxs559_block_master.cpp b/maxscale-system-test/mxs559_block_master.cpp new file mode 100644 index 000000000..5f5d8c4c8 --- /dev/null +++ b/maxscale-system-test/mxs559_block_master.cpp @@ -0,0 +1,145 @@ + +/** + * @file mxs559_block_master Playing with blocking and unblocking Master + * It does not reproduce the bug in reliavle way, but it is a good + * load and robustness test + * - create load on Master RWSplit + * - block and unblock Master in the loop + * - repeat with different time between block/unblock + * - check logs for lack of errors "authentication failure", "handshake failure" + * - check for lack of crashes in the log + */ + + +#include "testconnections.h" +#include "sql_t1.h" +#include + +typedef struct +{ + int port; + std::string ip; + std::string user; + std::string password; + bool ssl; + int exit_flag; +} openclose_thread_data; + +void *disconnect_thread(void *ptr); + +int main(int argc, char *argv[]) +{ + TestConnections test(argc, argv); + test.ssh_maxscale(true, "sysctl net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1 " + "net.core.somaxconn=10000 net.ipv4.tcp_max_syn_backlog=10000"); + + test.set_timeout(60); + test.connect_maxscale(); + create_t1(test.conn_rwsplit); + execute_query(test.conn_rwsplit, "set global max_connections=1000"); + test.close_maxscale_connections(); + + test.tprintf("Create query load"); + int load_threads_num = 10; + openclose_thread_data data_master[load_threads_num]; + pthread_t thread_master[load_threads_num]; + + /* Create independent threads each of them will create some load on Master */ + for (int i = 0; i < load_threads_num; i++) + { + data_master[i].exit_flag = 0; + data_master[i].ip = test.maxscale_IP; + data_master[i].port = test.rwsplit_port; + data_master[i].user = test.maxscale_user; + data_master[i].password = test.maxscale_password; + data_master[i].ssl = test.ssl; + pthread_create(&thread_master[i], NULL, disconnect_thread, &data_master[i]); + } + + int iterations = 5; + int sleep_interval = 10; + + for (int i = 0; i < iterations; i++) + { + test.stop_timeout(); + sleep(sleep_interval); + + test.set_timeout(60); + test.tprintf("Block master"); + test.repl->block_node(0); + + test.stop_timeout(); + sleep(sleep_interval); + + test.set_timeout(60); + test.tprintf("Unblock master"); + test.repl->unblock_node(0); + } + + test.tprintf("Waiting for all master load threads exit"); + for (int i = 0; i < load_threads_num; i++) + { + test.set_timeout(240); + data_master[i].exit_flag = 1; + pthread_join(thread_master[i], NULL); + } + + test.stop_timeout(); + test.tprintf("Make sure that replication works"); + test.repl->flush_hosts(); + if (!test.repl->fix_replication()) + { + test.tprintf("Replication is broken!"); + } + + // Try to connect over a period of 60 seconds. It is possible that + // there are no available network sockets which means we'll have to + // wait until some of them become available. This is caused by how the + // TCP stack works. + for (int i = 0; i < 60; i++) + { + test.set_timeout(60); + test.verbose = true; + int rc = test.connect_maxscale(); + test.verbose = false; + + if (rc == 0) + { + break; + } + sleep(1); + } + + test.try_query(test.conn_rwsplit, "DROP TABLE IF EXISTS t1"); + test.close_maxscale_connections(); + + test.check_maxscale_alive(); + test.check_log_err("due to authentication failure", false); + test.check_log_err("fatal signal 11", false); + test.check_log_err("due to handshake failure", false); + test.check_log_err("Refresh rate limit exceeded for load of users' table", false); + + return test.global_result; +} + + +void *disconnect_thread( void *ptr ) +{ + openclose_thread_data *data = (openclose_thread_data*) ptr; + char sql[1000000]; + + sleep(3); + create_insert_string(sql, 50000, 2); + + while (data->exit_flag == 0) + { + MYSQL *conn = open_conn_db_timeout(data->port, data->ip, "test", + data->user, data->password, + 10, data->ssl); + execute_query_silent(conn, sql); + mysql_close(conn); + } + + return NULL; +} + diff --git a/maxscale-system-test/mxs564_big_dump.cpp b/maxscale-system-test/mxs564_big_dump.cpp new file mode 100644 index 000000000..9771181be --- /dev/null +++ b/maxscale-system-test/mxs564_big_dump.cpp @@ -0,0 +1,237 @@ +/** + * @file mxs564_big_dump.cpp MXS-564 regression case ("Loading database dump through readwritesplit fails") + * - configure Maxscale to use Galera cluster + * - start several threads which are executing session command and then sending INSERT queries agaist RWSplit router + * - after a while block first slave + * - after a while block second slave + * - check that all INSERTs are ok + * - repeat with both RWSplit and ReadConn master routers + * - check Maxscale is alive + */ + + +#include "testconnections.h" +#include "sql_t1.h" +//#include "get_com_select_insert.h" + +typedef struct +{ + int exit_flag; + int thread_id; + long i; + int rwsplit_only; + TestConnections * Test; + MYSQL * conn1; + MYSQL * conn2; + MYSQL * conn3; +} openclose_thread_data; +void *query_thread1( void *ptr ); + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->stop_timeout(); + + int threads_num = 4; + openclose_thread_data data[threads_num]; + + int i; + int run_time = 100; + + if (Test->smoke) + { + run_time = 10; + } + + for (i = 0; i < threads_num; i++) + { + data[i].i = 0; + data[i].exit_flag = 0; + data[i].Test = Test; + data[i].rwsplit_only = 1; + data[i].thread_id = i; + } + + + pthread_t thread1[threads_num]; + int iret1[threads_num]; + + //Test->repl->flush_hosts(); + Test->set_timeout(20); + int master = Test->find_master_maxadmin(Test->galera); + Test->stop_timeout(); + Test->tprintf(("Master is %d\n"), master); + int k = 0; + int x = 0; + int slaves[2]; + while (k < 2 ) + { + if (x != master) + { + slaves[k] = x; + k++; + x++; + } + else + { + x++; + } + } + Test->tprintf(("Slave1 is %d\n"), slaves[0]); + Test->tprintf(("Slave2 is %d\n"), slaves[1]); + + Test->set_timeout(20); + Test->repl->connect(); + Test->connect_maxscale(); + Test->set_timeout(20); + create_t1(Test->conn_rwsplit); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 2000;"); + + Test->set_timeout(20); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t1"); + Test->try_query(Test->conn_rwsplit, (char *) "CREATE TABLE t1 (x1 int, fl int)"); + + for (i = 0; i < threads_num; i++) + { + data[i].rwsplit_only = 1; + } + /* Create independent threads each of them will execute function */ + for (i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, query_thread1, &data[i]); + } + Test->tprintf("Threads are running %d seconds \n", run_time); + + Test->set_timeout(3 * run_time + 60); + sleep(20); + sleep(run_time); + Test->tprintf("Blocking slave %d\n", slaves[0]); + Test->galera->block_node(slaves[0]); + sleep(run_time); + Test->galera->block_node(slaves[1]); + Test->tprintf("Blocking slave %d\n", slaves[1]); + sleep(run_time); + Test->tprintf("Unblocking slaves\n"); + Test->galera->unblock_node(slaves[0]); + Test->galera->unblock_node(slaves[1]); + + Test->set_timeout(120); + Test->tprintf("Waiting for all threads exit\n"); + for (i = 0; i < threads_num; i++) + { + data[i].exit_flag = 1; + pthread_join(thread1[i], NULL); + Test->tprintf("exit %d\n", i); + } + + Test->tprintf("all routers are involved, threads are running %d seconds more\n", run_time); + + for (i = 0; i < threads_num; i++) + { + data[i].rwsplit_only = 0; + } + for (i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, query_thread1, &data[i]); + } + + Test->set_timeout(3 * run_time + 60); + sleep(20); + sleep(run_time); + Test->tprintf("Blocking node %d\n", slaves[0]); + Test->galera->block_node(slaves[0]); + sleep(run_time); + Test->tprintf("Blocking node %d\n", slaves[1]); + Test->galera->block_node(slaves[1]); + sleep(run_time); + Test->tprintf("Unblocking nodes\n"); + Test->galera->unblock_node(slaves[0]); + Test->galera->unblock_node(slaves[1]); + + Test->set_timeout(120); + Test->tprintf("Waiting for all threads exit\n"); + for (i = 0; i < threads_num; i++) + { + data[i].exit_flag = 1; + pthread_join(thread1[i], NULL); + } + + sleep(5); + + Test->set_timeout(60); + Test->tprintf("set global max_connections = 100 for all backends\n"); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 100;"); + Test->tprintf("Drop t1\n"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE IF EXISTS t1;"); + Test->close_maxscale_connections(); + + Test->tprintf("Checking if Maxscale alive\n"); + Test->check_maxscale_alive(); + //Test->tprintf("Checking log for unwanted errors\n"); + //Test->check_log_err((char *) "due to authentication failure", false); + //Test->check_log_err((char *) "fatal signal 11", false); + //Test->check_log_err((char *) "due to handshake failure", false); + //Test->check_log_err((char *) "Refresh rate limit exceeded for load of users' table", false); + + int rval = Test->global_result; + delete Test; + return rval; +} + +void *query_thread1( void *ptr ) +{ + openclose_thread_data * data = (openclose_thread_data *) ptr; + char sql[1000000]; + sleep(data->thread_id); + create_insert_string(sql, 1000, 2); + + data->conn1 = data->Test->open_rwsplit_connection(); + if ((data->conn1 == NULL) || (mysql_errno(data->conn1) != 0 )) + { + data->Test->add_result(1, "Error connecting to RWSplit\n"); + return NULL; + } + + data->Test->try_query(data->conn1, (char *) "SET SESSION SQL_LOG_BIN=0;"); + + if (data->rwsplit_only == 0) + { + data->conn2 = data->Test->open_readconn_master_connection(); + if ((data->conn2 == NULL) || (mysql_errno(data->conn2) != 0 )) + { + data->Test->add_result(1, "Error connecting to ReadConn Master\n"); + return NULL; + } + data->Test->try_query(data->conn2, (char *) "SET SESSION SQL_LOG_BIN=0;"); + } + + while (data->exit_flag == 0) + { + if (data->Test->try_query(data->conn1, sql)) + { + data->Test->add_result(1, "Query to ReadConn Master failed\n"); + return NULL; + } + if (data->rwsplit_only == 0) + { + if (data->Test->try_query(data->conn2, sql)) + { + data->Test->add_result(1, "Query to RWSplit failed\n"); + return NULL; + } + } + data->i++; + } + if (data->conn1 != NULL) + { + mysql_close(data->conn1); + } + if (data->rwsplit_only == 0) + { + if (data->conn2 != NULL) + { + mysql_close(data->conn2); + } + } + return NULL; +} diff --git a/maxscale-system-test/mxs585.py b/maxscale-system-test/mxs585.py new file mode 100755 index 000000000..49b1d094f --- /dev/null +++ b/maxscale-system-test/mxs585.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +### +## @file mxs585.py Regression case for MXS-585 "Intermittent connection failure with MaxScale 1.2/1.3 using MariaDB/J 1.3" +## - open connection, execute simple query and close connection in the loop + +import maxpython + +test1 = maxpython.MaxScaleTest("mxs585.py") + +for i in range(0,100): + if i % 10 == 0: + print(str(i)) + test1.maxscale['rwsplit'].query_and_close("select 1") + test1.maxscale['rcmaster'].query_and_close("select 1") + test1.maxscale['rcslave'].query_and_close("select 1") diff --git a/maxscale-system-test/mxs598.py b/maxscale-system-test/mxs598.py new file mode 100755 index 000000000..ddb07180f --- /dev/null +++ b/maxscale-system-test/mxs598.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + + +### +## @file mxs598.py Regression case for MXS-598 "SSL RW Router / JDBC Exception" +## - use SSL for Maxscale client connection +## - simple transactions in the loop + +import maxpython + +test1 = maxpython.MaxScaleTest("mxs598.py") + +print("Connecting to MaxScale") +for i in test1.maxscale.values(): + i.connect("useSSL=true&requireSSL=true&verifyServerCert=false") + +print("Trying 100 simple transactions on all services") +for i in range(0,100): + for x in test1.maxscale.values(): + x.begin() + x.query("insert into test.t1 values (1)") + x.query("select * from test.t1") + x.commit() + +print("Closing connections") +for i in test1.maxscale.values(): + i.disconnect() diff --git a/maxscale-system-test/mxs621_unreadable_cnf.cpp b/maxscale-system-test/mxs621_unreadable_cnf.cpp new file mode 100644 index 000000000..9c302a27f --- /dev/null +++ b/maxscale-system-test/mxs621_unreadable_cnf.cpp @@ -0,0 +1,32 @@ +/** + * @file max621_unreadable_cnf.cpp mxs621 regression case ("MaxScale fails to start silently if config file is not readable") + * + * - make maxscale.cnf unreadable + * - try to restart Maxscale + * - check log for error + * - retore access rights to maxscale.cnf + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + Test->ssh_maxscale(true, "chmod 400 /etc/maxscale.cnf"); + Test->set_timeout(30); + Test->restart_maxscale(); + Test->set_timeout(30); + Test->check_log_err((char *) "Opening file '/etc/maxscale.cnf' for reading failed", true); + Test->set_timeout(30); + Test->ssh_maxscale(true, "chmod 777 /etc/maxscale.cnf"); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs652_bad_ssl.cpp b/maxscale-system-test/mxs652_bad_ssl.cpp new file mode 100644 index 000000000..6a3dc8de2 --- /dev/null +++ b/maxscale-system-test/mxs652_bad_ssl.cpp @@ -0,0 +1,57 @@ +/** + * @file mxs652_bad_ssl.cpp mxs652 regression case ("ssl is configured in a wrong way, but Maxscale can be started and works") + * + * - Maxscale.cnf contains ssl configuration for all services in 'router' section instead of 'listener' with 'ssl=require' + * - trying to connect to all routers without ssl and expect error + */ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->check_log_err((char *) "Unexpected parameter 'ssl_version'", true); + + + Test->tprintf("Trying RWSplit, expecting fault\n"); + MYSQL * conn = open_conn(Test->rwsplit_port, Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, + false); + + if (mysql_errno(conn) == 0) + { + Test->add_result(1, "Configurations is wrong, but connection to RWSplit is ok\n"); + mysql_close(conn); + } + + Test->tprintf("Trying ReadConn master, expecting fault\n"); + conn = open_conn(Test->readconn_master_port, Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, + false); + + if (mysql_errno(conn) == 0) + { + Test->add_result(1, "Configurations is wrong, but connection to ReadConn master is ok\n"); + mysql_close(conn); + } + + Test->tprintf("Trying ReadConn slave, expecting fault\n"); + conn = open_conn(Test->readconn_slave_port, Test->maxscale_IP, Test->maxscale_user, Test->maxscale_password, + false); + + if (mysql_errno(conn) == 0) + { + Test->add_result(1, "Configurations is wrong, but connection to ReadConn slave is ok\n"); + mysql_close(conn); + } + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs657_restart.cpp b/maxscale-system-test/mxs657_restart.cpp new file mode 100644 index 000000000..5a8175f60 --- /dev/null +++ b/maxscale-system-test/mxs657_restart.cpp @@ -0,0 +1,122 @@ +/** + * @file mxs657_restart.cpp Regression case for MXS-657 ("Debug assertion when service is shut down and restarted repeatedly") + * - playing with 'restart service' and restart Maxscale under load + */ + + +#include "testconnections.h" +#include "sysbench_commands.h" +#include "sql_t1.h" +#include "get_com_select_insert.h" + +#include "big_load.h" + + +TestConnections * Test ; + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; +int start_flag = 0; +int restart_flag = 0; // 0 - maxadmin restart service, 1 - restart maxscale +unsigned int old_slave; +void *kill_vm_thread( void *ptr ); + +int main(int argc, char *argv[]) +{ + Test = new TestConnections(argc, argv); + pthread_t restart_t; + int check_iret; + int i, j; + + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + + Test->set_timeout(2000); + + check_iret = pthread_create(&restart_t, NULL, kill_vm_thread, NULL); + + int iter = 1000; + if (Test->smoke) + { + iter = 100; + } + + for (i = 0; i < iter; i++) + { + Test->tprintf("i= %d\n", i); + Test->connect_maxscale(); + for (j = 0; j < iter; j++) + { + execute_query_silent(Test->conn_rwsplit, "SELECT 1"); + + } + Test->close_maxscale_connections(); + if (i > iter) + { + restart_flag = 1; + } + } + + restart_flag = 0; + + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + long int i1, i2; + + int threads_num = 25; + if (Test->smoke) + { + threads_num = 15; + } + Test->tprintf("Increasing connection and error limits on backend nodes.\n"); + Test->repl->connect(); + for ( i = 0; i < Test->repl->N; i++) + { + execute_query(Test->repl->nodes[i], (char *) "set global max_connections = 300;"); + execute_query(Test->repl->nodes[i], (char *) "set global max_connect_errors = 100000;"); + } + Test->repl->close_connections(); + + Test->tprintf("Creating query load with %d threads and use maxadmin service restart...\n", threads_num); + Test->set_timeout(1200); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], threads_num, Test, &i1, &i2, 1, false, + false); + restart_flag = 1; + Test->set_timeout(1200); + Test->tprintf("Creating query load with %d threads and restart maxscalen", threads_num); + load(&new_inserts[0], &new_selects[0], &selects[0], &inserts[0], threads_num, Test, &i1, &i2, 1, false, + false); + + Test->tprintf("Exiting ...\n"); + exit_flag = 1; + pthread_join(restart_t, NULL); + + Test->tprintf("Checxking if MaxScale is still alive!\n"); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + +void *kill_vm_thread( void *ptr ) +{ + while (exit_flag == 0) + { + sleep(2); + if (restart_flag == 0) + { + Test->execute_maxadmin_command((char * ) "restart service \"RW Split Router\""); + } + else + { + Test->restart_maxscale(); + } + } + + return NULL; +} + diff --git a/maxscale-system-test/mxs657_restart_service.cpp b/maxscale-system-test/mxs657_restart_service.cpp new file mode 100644 index 000000000..8086bd231 --- /dev/null +++ b/maxscale-system-test/mxs657_restart_service.cpp @@ -0,0 +1,97 @@ +/** + * @file mxs657_restart_service.cpp mxs657 regression case ("Debug assertion when service is shut down and restarted repeatedly") + * + * - shutdown and restart RW Split Router in the loop from a number of threads + * Note: does not work crash reliable way with 'smoke' option + */ + + + +#include +#include +#include "testconnections.h" + +using namespace std; +void *query_thread1( void *ptr ); +TestConnections * Test; +bool exit_flag = false; +char * shutdown_cmd; +char * restart_cmd; + +char *router_sht = (char *) "shutdown service \"RW Split Router\""; +char *router_rst = (char *) "restart service \"RW Split Router\""; + +char *listener_sht = (char *) "shutdown service \"RW Split Listener\""; +char *listener_rst = (char *) "restart service \"RW Split Listener\""; + +char *monitor_sht = (char *) "shutdown service \"MySQL Monitor\""; +char *monitor_rst = (char *) "restart service \"MySQL Monitor\""; + +void sht_rst_service() +{ + int threads_num = 5; + pthread_t thread1[threads_num]; + + int iret1[threads_num]; + int i; + + for (i = 0; i < threads_num; i++) + { + iret1[i] = pthread_create(&thread1[i], NULL, query_thread1, NULL); + } + + Test->tprintf("Trying to shutdown and restart RW Split router in the loop\n"); + + sleep(10); + + Test->tprintf("Done, exiting threads\n\n"); + + exit_flag = true; + for (int i = 0; i < threads_num; i++) + { + pthread_join(thread1[i], NULL); + } + + Test->tprintf("Done!\n"); +} + +int main(int argc, char *argv[]) +{ + Test = new TestConnections(argc, argv); + + Test->tprintf("Shutdown and restart Router\n"); + + shutdown_cmd = router_sht; + restart_cmd = router_rst; + + sht_rst_service(); + + Test->tprintf("Shutdown and restart Listener\n"); + + shutdown_cmd = listener_sht; + restart_cmd = listener_rst; + + sht_rst_service(); + + Test->tprintf("Shutdown and restart Monitor\n"); + + shutdown_cmd = monitor_sht; + restart_cmd = monitor_rst; + + sht_rst_service(); + + Test->check_maxscale_alive(); + Test->check_log_err((char *) "received fatal signal", false); + int rval = Test->global_result; + delete Test; + return rval; +} + +void *query_thread1( void *ptr ) +{ + while (!exit_flag) + { + Test->execute_maxadmin_command(shutdown_cmd); + Test->execute_maxadmin_command(restart_cmd); + } +} diff --git a/maxscale-system-test/mxs682_cyrillic.cpp b/maxscale-system-test/mxs682_cyrillic.cpp new file mode 100644 index 000000000..1561cfe86 --- /dev/null +++ b/maxscale-system-test/mxs682_cyrillic.cpp @@ -0,0 +1,100 @@ +/** + * @file mxs682_cyrillic.cpp put cyrillic letters to the table + * - put string with Cyrillic into table + * - check SELECT from backend + */ + + + +#include +#include +#include "testconnections.h" +#include + +using namespace std; + +void check_val(MYSQL* conn, TestConnections* Test) +{ + char val[256]; + Test->set_timeout(20); + find_field(conn, "SELECT * FROM t2", "x", val); + + Test->tprintf("result: %s\n", val); + + if (strcmp("Кот", val) != 0 ) + { + Test->add_result(1, "Wrong SELECT result: %s\n", val); + } +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Mariadb_nodes * nodes; + if (strstr(Test->test_name, "galera") != NULL) + { + nodes = Test->galera; + Test->tprintf("Galera!\n"); + } + else + { + nodes = Test->repl; + } + + + /* + iconv_t converter = iconv_open ("koi8-r", "utf-8"); + Test->tprintf("errno %d\n", errno); + + char in_buf[] = "Кот"; + char out_buf[100]; + char *in_ptr = in_buf; + char *out_ptr = out_buf; + size_t in_size = strlen(in_buf); + size_t out_size = 100; + + size_t n = iconv(converter, &in_ptr, &in_size, &out_ptr, &out_size); + + Test->tprintf("n = %d\n", n); + //Test->tprintf("UTF-8: %s\n", out_buf); + + iconv_close(converter); + */ + + Test->connect_maxscale(); + Test->set_timeout(10); + nodes->connect(); + + Test->set_timeout(10); + MYSQL * conn = Test->conn_rwsplit; + + //Test->try_query(conn, (char *) "set names utf8mb4;"); + execute_query_silent(conn, (char *) "DROP TABLE t2;"); + Test->try_query(conn, (char *) "CREATE TABLE t2 (x varchar(10));"); + char sql[256]; + sprintf(sql, "INSERT INTO t2 VALUES (\"Кот\");"); + Test->try_query(conn, sql); + Test->stop_timeout(); + sleep(5); + + check_val(Test->conn_rwsplit, Test); + check_val(Test->conn_master, Test); + check_val(Test->conn_slave, Test); + + for (int i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Node %d\n", i); + check_val(nodes->nodes[i], Test); + } + + //execute_query_silent(conn, (char *) "DROP TABLE t2;"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + + diff --git a/maxscale-system-test/mxs710_bad_socket.cpp b/maxscale-system-test/mxs710_bad_socket.cpp new file mode 100644 index 000000000..e79013c18 --- /dev/null +++ b/maxscale-system-test/mxs710_bad_socket.cpp @@ -0,0 +1,25 @@ +/** + * @file mxs710_bad_socket.cpp mxs710_bad_socket regression case (Maxscale does not startup properly and crashes after trying to login to database) + * - try to start maxscale with "socket=/var/lib/mysqld/mysql.sock" in the listener definition + * - do not expect crash + * - try the same with two listers for one service, one of them uses unreachable port + */ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->check_maxscale_processes(0); + Test->check_log_err("Fatal", false); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs716.cpp b/maxscale-system-test/mxs716.cpp new file mode 100644 index 000000000..5d9c00cf9 --- /dev/null +++ b/maxscale-system-test/mxs716.cpp @@ -0,0 +1,86 @@ +/** + * @file mxs716.cpp Test for MXS-716 ("Access Denied: User without global privileges on a schema + * but with grants only on some tables can't connect if the default schema is specified + * in the connection string") + * + * - Connect using different default databases with database and table level grants. + */ + + +#include +#include +#include "testconnections.h" +#include "sql_t1.h" + +void run_test(TestConnections* Test, const char* database) +{ + + Test->set_timeout(20); + Test->tprintf("Trying to connect using 'table_privilege'@'%%' to database '%s'", database); + + MYSQL* conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, database, "table_privilege", "pass", + Test->ssl); + + if (conn && mysql_errno(conn) == 0) + { + Test->set_timeout(20); + Test->tprintf("Trying SELECT on %s.t1", database); + Test->try_query(conn, "SELECT * FROM t1"); + } + else + { + Test->add_result(1, "Failed to connect using database '%s': %s", database, mysql_error(conn)); + } + + mysql_close(conn); +} + +int main(int argc, char *argv[]) +{ + TestConnections* Test = new TestConnections(argc, argv); + + Test->connect_maxscale(); + Test->tprintf("Preparing test"); + Test->set_timeout(180); + execute_query(Test->conn_rwsplit, "DROP DATABASE IF EXISTS db1"); + execute_query(Test->conn_rwsplit, "DROP DATABASE IF EXISTS db2"); + execute_query(Test->conn_rwsplit, "DROP DATABASE IF EXISTS db3"); + execute_query(Test->conn_rwsplit, "DROP DATABASE IF EXISTS db4"); + execute_query(Test->conn_rwsplit, "CREATE DATABASE db1"); + execute_query(Test->conn_rwsplit, "CREATE DATABASE db2"); + execute_query(Test->conn_rwsplit, "CREATE DATABASE db3"); + execute_query(Test->conn_rwsplit, "CREATE DATABASE db4"); + execute_query(Test->conn_rwsplit, "CREATE TABLE db1.t1 (id INT)"); + execute_query(Test->conn_rwsplit, "CREATE TABLE db2.t1 (id INT)"); + execute_query(Test->conn_rwsplit, "CREATE TABLE db3.t1 (id INT)"); + execute_query(Test->conn_rwsplit, "CREATE TABLE db4.t1 (id INT)"); + execute_query(Test->conn_rwsplit, "INSERT INTO db1.t1 VALUES (1)"); + execute_query(Test->conn_rwsplit, "INSERT INTO db2.t1 VALUES (1)"); + execute_query(Test->conn_rwsplit, "INSERT INTO db3.t1 VALUES (1)"); + execute_query(Test->conn_rwsplit, "INSERT INTO db4.t1 VALUES (1)"); + execute_query(Test->conn_rwsplit, "CREATE USER 'table_privilege'@'%%' IDENTIFIED BY 'pass'"); + execute_query(Test->conn_rwsplit, "GRANT SELECT ON db1.* TO 'table_privilege'@'%%'"); + execute_query(Test->conn_rwsplit, "GRANT SELECT ON db2.* TO 'table_privilege'@'%%'"); + execute_query(Test->conn_rwsplit, "GRANT SELECT ON db3.t1 TO 'table_privilege'@'%%'"); + execute_query(Test->conn_rwsplit, "GRANT SELECT ON db4.t1 TO 'table_privilege'@'%%'"); + + Test->repl->sync_slaves(); + + run_test(Test, "db1"); + run_test(Test, "db2"); + run_test(Test, "db3"); + run_test(Test, "db4"); + + Test->tprintf("Cleaning up..."); + Test->set_timeout(60); + Test->connect_maxscale(); + execute_query(Test->conn_rwsplit, "DROP DATABASE db1"); + execute_query(Test->conn_rwsplit, "DROP DATABASE db2"); + execute_query(Test->conn_rwsplit, "DROP DATABASE db3"); + execute_query(Test->conn_rwsplit, "DROP DATABASE db4"); + execute_query(Test->conn_rwsplit, "DROP USER 'table_privilege'@'%%'"); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs720_line_with_no_equal.cpp b/maxscale-system-test/mxs720_line_with_no_equal.cpp new file mode 100644 index 000000000..a601ced77 --- /dev/null +++ b/maxscale-system-test/mxs720_line_with_no_equal.cpp @@ -0,0 +1,26 @@ +/** + * @file max720_line_with_no_equal.cpp mxs720 regression case - first part: line without "=", second - weird lines ("MaxScale fails to start and doesn't log any useful message when there are spurious characters in the config file") + * + * - use incorrect maxscale.cnf + * - check log for error + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + Test->check_log_err((char *) "Failed to pre-parse configuration file", true); + + Test->check_maxscale_processes(0); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs720_wierd_line.cpp b/maxscale-system-test/mxs720_wierd_line.cpp new file mode 100644 index 000000000..02537257e --- /dev/null +++ b/maxscale-system-test/mxs720_wierd_line.cpp @@ -0,0 +1,27 @@ +/** + * @file max720_wierd_line.cpp mxs720 regression case - second part: weird lines ("MaxScale fails to start and doesn't log any useful message when there are spurious characters in the config file") + * + * - use incorrect maxscale.cnf + * - check log for error + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + Test->check_log_err((char *) "Unexpected parameter 'укпоукц'", true); + Test->check_log_err((char *) "Unexpected parameter 'hren'", true); + + Test->check_maxscale_processes(0); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs722.cpp b/maxscale-system-test/mxs722.cpp new file mode 100644 index 000000000..9a94ea9c7 --- /dev/null +++ b/maxscale-system-test/mxs722.cpp @@ -0,0 +1,57 @@ +/** + * @file mxs722.cpp MaxScale configuration check functionality test + * + * - Get baseline for test from a valid config + * - Test wrong parameter name + * - Test wrong router_options value + * - Test wrong filter parameter + * - Test missing config file + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections* test = new TestConnections(argc, argv); + test->stop_timeout(); + test->stop_maxscale(); + + /** Copy original config so we can easily reset the testing environment */ + test->ssh_maxscale(true, "cp /etc/maxscale.cnf /etc/maxscale.cnf.backup"); + + /** Get a baseline result with a good configuration */ + int baseline = test->ssh_maxscale(true, "maxscale -c --user=maxscale"); + + /** Configure bad parameter for a listener */ + test->ssh_maxscale(true, "sed -i -e 's/service/ecivres/' /etc/maxscale.cnf"); + test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), + "Bad parameter name should be detected.\n"); + test->ssh_maxscale(true, "cp /etc/maxscale.cnf.backup /etc/maxscale.cnf"); + + /** Set router_options to a bad value */ + test->ssh_maxscale(true, "sed -i -e 's/router_options.*/router_options=bad_option=true/' /etc/maxscale.cnf"); + test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), + "Bad router_options should be detected.\n"); + + test->ssh_maxscale(true, "cp /etc/maxscale.cnf.backup /etc/maxscale.cnf"); + + /** Configure bad filter parameter */ + test->ssh_maxscale(true, "sed -i -e 's/filebase/basefile/' /etc/maxscale.cnf"); + test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), + "Bad filter parameter should be detected.\n"); + + /** Remove configuration file */ + test->ssh_maxscale(true, "rm -f /etc/maxscale.cnf"); + test->add_result(baseline == test->ssh_maxscale(true, "maxscale -c --user=maxscale"), + "Missing configuration file should be detected.\n"); + + int rval = test->global_result; + delete test; + return rval; +} + diff --git a/maxscale-system-test/mxs729_maxadmin.cpp b/maxscale-system-test/mxs729_maxadmin.cpp new file mode 100644 index 000000000..22c8a1c3e --- /dev/null +++ b/maxscale-system-test/mxs729_maxadmin.cpp @@ -0,0 +1,149 @@ + +/** + * @file mxs729_maxadmin.cpp Test of 'maxadmin' user add/delete + * - try to call Maxadmin as normal user + * - try to call Maxadmin as 'root' user + * - execute 'enable account' + * - try to call Maxadmin using this enable user + * - 'disable accout' + * - try to enable non-existing user with very long name + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +const char * only_root = "Enabled Linux accounts (secure) : \n"; +const char * user_added = "The Linux user %s has successfully been enabled.\n"; +const char * user_removed = "The Linux user %s has successfully been disabled.\n"; +const char * root_added = "User root has been successfully added.\n"; +const char * user_and_root = "Enabled Linux accounts (secure) : %s\n"; +const char * user_only = "Enabled Linux accounts (secure) : %s\n"; + +void add_remove_maxadmin_user(TestConnections* Test) +{ + char str[1024]; + + Test->tprintf("enable account %s to maxadmin:\n", Test->maxscale_access_user); + char * st3 = Test->ssh_maxscale_output(true, "maxadmin enable account %s", Test->maxscale_access_user); + Test->tprintf("Result: %s\n", st3); + sprintf(str, user_added, Test->maxscale_access_user); + if (strstr(st3, str) == NULL) + { + Test->add_result(1, "There is no proper '%s' message\n", str); + } + else + { + Test->tprintf("OK\n"); + } + + Test->tprintf("trying maxadmin without 'root':\n"); + char * st4 = Test->ssh_maxscale_output(false, "maxadmin show users"); + Test->tprintf("Result: %s\n", st4); + sprintf(str, user_only, Test->maxscale_access_user); + if (strstr(st4, str) == NULL) + { + Test->add_result(1, "There is no proper '%s' message\n", str); + } + else + { + Test->tprintf("OK\n"); + } + + Test->tprintf("trying maxadmin with 'root':\n"); + int st5 = Test->ssh_maxscale(true, "maxadmin show users"); + if (st5 != 0) + { + Test->add_result(1, "User added and access to MaxAdmin as 'root' became impossible\n"); + } + else + { + Test->tprintf("OK\n"); + } + + Test->tprintf("trying maxadmin without 'root'\n"); + char * st7 = Test->ssh_maxscale_output(false, "maxadmin show users"); + Test->tprintf("Result: %s\n", st7); + sprintf(str, user_and_root, Test->maxscale_access_user); + if (strstr(st7, str) == NULL) + { + Test->add_result(1, "There is no proper '%s' message\n", str); + } + else + { + Test->tprintf("OK\n"); + } + + Test->tprintf("removing user '%s'\n", Test->maxscale_access_user); + char * st8 = Test->ssh_maxscale_output(true, "maxadmin disable account %s", Test->maxscale_access_user); + Test->tprintf("trying maxadmin with 'root': %s\n", st8); + sprintf(str, user_removed, Test->maxscale_access_user); + if (strstr(st8, str) == NULL) + { + Test->add_result(1, "Wrong output of disable command\n"); + } + else + { + Test->tprintf("OK\n"); + } + + Test->tprintf("Trying with removed user '%s'\n", Test->maxscale_access_user); + int st9 = Test->ssh_maxscale(false, "maxadmin show users"); + if (st9 == 0) + { + Test->add_result(1, "User '%s'' removed, but access to MaxAdmin as '%s' is still possible\n", + Test->maxscale_access_user, Test->maxscale_access_user); + } + else + { + Test->tprintf("OK\n"); + } +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(600); + + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/passwd"); + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/maxadmin-users"); + Test->restart_maxscale(); + + Test->tprintf("trying maxadmin without 'root'\n"); + int st1 = Test->ssh_maxscale(false, "maxadmin show users"); + Test->tprintf("exit code is: %d\n", st1); + if (st1 == 0) + { + Test->add_result(1, "Access to MaxAdmin is possible without 'root' priveleges\n"); + } + + Test->tprintf("trying maxadmin with 'root'\n"); + char * st2 = Test->ssh_maxscale_output(true, "maxadmin show users"); + Test->tprintf("Result: \n %s\n", st2); + if (strstr(st2, only_root) == NULL) + { + Test->add_result(1, "Wrong list of MaxAdmin users\n"); + } + + add_remove_maxadmin_user(Test); + + Test->tprintf("trying long wierd user\n"); + char * st10 = Test->ssh_maxscale_output(true, + "maxadmin enable account yygrgtrпрекури6н33имн756ККККЕН:УИГГГГ*?:*:*fj34oru34h275g23457g2v90590+u764gv56837fbv62381§SDFERGtrg45ergfergergefewfergt456ty"); + /*Test->tprintf("Result: %s\n", st10); + if (strstr(st10, "has been successfully added") == NULL) + { + Test->add_result(1, "Wrong list of MaxAdmin users\n"); + }*/ + + Test->check_maxscale_alive(); + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/passwd"); + Test->ssh_maxscale(true, "rm -rf /var/lib/maxscale/maxadmin-users"); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs781_binlog_wrong_passwrd.cpp b/maxscale-system-test/mxs781_binlog_wrong_passwrd.cpp new file mode 100644 index 000000000..72b93d32a --- /dev/null +++ b/maxscale-system-test/mxs781_binlog_wrong_passwrd.cpp @@ -0,0 +1,74 @@ +/** + * @file mxs781_binlog_wrong_passwrd.cpp Try to configure binlog router to use wrong password for Master and check 'slave status' on binlog + * - try to put wrong password when connect binlog router to real master + * - check binlog router status using 'show slave status', expect 'Slave stopped' + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" + +const char * setup_binlog_wrong_passwrd = + "change master to MASTER_HOST='%s',\ + MASTER_USER='repl',\ + MASTER_PASSWORD='wrong_password',\ + MASTER_LOG_FILE='mar-bin.000001',\ + MASTER_LOG_POS=4,\ + MASTER_PORT=%d"; + + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + char str[1024]; + + Test->tprintf("Connecting to all backend nodes\n"); + Test->add_result(Test->repl->connect(), "Connecting to backed failed\n"); + + Test->prepare_binlog(); + + Test->tprintf("Connecting to MaxScale binlog router (with any DB)\n"); + Test->set_timeout(30); + MYSQL * binlog = open_conn_no_db(Test->binlog_port, Test->maxscale_IP, Test->repl->user_name, + Test->repl->password, Test->ssl); + + Test->add_result(mysql_errno(binlog), "Error connection to binlog router %s\n", mysql_error(binlog)); + + Test->tprintf("'Stop slave' to binlog\n"); + Test->set_timeout(10); + execute_query(binlog, (char *) "stop slave"); + + Test->tprintf("configuring Maxscale binlog router with wrong password\n"); + sprintf(str, setup_binlog_wrong_passwrd, Test->repl->IP[0], Test->repl->port[0]); + Test->tprintf("binlog setup sql: %s\n", str); + Test->set_timeout(10); + execute_query(binlog, str); + Test->tprintf("Error: %s\n", mysql_error(binlog)); + + Test->tprintf("'start slave' to binlog\n"); + Test->set_timeout(10); + execute_query(binlog, "start slave"); + Test->tprintf("Error: %s\n", mysql_error(binlog)); + + Test->stop_timeout(); + sleep(25); + Test->set_timeout(10); + find_field(binlog, (char *) "show slave status", (char *) "Slave_IO_State", str); + Test->add_result(strcasecmp(str, "Slave stopped"), "Wrong slave state: %s\n", str); + + Test->set_timeout(10); + find_field(binlog, (char *) "show slave status", (char *) "Last_Error", str); + Test->add_result(strcasecmp(str, "#28000 Authentication with master server failed"), + "Wrong slave state: %s\n", str); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs791.sh b/maxscale-system-test/mxs791.sh new file mode 100755 index 000000000..0729ae40f --- /dev/null +++ b/maxscale-system-test/mxs791.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +### +## @file mxs791.sh Simple connect test in bash +## - connects to Maxscale, checks that defined in cmd line DB is selected + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name="mxs791.sh" +echo test name is $test_name + +$test_dir/mxs791_base.sh + +res=$? + +$test_dir/copy_logs.sh $test_name +exit $res diff --git a/maxscale-system-test/mxs791_base.sh b/maxscale-system-test/mxs791_base.sh new file mode 100755 index 000000000..f22643e33 --- /dev/null +++ b/maxscale-system-test/mxs791_base.sh @@ -0,0 +1,32 @@ +#!/bin/bash +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +res=0 +echo "Trying RWSplit" +echo "show tables" | mysql -u$maxscale_user -p$maxscale_password -h $maxscale_IP -P 4006 $ssl_option test +if [ $? != 0 ] ; then + res=1 + echo "Can't connect to DB 'test'" +fi + +echo "Trying ReadConn master" +echo "show tables" | mysql -u$maxscale_user -p$maxscale_password -h $maxscale_IP -P 4008 $ssl_options test +if [ $? != 0 ] ; then + res=1 + echo "Can't connect to DB 'test'" +fi + +echo "Trying ReadConn slave" +echo "show tables" | mysql -u$maxscale_user -p$maxscale_password -h $maxscale_IP -P 4009 $ssl_options test +if [ $? != 0 ] ; then + res=1 + echo "Can't connect to DB 'test'" +fi + +exit $res diff --git a/maxscale-system-test/mxs791_galera.sh b/maxscale-system-test/mxs791_galera.sh new file mode 100755 index 000000000..4d9bdb2c0 --- /dev/null +++ b/maxscale-system-test/mxs791_galera.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +### +## @file mxs791.sh Simple connect test in bash +## - connects to Maxscale, checks that defined in cmd line DB is selected + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name="mxs791_galera.sh" +echo test name is $test_name + +$test_dir/mxs791_base.sh + +res=$? + +$test_dir/copy_logs.sh $test_name +exit $res diff --git a/maxscale-system-test/mxs812_1.cpp b/maxscale-system-test/mxs812_1.cpp new file mode 100644 index 000000000..5f7ad1015 --- /dev/null +++ b/maxscale-system-test/mxs812_1.cpp @@ -0,0 +1,82 @@ +/** + * @file mxs812_1.cpp - Checks "Current no. of conns" maxadmin output after long blob inserting + * - set global max_allowed_packet=10000000 + * - pretare statement INSERT INTO long_blob_table(x, b) VALUES(1, ?) + * - load chunks + * - execute statement + * - wait 5 seconds + * - check "Current no. of conns" maxadmin output, expect 0 + * - repeat test 10 times + */ + + +#include "testconnections.h" + +void run_test(TestConnections *Test, size_t size, int chunks) +{ + char *insert_stmt = (char *) "INSERT INTO long_blob_table(x, b) VALUES(1, ?)"; + MYSQL *conn = Test->conn_rwsplit; + MYSQL_STMT * stmt = mysql_stmt_init(conn); + + Test->tprintf("Preparing statement"); + Test->add_result(mysql_stmt_prepare(stmt, insert_stmt, strlen(insert_stmt)), "Error preparing stmt: %s\n", + mysql_stmt_error(stmt)); + + MYSQL_BIND param[1]; + param[0].buffer_type = MYSQL_TYPE_STRING; + param[0].is_null = 0; + + Test->add_result(mysql_stmt_bind_param(stmt, param), "Error parameter binding: %s\n", mysql_stmt_error(stmt)); + + unsigned long *data = (unsigned long *) malloc(size * sizeof(long int)); + + memset(data, '.', size * sizeof(long int)); + + Test->tprintf("Sending %d x %d bytes of data", size, chunks); + for (int i = 0; i < chunks; i++) + { + Test->set_timeout(600); + Test->tprintf("Chunk #%d\n", i); + if (mysql_stmt_send_long_data(stmt, 0, (char *) data, size * sizeof(long int)) != 0) + { + Test->add_result(1, "Error inserting data, iteration %d, error %s\n", i, mysql_stmt_error(stmt)); + return; + } + } + + Test->set_timeout(600); + Test->tprintf("Executing statement"); + Test->add_result(mysql_stmt_execute(stmt), "INSERT Statement with BLOB failed, error is %s\n", + mysql_stmt_error(stmt)); + + Test->stop_timeout(); + sleep(5); + Test->check_current_operations(0); + Test->tprintf("Closing statement"); + Test->add_result(mysql_stmt_close(stmt), "Error closing stmt\n"); +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(300); + int iter = 10; + + Test->repl->execute_query_all_nodes( (char *) "set global max_allowed_packet=10000000"); + + /** Create test table */ + Test->repl->connect(); + Test->try_query(Test->repl->nodes[0], (char*)"DROP TABLE IF EXISTS long_blob_table"); + Test->try_query(Test->repl->nodes[0], (char*)"CREATE TABLE long_blob_table(x INT, b BLOB)"); + + Test->connect_maxscale(); + Test->tprintf("Starting test"); + for (int i = 0; i < iter; i++) + { + run_test(Test, 500000, 10); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs812_2.cpp b/maxscale-system-test/mxs812_2.cpp new file mode 100644 index 000000000..7c99d47e8 --- /dev/null +++ b/maxscale-system-test/mxs812_2.cpp @@ -0,0 +1,101 @@ +/** + * @file mxs812_2.cpp - Execute binary protocol prepared statements while master is blocked, checks "Current no. of conns" after the test + * - start threads which prepares and executes simple statement in the loop + * - every 5 seconds block and after another 5 seconds unblock Master + * - checks "Current no. of conns" after the test, expect 0 + */ + +#include "testconnections.h" + +int test_ps(TestConnections* Test, MYSQL * conn) +{ + const char select_stmt[] = "SELECT ?, ?, ?, ?"; + MYSQL_STMT *stmt = mysql_stmt_init(conn); + + mysql_stmt_prepare(stmt, select_stmt, sizeof(select_stmt) - 1); + + int value = 1; + MYSQL_BIND param[4]; + + param[0].buffer_type = MYSQL_TYPE_LONG; + param[0].is_null = 0; + param[0].buffer = &value; + param[1].buffer_type = MYSQL_TYPE_LONG; + param[1].is_null = 0; + param[1].buffer = &value; + param[2].buffer_type = MYSQL_TYPE_LONG; + param[2].is_null = 0; + param[2].buffer = &value; + param[3].buffer_type = MYSQL_TYPE_LONG; + param[3].is_null = 0; + param[3].buffer = &value; + + mysql_stmt_bind_param(stmt, param); + mysql_stmt_execute(stmt); + mysql_stmt_close(stmt); + + return 0; +} + +static bool running = true; + +void* test_thr(void *data) +{ + TestConnections *Test = (TestConnections*)data; + + while (running) + { + MYSQL *mysql = Test->open_rwsplit_connection(); + + for (int i = 0; i < 3; i++) + { + test_ps(Test, mysql); + } + + mysql_close(mysql); + } +} + +#define THREADS 5 + +int main(int argc, char *argv[]) +{ + TestConnections *Test = new TestConnections(argc, argv); + pthread_t thr[THREADS]; + int iter = 5; + + Test->tprintf("Starting %d query threads", THREADS); + + for (int i = 0; i < THREADS; i++) + { + pthread_create(&thr[i], NULL, test_thr, Test); + } + + for (int i = 0; i < iter; i++) + { + sleep(5); + Test->tprintf("Blocking master"); + Test->repl->block_node(0); + sleep(5); + Test->tprintf("Unblocking master"); + Test->repl->unblock_node(0); + } + + running = false; + + Test->tprintf("Joining threads"); + for (int i = 0; i < THREADS; i++) + { + pthread_join(thr[i], NULL); + } + + Test->stop_timeout(); + sleep(5); + Test->check_maxscale_alive(); + Test->check_current_operations(0); + + int rval = Test->global_result; + delete Test; + + return rval; +} diff --git a/maxscale-system-test/mxs813_long_hostname.cpp b/maxscale-system-test/mxs813_long_hostname.cpp new file mode 100644 index 000000000..b7ab8d63d --- /dev/null +++ b/maxscale-system-test/mxs813_long_hostname.cpp @@ -0,0 +1,47 @@ +/** + * @file mxs813_long_hostname - regression case for crash if long host name is used for binlog router + * - configure binlog router setup + * - stop slave + * - change master to master_host= + * - start slave + * - show slave status + * - show slave status; + * - show slave status\G + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->start_binlog(); + + MYSQL * binlog = open_conn_no_db(Test->binlog_port, Test->maxscale_IP, Test->repl->user_name, + Test->repl->password, Test->ssl); + + Test->tprintf("stop slave\n"); + Test->try_query(binlog, "stop slave"); + Test->tprintf("change master to..\n"); + Test->try_query(binlog, + "change master to master_host='12345678901234567890123456789012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.com';"); + Test->tprintf("start slave\n"); + Test->try_query(binlog, "start slave"); + Test->tprintf("show slave status\n"); + Test->try_query(binlog, "show slave status"); + Test->tprintf("show slave status error: %s\n", mysql_error(binlog)); + execute_query(binlog, "show slave status;"); + execute_query(binlog, "show slave status\\G"); + + mysql_close(binlog); + + Test->check_maxscale_processes(1); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs822_maxpasswd.cpp b/maxscale-system-test/mxs822_maxpasswd.cpp new file mode 100644 index 000000000..0d3d1c9ef --- /dev/null +++ b/maxscale-system-test/mxs822_maxpasswd.cpp @@ -0,0 +1,63 @@ +/** + * @file mxs822_maxpasswd.cpp Regression test for bug MXS-822 ("encrypted passwords containing special characters appear to not work") + * - create .secret with maxkeys + * - generate encripted password with maxpasswd, use password with special characters + * - replace passwords in maxscale.cnf with generated encripted password + * - try to connect to RWSplit + * - restore passwords in maxscale.cnf + * - repeate for several other password with special characters + */ + +#include +#include +#include "testconnections.h" + +using namespace std; + +void try_password(TestConnections* Test, char * pass) +{ + + /** + * Create the user + */ + Test->connect_maxscale(); + execute_query_silent(Test->conn_rwsplit, "DROP USER 'test'@'%'"); + execute_query(Test->conn_rwsplit, "CREATE USER 'test'@'%%' IDENTIFIED BY '%s'", pass); + execute_query(Test->conn_rwsplit, "GRANT ALL ON *.* TO 'test'@'%%'"); + Test->close_maxscale_connections(); + + /** + * Encrypt and change the password + */ + Test->tprintf("Encrypting password: %s", pass); + Test->set_timeout(30); + int rc = Test->ssh_maxscale(true, "maxpasswd '%s' | tr -dc '[:xdigit:]' > /tmp/pw.txt && " + "sed -i 's/user=.*/user=test/' /etc/maxscale.cnf && " + "sed -i \"s/passwd=.*/passwd=$(cat /tmp/pw.txt)/\" /etc/maxscale.cnf && " + "service maxscale restart && " + "sleep 3 && " + "sed -i 's/user=.*/user=maxskysql/' /etc/maxscale.cnf && " + "sed -i 's/passwd=.*/passwd=skysql/' /etc/maxscale.cnf && " + "service maxscale restart", pass); + + Test->add_result(rc, "Failed to encrypt password '%s'", pass); + sleep(3); +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->ssh_maxscale(true, "maxkeys"); + Test->ssh_maxscale(true, "sudo chown maxscale:maxscale /var/lib/maxscale/.secrets"); + + try_password(Test, (char *) "aaa$aaa"); + try_password(Test, (char *) "#¤&"); + try_password(Test, (char *) "пароль"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs827_write_timeout.cpp b/maxscale-system-test/mxs827_write_timeout.cpp new file mode 100644 index 000000000..659c2d0d0 --- /dev/null +++ b/maxscale-system-test/mxs827_write_timeout.cpp @@ -0,0 +1,41 @@ +/** + * @file mxs827_write_timeout "ReadWriteSplit only keeps used connection alive, query crashes after unused connection times out" + * - SET wait_timeout=20 + * - do only SELECT during 30 seconds + * - try INSERT + */ + +#include +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + Test->connect_maxscale(); + + Test->try_query(Test->conn_rwsplit, "SET wait_timeout=20"); + + create_t1(Test->conn_rwsplit); + + for (int i = 0; i < 30; i++) + { + Test->tprintf("Trying query %d\n", i); + Test->set_timeout(10); + Test->try_query(Test->conn_rwsplit, "SELECT 1"); + sleep(1); + } + + Test->try_query(Test->conn_rwsplit, "INSERT INTO t1 VALUES (1, 1)"); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs874_slave_recovery.cpp b/maxscale-system-test/mxs874_slave_recovery.cpp new file mode 100644 index 000000000..f3ebe8856 --- /dev/null +++ b/maxscale-system-test/mxs874_slave_recovery.cpp @@ -0,0 +1,83 @@ +/** + * @file mxs874_slave_recovery.cpp Block and unblock first and second slaves and check that they are recovered + * - Start MaxScale with 1 master and 2 slaves + * - Connect to MaxScale with Readwritesplit + * - Execute SET @a=1 + * - Block first slave + * - Wait until monitor detects it + * - Unblock first slave and block the second slave + * - Check that first slave is recovered + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(10); + + Test->connect_maxscale(); + + Test->set_timeout(10); + Test->try_query(Test->conn_rwsplit, (char *) "SET @a=1"); + Test->stop_timeout(); + sleep(1); + Test->set_timeout(20); + Test->tprintf("Blocking first slave\n"); + Test->repl->block_node(1); + Test->stop_timeout(); + sleep(5); + Test->set_timeout(10); + Test->tprintf("Unblocking first slave and blocking second slave\n"); + + Test->repl->unblock_node(1); + Test->stop_timeout(); + sleep(5); + Test->repl->block_node(2); + Test->stop_timeout(); + sleep(5); + Test->set_timeout(20); + + int retries; + + for (retries = 0; retries < 10; retries++) + { + char server1_status[256]; + Test->get_maxadmin_param((char *) "show server server2", (char *) "Status", server1_status); + if (strstr(server1_status, "Running")) + { + break; + } + sleep(1); + } + + Test->add_result(retries == 10, "Slave is not recovered, slave status is not Running\n"); + + Test->repl->connect(); + int real_id = Test->repl->get_server_id(1); + + char server_id[200] = ""; + find_field(Test->conn_rwsplit, "SELECT @@server_id", "@@server_id", server_id); + int queried_id = atoi(server_id); + + Test->add_result(queried_id != real_id, "The query server ID '%d' does not match the one from server '%d'. " + "Slave was not recovered.", queried_id, real_id); + + char userval[200] = ""; + find_field(Test->conn_rwsplit, "SELECT @a", "@a", userval); + + Test->add_result(atoi(userval) != 1, "User variable @a is not 1, it is '%s'", userval); + + Test->tprintf("Unblocking second slave\n"); + Test->repl->unblock_node(2); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mxs922_bad_server.cpp b/maxscale-system-test/mxs922_bad_server.cpp new file mode 100644 index 000000000..a1411a946 --- /dev/null +++ b/maxscale-system-test/mxs922_bad_server.cpp @@ -0,0 +1,113 @@ +/** + * @file mxs922_bad_server.cpp MXS-922: Server removal test + * + */ + +#include "testconnections.h" + +#define MONITOR_NAME "mysql-monitor" +#define SERVICE_NAME "rwsplit-service" + +void add_servers(TestConnections *test) +{ + test->tprintf("Adding the servers"); + test->set_timeout(120); + + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin add server server%d " MONITOR_NAME, i + 1); + test->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME, i + 1); + } + test->stop_timeout(); +} + +void remove_servers(TestConnections *test) +{ + test->tprintf("Remove the servers"); + test->set_timeout(120); + + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin remove server server%d " MONITOR_NAME, i + 1); + test->ssh_maxscale(true, "maxadmin remove server server%d " SERVICE_NAME, i + 1); + } + test->stop_timeout(); +} + +void destroy_servers(TestConnections *test) +{ + test->tprintf("Destroy the servers"); + test->set_timeout(120); + + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin destroy server server%d", i + 1); + } + test->stop_timeout(); +} + +void do_query(TestConnections *test, bool should_fail) +{ + test->tprintf("Trying to query, expecting %s", should_fail ? "failure" : "success"); + test->set_timeout(120); + + test->connect_maxscale(); + + bool failed = execute_query(test->conn_rwsplit, "select @@server_id") == 0; + + const char *msg = should_fail ? + "Query was successful when failure was expected." : + "Query failed when success was expected."; + + test->add_result(failed == should_fail, msg); + test->close_maxscale_connections(); + + test->stop_timeout(); +} + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + + test->tprintf("Creating servers with bad addresses"); + + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin create server server%d 3306 %s", + i + 1, test->repl->IP[i]); + } + + /** Add the servers to the monitor and service */ + add_servers(test); + + do_query(test, true); + + /** Remove and destroy servers from monitor and service */ + remove_servers(test); + destroy_servers(test); + + test->tprintf("Create the servers with correct parameters"); + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin create server server%d %s %d", i + 1, test->repl->IP[i], test->repl->port[i]); + } + + /** Add the servers again */ + add_servers(test); + + test->tprintf("Wait for the monitor to see the new servers"); + sleep(2); + + do_query(test, false); + + /** Remove everything */ + remove_servers(test); + destroy_servers(test); + + do_query(test, true); + + test->check_maxscale_processes(1); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_double_listener.cpp b/maxscale-system-test/mxs922_double_listener.cpp new file mode 100644 index 000000000..f9610d3a5 --- /dev/null +++ b/maxscale-system-test/mxs922_double_listener.cpp @@ -0,0 +1,27 @@ +/** + * @file mxs922_double_listener.cpp MXS-922: Double creation of listeners + * + * Check that MaxScale doesn't crash when the same listeners are created twice. + */ + +#include "config_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + Config config(test); + + config.create_all_listeners(); + config.create_all_listeners(); + test->check_maxscale_processes(1); + + config.create_monitor("mysql-monitor", "mysqlmon", 500); + config.reset(); + + sleep(1); + + test->check_maxscale_alive(); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_listener_ssl.cpp b/maxscale-system-test/mxs922_listener_ssl.cpp new file mode 100644 index 000000000..53fef7d73 --- /dev/null +++ b/maxscale-system-test/mxs922_listener_ssl.cpp @@ -0,0 +1,31 @@ +/** + * @file mxs922_listener_ssl.cpp MXS-922: Dynamic SSL test + */ + +#include "testconnections.h" +#include "config_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + Config config(test); + + config.create_listener(Config::SERVICE_RWSPLIT); + config.create_monitor("mysql-monitor", "mysqlmon", 500); + config.reset(); + sleep(1); + + test->connect_maxscale(); + test->try_query(test->conn_rwsplit, "select @@server_id") == 0; + config.create_ssl_listener(Config::SERVICE_RCONN_SLAVE); + + MYSQL *conn = open_conn(test->readconn_master_port, test->maxscale_IP, test->maxscale_user, + test->maxscale_password, true); + test->add_result(execute_query(conn, "select @@server_id"), "SSL query failed"); + mysql_close(conn); + + test->check_maxscale_processes(1); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_monitor.cpp b/maxscale-system-test/mxs922_monitor.cpp new file mode 100644 index 000000000..0d3abb9fd --- /dev/null +++ b/maxscale-system-test/mxs922_monitor.cpp @@ -0,0 +1,82 @@ +/** + * @file mxs922_monitor.cpp MXS-922: Monitor creation test + * + */ + +#include "config_operations.h" + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + Config config(test); + + test->tprintf("Creating monitor"); + + config.create_all_listeners(); + config.create_monitor("mysql-monitor", "mysqlmon", 500); + config.reset(); + + sleep(1); + + test->check_maxscale_alive(); + + config.destroy_monitor("mysql-monitor"); + + test->check_maxscale_alive(); + + test->ssh_maxscale(true, "for i in 0 1 2 3; do maxadmin clear server server$i running; done"); + + test->add_result(test->connect_maxscale() == 0, "Should not be able to connect"); + + config.create_monitor("mysql-monitor2", "mysqlmon", 500); + config.add_created_servers("mysql-monitor2"); + + sleep(1); + test->check_maxscale_alive(); + + /** Try to alter the monitor user */ + test->connect_maxscale(); + execute_query(test->conn_rwsplit, "DROP USER 'test'@'%%'"); + execute_query(test->conn_rwsplit, "CREATE USER 'test'@'%%' IDENTIFIED BY 'test'"); + execute_query(test->conn_rwsplit, "GRANT ALL ON *.* TO 'test'@'%%'"); + test->close_maxscale_connections(); + + config.alter_monitor("mysql-monitor2", "user", "test"); + config.alter_monitor("mysql-monitor2", "password", "test"); + + sleep(1); + test->check_maxscale_alive(); + + /** Remove the user */ + test->connect_maxscale(); + execute_query(test->conn_rwsplit, "DROP USER 'test'@'%%'"); + + config.restart_monitors(); + + /** + * Make sure the server are in a bad state. This way we'll know that the + * monitor is running if the states have changed and the query is + * successful. + */ + test->ssh_maxscale(true, "for i in 0 1 2 3; do maxadmin clear server server$i running; done"); + + sleep(1); + test->add_result(execute_query_silent(test->conn_rwsplit, "SELECT 1") == 0, + "Query should fail when monitor has wrong credentials"); + test->close_maxscale_connections(); + + for (int i = 0; i < test->repl->N; i++) + { + config.alter_server(i, "monitoruser", "skysql"); + config.alter_server(i, "monitorpw", "skysql"); + } + + config.restart_monitors(); + sleep(1); + test->check_maxscale_alive(); + + test->check_log_err("Fatal", false); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_restart.cpp b/maxscale-system-test/mxs922_restart.cpp new file mode 100644 index 000000000..a991734ab --- /dev/null +++ b/maxscale-system-test/mxs922_restart.cpp @@ -0,0 +1,78 @@ +/** + * @file mxs922_restart.cpp MXS-922: Test persisting of configuration changes + * + */ + +#include "testconnections.h" + +#define MONITOR_NAME "mysql-monitor" +#define SERVICE_NAME1 "rwsplit-service" +#define SERVICE_NAME2 "read-connection-router-slave" +#define SERVICE_NAME3 "read-connection-router-master" + +void add_servers(TestConnections *test) +{ + test->tprintf("Adding the servers"); + + for (int i = 0; i < 4; i++) + { + test->set_timeout(120); + test->ssh_maxscale(true, "maxadmin add server server%d " MONITOR_NAME, i + 1); + test->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME1, i + 1); + test->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME2, i + 1); + test->ssh_maxscale(true, "maxadmin add server server%d " SERVICE_NAME3, i + 1); + test->stop_timeout(); + } +} + +void do_query(TestConnections *test, bool should_fail) +{ + test->tprintf("Trying to query, expecting %s", should_fail ? "failure" : "success"); + test->set_timeout(120); + + test->connect_maxscale(); + + bool failed = execute_query(test->conn_rwsplit, "select @@server_id") == 0; + + const char *msg = should_fail ? + "Query was successful when failure was expected." : + "Query failed when success was expected."; + + test->add_result(failed == should_fail, msg); + test->close_maxscale_connections(); + + test->stop_timeout(); +} + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + + test->tprintf("Creating servers"); + + for (int i = 0; i < 4; i++) + { + test->ssh_maxscale(true, "maxadmin create server server%d %s", i + 1, test->repl->IP[i]); + } + + /** Add the servers again */ + add_servers(test); + + test->tprintf("Wait for the monitor to see the new servers"); + sleep(2); + + do_query(test, false); + + + test->tprintf("Restarting MaxScale"); + test->restart_maxscale(); + sleep(10); + + do_query(test, false); + + test->check_maxscale_alive(); + test->check_log_err("Fatal", false); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_scaling.cpp b/maxscale-system-test/mxs922_scaling.cpp new file mode 100644 index 000000000..9f79d70c9 --- /dev/null +++ b/maxscale-system-test/mxs922_scaling.cpp @@ -0,0 +1,93 @@ +/** + * @file mxs922_scaling.cpp MXS-922: Server scaling test + * + */ + +#include "testconnections.h" +#include "config_operations.h" + +static bool running = true; + +void* query_thread(void *data) +{ + TestConnections *test = static_cast(data); + + MYSQL *mysql = test->open_rwsplit_connection(); + my_bool yes = true; + mysql_options(mysql, MYSQL_OPT_RECONNECT, &yes); + + while (running) + { + execute_query_silent(mysql, "SELECT @@server_id"); + execute_query_silent(mysql, "SELECT last_insert_id()"); + } + + mysql_close(mysql); + + return NULL; +} + + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + Config config(test); + + config.create_all_listeners(); + config.create_monitor("mysql-monitor", "mysqlmon", 500); + + int num_threads = 5; + int iterations = test->smoke ? 5 : 25; + pthread_t threads[num_threads]; + + test->tprintf("Creating client threads"); + + for (int i = 0; i < num_threads; i++) + { + pthread_create(&threads[i], NULL, query_thread, test); + } + + + test->tprintf("Adding and removing servers for %d seconds.", iterations * test->repl->N); + + for (int x = 0; x < iterations; x++) + { + for (int i = 0; i < test->repl->N; i++) + { + if ((x + i) % 2 == 0) + { + config.create_server(i); + config.add_server(i); + } + else + { + config.remove_server(i); + config.destroy_server(i); + } + + sleep(1); + } + } + + running = false; + + for (int i = 0; i < num_threads; i++) + { + pthread_join(threads[i], NULL); + } + + /** Make sure the servers exist before checking that connectivity is OK */ + for (int i = 0; i < test->repl->N; i++) + { + config.create_server(i); + config.add_server(i); + } + + sleep(1); + + test->check_maxscale_alive(); + test->check_log_err("Fatal", false); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs922_server.cpp b/maxscale-system-test/mxs922_server.cpp new file mode 100644 index 000000000..f5f150acf --- /dev/null +++ b/maxscale-system-test/mxs922_server.cpp @@ -0,0 +1,99 @@ +/** + * @file mxs922_server.cpp MXS-922: Server creation test + * + */ + +#include "testconnections.h" +#include "config_operations.h" + +int check_server_id(TestConnections *test, int idx) +{ + test->close_maxscale_connections(); + test->connect_maxscale(); + + int a = test->repl->get_server_id(idx); + int b = -1; + char str[1024]; + + if (find_field(test->conn_rwsplit, "SELECT @@server_id", "@@server_id", str) == 0) + { + b = atoi(str); + } + + return a - b; +} + +int main(int argc, char *argv[]) +{ + TestConnections *test = new TestConnections(argc, argv); + Config config(test); + + config.create_all_listeners(); + config.create_monitor("mysql-monitor", "mysqlmon", 500); + + test->tprintf("Testing server creation and destruction"); + + config.create_server(1); + config.create_server(1); + config.check_server_count(1); + config.destroy_server(1); + config.destroy_server(1); + config.check_server_count(0); + test->check_maxscale_processes(1); + + test->tprintf("Testing adding of server to service"); + + config.create_server(1); + config.add_server(1); + config.check_server_count(1); + sleep(1); + test->check_maxscale_alive(); + config.remove_server(1); + config.destroy_server(1); + config.check_server_count(0); + + test->tprintf("Testing altering of server"); + + config.create_server(1); + config.add_server(1); + config.alter_server(1, "address", test->repl->IP[1]); + sleep(1); + test->check_maxscale_alive(); + config.alter_server(1, "address", "This-is-not-the-address-you-are-looking-for"); + config.alter_server(1, "port", 12345); + test->connect_maxscale(); + test->add_result(execute_query_silent(test->conn_rwsplit, "SELECT 1") == 0, + "Query with bad address should fail"); + + config.remove_server(1); + config.destroy_server(1); + + + test->tprintf("Testing server weights"); + + config.reset(); + sleep(1); + test->repl->connect(); + + config.alter_server(1, "weight", 1); + config.alter_server(2, "weight", 1); + config.alter_server(3, "weight", 1000); + test->add_result(check_server_id(test, 3), "The server_id values don't match"); + + config.alter_server(1, "weight", 1); + config.alter_server(2, "weight", 1000); + config.alter_server(3, "weight", 1); + test->add_result(check_server_id(test, 2), "The server_id values don't match"); + + config.alter_server(1, "weight", 1000); + config.alter_server(2, "weight", 1); + config.alter_server(3, "weight", 1); + test->add_result(check_server_id(test, 1), "The server_id values don't match"); + + config.reset(); + sleep(1); + test->check_maxscale_alive(); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/mxs951_utfmb4.cpp b/maxscale-system-test/mxs951_utfmb4.cpp new file mode 100644 index 000000000..c29fa0d8f --- /dev/null +++ b/maxscale-system-test/mxs951_utfmb4.cpp @@ -0,0 +1,66 @@ +/** + * @file mxs951_utfmb4_galera.cpp Set utf8mb4 in the backend and restart Maxscale + * - add following to backend server configuration: + @verbatim +[mysqld] +character_set_server=utf8mb4 +collation_server=utf8mb4_unicode_520_ci + @endverbatim + * - for all backend nodes: SET GLOBAL character_set_server = 'utf8mb4'; SET NAMES 'utf8mb4' + * - restart Maxscale + * - connect to Maxscale + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->stop_timeout(); + + char cmd [1024]; + sprintf(cmd, "%s/utf64.cnf", test_dir); + for (int i = 0; i < Test->repl->N; i++) + { + Test->repl->copy_to_node(cmd, (char *) "./", i); + Test->repl->ssh_node(i, (char *) "cp ./utf64.cnf /etc/my.cnf.d/", true); + } + + Test->repl->start_replication(); + + + Test->tprintf("Set utf8mb4 for backend"); + Test->repl->execute_query_all_nodes((char *) "SET GLOBAL character_set_server = 'utf8mb4';"); + + Test->tprintf("Set names to utf8mb4 for backend"); + Test->repl->execute_query_all_nodes((char *) "SET NAMES 'utf8mb4';"); + + Test->set_timeout(120); + + Test->tprintf("Restart Maxscale"); + Test->restart_maxscale(); + + sleep(10); + + Test->check_maxscale_alive(); + //Test->check_maxscale_processes(0); + + Test->stop_timeout(); + Test->tprintf("Restore backend configuration\n"); + for (int i = 0; i < Test->repl->N; i++) + { + Test->repl->ssh_node(i, (char *) "rm /etc/my.cnf.d/utf64.cnf", true); + } + Test->repl->start_replication(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/mxs957.cpp b/maxscale-system-test/mxs957.cpp new file mode 100644 index 000000000..0d9e653db --- /dev/null +++ b/maxscale-system-test/mxs957.cpp @@ -0,0 +1,56 @@ +/** + * @file mxs957.cpp Execute given SQL through readwritesplit (with temporary tables usage) + * + * + * Execute the following SQL through readwritesplit without errors. + * + * CREATE OR REPLACE TABLE t1(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY); + * CREATE OR REPLACE TABLE t2(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY); + * CREATE TEMPORARY TABLE temp1(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY); + * INSERT INTO temp1 values (1), (2), (3); + * INSERT INTO t1 values (1), (2), (3); + * INSERT INTO t2 values (1), (2), (3); + * CREATE TEMPORARY TABLE temp2 + * SELECT DISTINCT p.id FROM temp1 p JOIN t1 t ON (t.id = p.id) + * LEFT JOIN t2 ON (t.id = t2.id) + * WHERE p.id IS NOT NULL AND @@server_id IS NOT NULL; + * SELECT * FROM temp2; + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +const char* queries[] = +{ + "USE test", + "CREATE OR REPLACE TABLE t1(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY)", + "CREATE OR REPLACE TABLE t2(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY)", + "CREATE TEMPORARY TABLE temp1(`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY)", + "INSERT INTO temp1 values (1), (2), (3)", + "INSERT INTO t1 values (1), (2), (3)", + "INSERT INTO t2 values (1), (2), (3)", + "CREATE TEMPORARY TABLE temp2 SELECT DISTINCT p.id FROM temp1 p JOIN t1 t ON (t.id = p.id) LEFT JOIN t2 ON (t.id = t2.id) WHERE p.id IS NOT NULL AND @@server_id IS NOT NULL", + "SELECT * FROM temp2", + "DROP TABLE t1", + "DROP TABLE t2", + NULL +}; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->connect_maxscale(); + + for (int i = 0; queries[i]; i++) + { + Test->set_timeout(30); + Test->try_query(Test->conn_rwsplit, queries[i]); + } + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/mysqlmon_backup.cpp b/maxscale-system-test/mysqlmon_backup.cpp new file mode 100644 index 000000000..3fccd5e2f --- /dev/null +++ b/maxscale-system-test/mysqlmon_backup.cpp @@ -0,0 +1,112 @@ +/** + * MySQL Monitor crash safety test + * + * - Start MaxScale + * - Kill slaves to trigger stale master status + * - Kill MaxScale process and restart MaxScale + * - Expect stale master status to still exist for the master + */ + +#include "testconnections.h" + +void check_master(TestConnections& test) +{ + test.add_result(test.find_master_maxadmin(test.repl) != 0, "Node 0 is not the master"); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "INSERT INTO test.t1 VALUES (1)"); + test.close_maxscale_connections(); +} + +void check_slave(TestConnections& test) +{ + test.add_result(test.find_slave_maxadmin(test.repl) == -1, "No slaves found"); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "SELECT * FROM test.t1"); + test.close_maxscale_connections(); +} + +void kill_maxscale(TestConnections& test) +{ + test.tprintf("Killing and restarting MaxScale"); + test.ssh_maxscale(true, "pkill -9 maxscale"); + test.start_maxscale(); + + test.tprintf("Waiting for MaxScale to start"); + sleep(10); +} + +void restart_maxscale(TestConnections& test) +{ + test.restart_maxscale(); + test.tprintf("Waiting for MaxScale to start"); + sleep(10); +} + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "CREATE OR REPLACE TABLE test.t1(id int)"); + test.close_maxscale_connections(); + + test.tprintf("Checking that node 0 is the master and slaves are OK"); + check_master(test); + check_slave(test); + + test.tprintf("Blocking slaves to trigger stale master status"); + test.repl->block_node(1); + test.repl->block_node(2); + test.repl->block_node(3); + sleep(5); + + test.tprintf("Checking that master has stale status"); + check_master(test); + + kill_maxscale(test); + + test.tprintf("Checking that master still has stale status"); + check_master(test); + + restart_maxscale(test); + + test.tprintf("Checking that master has stale status after restart"); + check_master(test); + + test.repl->unblock_node(1); + test.repl->unblock_node(2); + test.repl->unblock_node(3); + sleep(5); + + test.tprintf("Checking that node 0 is the master and slaves are OK"); + check_master(test); + check_slave(test); + + test.tprintf("Blocking master to trigger stale slave status"); + test.repl->block_node(0); + sleep(5); + + test.tprintf("Checking that slaves have stale status"); + check_slave(test); + + kill_maxscale(test); + + test.tprintf("Checking that slaves still have stale status"); + check_slave(test); + + restart_maxscale(test); + + test.tprintf("Checking that slaves have stale status after restart"); + check_slave(test); + + test.repl->unblock_node(0); + sleep(5); + + test.tprintf("Checking that node 0 is the master and slaves are OK"); + check_master(test); + check_slave(test); + + return test.global_result; +} diff --git a/maxscale-system-test/mysqltest_driver.sh b/maxscale-system-test/mysqltest_driver.sh new file mode 100755 index 000000000..212561d61 --- /dev/null +++ b/maxscale-system-test/mysqltest_driver.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# First argument is the name of the test +# Second argument is the directory name where tests are found +# Third argument defines the MaxScale port +# Fourth OPTIONAL argument defines the user to be used. +# Fifth OPTIONAL argument defines the password to be used. + +if [ $# -lt 3 ] +then + echo "Usage: NAME TESTDIR PORT [USER] [PASSWORD]" + exit 1 +fi + +if [ "$maxscale_IP" == "" ] +then + echo "Error: The environment variable maxscale_IP must be set." + exit 1 +fi + +if [ $# -ge 5 ] +then + password=$5 +else + password=skysql +fi + +if [ $# -ge 4 ] +then + user=$4 +else + user=skysql +fi + +# Prepare the test environment +test_dir=`pwd` +port=$3 + +$test_dir/non_native_setup $1 + +cd $2 || exit 1 + +res=0 + +# Create a directory for the mysqltest logs +[ -d log ] && rm -r log +mkdir log || exit 1 + +echo + +# Run the test +for t in `cd t; ls *.test` +do + printf "$t:" + test_name=${t%%.test} + mysqltest --host=$maxscale_IP --port=$port \ + --user=$user --password=$password \ + --logdir=log \ + --test-file=t/$test_name.test \ + --result-file=r/$test_name.result \ + --silent + + if [ $? -eq 0 ] + then + echo " OK" + else + echo " FAILED" + res=1 + fi +done + +echo + +# Copy logs from the VM +$test_dir/copy_logs.sh $1 + +exit $res diff --git a/maxscale-system-test/namedserverfilter.cpp b/maxscale-system-test/namedserverfilter.cpp new file mode 100644 index 000000000..0aac2c734 --- /dev/null +++ b/maxscale-system-test/namedserverfilter.cpp @@ -0,0 +1,45 @@ +/** + * @file namedserverfilter.cpp Namedserverfilter test + * + * Check that a readwritesplit service with a namedserverfilter will route a + * SELECT @@server_id to the correct server. The filter is configured with + * `match=SELECT` which should match any SELECT query. + */ + + +#include +#include "testconnections.h" + +using namespace std; + +int compare_server_id(TestConnections* test, char *node_id) +{ + char str[1024]; + int rval = 0; + if (find_field(test->conn_rwsplit, "SELECT @@server_id", "@@server_id", str)) + { + test->tprintf("Failed to query for @@server_id.\n"); + rval = 1; + } + else if (strcmp(node_id, str)) + { + test->tprintf("@@server_id is %s instead of %s\n", str, node_id); + rval = 1; + } + return rval; +} + +int main(int argc, char **argv) +{ + TestConnections *test = new TestConnections(argc, argv); + test->repl->connect(); + char server_id[1024]; + + sprintf(server_id, "%d", test->repl->get_server_id(1)); + test->tprintf("Server ID of server2 is: %s\n", server_id); + test->add_result(test->connect_rwsplit(), "Test failed to connect to MaxScale.\n"); + test->add_result(compare_server_id(test, server_id), "Test failed, server ID was not correct.\n"); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/no_password.cpp b/maxscale-system-test/no_password.cpp new file mode 100644 index 000000000..8eeac22d9 --- /dev/null +++ b/maxscale-system-test/no_password.cpp @@ -0,0 +1,24 @@ +/** + * Check that using no password returns correct error message + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + + MYSQL *mysql = open_conn(test.rwsplit_port, test.maxscale_IP, "testuser", NULL, false); + test.add_result(mysql_errno(mysql) == 0, "Connecting to MaxScale should fail"); + test.add_result(strstr(mysql_error(mysql), "using password: NO") == NULL, "Missing (using password: NO) error message, got this instead: %s", mysql_error(mysql)); + test.tprintf("MySQL error: %s", mysql_error(mysql)); + mysql_close(mysql); + + open_conn(test.rwsplit_port, test.maxscale_IP, "testuser", "testpassword", false); + test.add_result(mysql_errno(mysql) == 0, "Connecting to MaxScale should fail"); + test.add_result(strstr(mysql_error(mysql), "using password: YES") == NULL, "Missing (using password: YES) error message, got this instead: %s", mysql_error(mysql)); + test.tprintf("MySQL error: %s", mysql_error(mysql)); + mysql_close(mysql); + + return test.global_result; +} diff --git a/maxscale-system-test/non_native_setup.cpp b/maxscale-system-test/non_native_setup.cpp new file mode 100644 index 000000000..166d90d33 --- /dev/null +++ b/maxscale-system-test/non_native_setup.cpp @@ -0,0 +1,30 @@ +/** + * @file Simple dummy configuration program for non-C++ tests + * - Configure Maxscale (prepare maxscale.cnf and copy it to Maxscale machine) + * - check backends + * - try to restore broken backends + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + return 1; + } + + int local_argc = argc - 1; + char** local_argv = &argv[1]; + + TestConnections * Test = new TestConnections(local_argc, local_argv); + + sleep(3); + + return 0; +} diff --git a/maxscale-system-test/nsfilter.py b/maxscale-system-test/nsfilter.py new file mode 100755 index 000000000..329be7886 --- /dev/null +++ b/maxscale-system-test/nsfilter.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python + +import maxpython + +test = maxpython.MaxScaleTest("nsfilter") + +server_id = [] + +for conn in test.repl: + server_id[conn] = conn.query("SELECT @@server_id") + +nomatch = test.maxscale['rwsplit'].query("SELECT @@server_id") +match = test.maxscale['rwsplit'].query("SELECT \"test\", @@server_id") + +print(nomatch) +print(match) diff --git a/maxscale-system-test/open_close_connections.cpp b/maxscale-system-test/open_close_connections.cpp new file mode 100644 index 000000000..6dca01fa5 --- /dev/null +++ b/maxscale-system-test/open_close_connections.cpp @@ -0,0 +1,110 @@ +/** + * @file open_close_connections.cpp Simple test which creates load which is very short sessions + * + * - 20 threads are opening and immediatelly closing connection in the loop + */ + +#include "testconnections.h" + +typedef struct +{ + int exit_flag; + int thread_id; + long i; + int rwsplit_only; + TestConnections * Test; +} openclose_thread_data; + +void *query_thread1(void *ptr); +int threads_num = 20; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int run_time = Test->smoke ? 10 : 300; + + openclose_thread_data data[threads_num]; + for (int i = 0; i < threads_num; i++) + { + data[i].i = 0; + data[i].exit_flag = 0; + data[i].Test = Test; + data[i].thread_id = i; + } + + // Tuning these kernel parameters removes any system limitations on how many + // connections can be created within a short period + Test->ssh_maxscale(true, "sysctl net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1 " + "net.core.somaxconn=10000 net.ipv4.tcp_max_syn_backlog=10000"); + + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 50000;"); + Test->repl->sync_slaves(); + + pthread_t thread1[threads_num]; + + /* Create independent threads each of them will execute function */ + for (int i = 0; i < threads_num; i++) + { + pthread_create(&thread1[i], NULL, query_thread1, &data[i]); + } + + Test->tprintf("Threads are running %d seconds \n", run_time); + + for (int i = 0; i < run_time && Test->global_result == 0; i++) + { + sleep(1); + } + + for (int i = 0; i < threads_num; i++) + { + data[i].exit_flag = 1; + pthread_join(thread1[i], NULL); + } + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + +void *query_thread1( void *ptr ) +{ + openclose_thread_data * data = (openclose_thread_data *) ptr; + + while (data->exit_flag == 0 && data->Test->global_result == 0) + { + MYSQL *conn1 = data->Test->open_rwsplit_connection(); + data->Test->add_result(mysql_errno(conn1), + "Error opening RWsplit conn, thread num is %d, iteration %d, error is: %s\n", + data->thread_id, data->i, mysql_error(conn1)); + MYSQL *conn2 = data->Test->open_readconn_master_connection(); + data->Test->add_result(mysql_errno(conn2), + "Error opening ReadConn master conn, thread num is %d, iteration %d, error is: %s\n", data->thread_id, + data->i, mysql_error(conn2)); + MYSQL *conn3 = data->Test->open_readconn_slave_connection(); + data->Test->add_result(mysql_errno(conn3), + "Error opening ReadConn master conn, thread num is %d, iteration %d, error is: %s\n", data->thread_id, + data->i, mysql_error(conn3)); + // USE test here is a hack to prevent Maxscale from failure; should be removed when fixed + if (conn1 != NULL) + { + data->Test->try_query(conn1, (char*) "USE test"); + mysql_close(conn1); + } + + if (conn2 != NULL) + { + data->Test->try_query(conn2, (char*) "USE test"); + mysql_close(conn2); + } + if (conn3 != NULL) + { + data->Test->try_query(conn3, (char*) "USE test"); + mysql_close(conn3); + } + + data->i++; + } + + return NULL; +} diff --git a/maxscale-system-test/pers_01.cpp b/maxscale-system-test/pers_01.cpp new file mode 100644 index 000000000..314a50f3b --- /dev/null +++ b/maxscale-system-test/pers_01.cpp @@ -0,0 +1,212 @@ +/** + * @file pers_01.cpp - Persistent connection tests + * configuration: + * @verbatim +[server1] +type=server +address=###node_server_IP_1### +port=###node_server_port_1### +protocol=MySQLBackend +persistpoolmax=1 +persistmaxtime=3660 + +[server2] +type=server +address=###node_server_IP_2### +port=###node_server_port_2### +protocol=MySQLBackend +persistpoolmax=5 +persistmaxtime=60 + +[server3] +type=server +address=###node_server_IP_3### +port=###node_server_port_3### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=60 + +[server4] +type=server +address=###node_server_IP_4### +port=###node_server_port_4### +protocol=MySQLBackend +persistpoolmax=30 +persistmaxtime=30 + +[gserver1] +type=server +address=###galera_server_IP_1### +port=###galera_server_port_1### +protocol=MySQLBackend +persistpoolmax=10 +persistmaxtime=3660 + +[gserver2] +type=server +address=###galera_server_IP_2### +port=###galera_server_port_2### +protocol=MySQLBackend +persistpoolmax=15 +persistmaxtime=30 + +[gserver3] +type=server +address=###galera_server_IP_3### +port=###galera_server_port_3### +protocol=MySQLBackend +persistpoolmax=19 +persistmaxtime=0 + +[gserver4] +type=server +address=###galera_server_IP_4### +port=###galera_server_port_4### +protocol=MySQLBackend +persistpoolmax=0 +persistmaxtime=3660 + + +@endverbatim + * open 70 connections to all Maxscale services + * close connections + * TEST1: check value of "Persistent measured pool size" parameter in 'maxadmin' output, expect: + @verbatim +server1: 1 +server2: 5 +server3: 10 +server4: 30 +gserver1: 10 +gserver2: 15 +gserver3: 0 +gserver4: 0 +@endverbatim + * Test2: wait 10 seconds, check "Persistent measured pool size" again. expect the same + * Test3: wait 30 seconds more, expect: +@verbatim +server1: 1 +server2: 5 +server3: 10 +server4: 0 +gserver1: 10 +gserver2: 0 +gserver3: 0 +gserver4: 0 +@endverbatim + + */ + + +#include "testconnections.h" +#include "maxadmin_operations.h" + +void check_pers_conn(TestConnections* Test, int pers_conn_expected[], char * server) +{ + char result[1024]; + char str[256]; + int pers_conn[4]; + + for (int i = 0; i < 4; i++) + { + sprintf(str, "show server %s%d", server, i + 1); + Test->get_maxadmin_param(str, (char *) "Persistent measured pool size:", result); + Test->tprintf("%s: %s\n", str, result); + sscanf(result, "%d", &pers_conn[i]); + if (pers_conn[i] != pers_conn_expected[i]) + { + Test->add_result(1, "Persistent measured pool size: %s%d has %d, but expected %d\n", server, i + 1, + pers_conn[i], pers_conn_expected[i]); + } + } +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + int pers_conn_expected[4]; + int galera_pers_conn_expected[4]; + + + pers_conn_expected[0] = 1; + pers_conn_expected[1] = 5; + pers_conn_expected[2] = 10; + pers_conn_expected[3] = 30; + + galera_pers_conn_expected[0] = 10; + galera_pers_conn_expected[1] = 15; + galera_pers_conn_expected[2] = 0; + galera_pers_conn_expected[3] = 0; + + Test->restart_maxscale(); + + Test->add_result(Test->create_connections(70, true, true, true, true), + "Error creating connections\n"); + sleep(5); + Test->set_timeout(20); + + Test->tprintf("Test 1:\n"); + check_pers_conn(Test, pers_conn_expected, (char *) "server"); + + Test->tprintf("Galera: \n"); + check_pers_conn(Test, galera_pers_conn_expected, (char *) "gserver"); + + Test->stop_timeout(); + + Test->tprintf("Sleeping 10 seconds\n"); + sleep(10); + + Test->set_timeout(20); + Test->tprintf("Test 2:\n"); + check_pers_conn(Test, pers_conn_expected, (char *) "server"); + + printf("Galera: \n"); + check_pers_conn(Test, galera_pers_conn_expected, (char *) "gserver"); + + Test->tprintf("Sleeping 30 seconds\n"); + Test->stop_timeout(); + sleep(30); + + Test->set_timeout(20); + printf("Test 3:\n"); + + pers_conn_expected[0] = 1; + pers_conn_expected[1] = 5; + pers_conn_expected[2] = 10; + pers_conn_expected[3] = 0; + + galera_pers_conn_expected[0] = 10; + galera_pers_conn_expected[1] = 0; + galera_pers_conn_expected[2] = 0; + galera_pers_conn_expected[3] = 0; + + check_pers_conn(Test, pers_conn_expected, (char *) "server"); + + Test->tprintf("Galera: \n"); + check_pers_conn(Test, galera_pers_conn_expected, (char *) "gserver"); + + Test->tprintf("Sleeping 30 seconds\n"); + Test->stop_timeout(); + sleep(30); + Test->set_timeout(20); + + Test->tprintf("Test 3:\n"); + + pers_conn_expected[0] = 1; + pers_conn_expected[1] = 0; + pers_conn_expected[2] = 0; + pers_conn_expected[3] = 0; + + galera_pers_conn_expected[0] = 10; + galera_pers_conn_expected[1] = 0; + galera_pers_conn_expected[2] = 0; + galera_pers_conn_expected[3] = 0; + + check_pers_conn(Test, pers_conn_expected, (char *) "server"); + + Test->tprintf("Galera: \n"); + check_pers_conn(Test, galera_pers_conn_expected, (char *) "gserver"); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/pers_02.cpp b/maxscale-system-test/pers_02.cpp new file mode 100644 index 000000000..af588cecb --- /dev/null +++ b/maxscale-system-test/pers_02.cpp @@ -0,0 +1,43 @@ +/** + * @file pers_02.cpp - Persistent connection tests - crash during Maxscale restart + * + * - Set max_connections to 20 + * - Open 75 connections to all Maxscale services + * - Close connections + * - Restart replication (stop all nodes and start them again, execute CHANGE MASTER TO again) + * - Set max_connections to 2000 + * - Open 70 connections to all Maxscale services + * - Close connections + * - Check there is not crash during restart + */ + + +#include "testconnections.h" +#include "maxadmin_operations.h" + + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(60); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 20;"); + Test->create_connections(75, true, true, true, true); + + Test->stop_timeout(); + Test->repl->stop_nodes(); + Test->repl->start_replication(); + Test->repl->close_connections(); + Test->repl->sync_slaves(); + + Test->set_timeout(60); + Test->repl->execute_query_all_nodes((char *) "set global max_connections = 2000;"); + Test->add_result(Test->create_connections(70 , true, true, true, true), + "Connections creation error \n"); + + Test->check_log_err((char *) "fatal signal 11", false); + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/prepared_statement.cpp b/maxscale-system-test/prepared_statement.cpp new file mode 100644 index 000000000..d58206d0c --- /dev/null +++ b/maxscale-system-test/prepared_statement.cpp @@ -0,0 +1,58 @@ +/** + * @file prepared_statement.cpp Checks if prepared statement works via Maxscale + * + * - Create table t1 and fill it ith some data + * - via RWSplit: + * + PREPARE stmt FROM 'SELECT * FROM t1 WHERE fl=@x;'; + * + SET @x = 3;") + * + EXECUTE stmt") + * + SET @x = 4;") + * + EXECUTE stmt") + * - check if Maxscale is alive + */ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(60); + int N = 4; + + Test->repl->connect(); + if (Test->connect_maxscale() != 0 ) + { + printf("Error connecting to MaxScale\n"); + delete Test; + exit(1); + } + + create_t1(Test->conn_rwsplit); + insert_into_t1(Test->conn_rwsplit, N); + + Test->set_timeout(20); + Test->try_query(Test->conn_rwsplit, (char *) "PREPARE stmt FROM 'SELECT * FROM t1 WHERE fl=@x;';"); + Test->try_query(Test->conn_rwsplit, (char *) "SET @x = 3;"); + Test->try_query(Test->conn_rwsplit, (char *) "EXECUTE stmt"); + Test->try_query(Test->conn_rwsplit, (char *) "SET @x = 4;"); + Test->try_query(Test->conn_rwsplit, (char *) "EXECUTE stmt"); + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + + +/* + Hi Timofey, I can't imagine repeatable way to run in to the situation where session command replies would arrive in different order, at least without additional instrumentation. You can, however, increase the probability for it to occur by setting up master and multiple slaves, the more the better. + +Then start to prepare statements which return much data. The length of response packet depends on number of columns so good query would be something that produces lots of columns, like select a.user, b.user, c.user, ... z.user from mysql.user a, mysql.user b, mysql.user c, ... mysql.user z + +I'm not sure that it will happen but it is possible. You can also try to make it happen by decreasing the size of network packet because the smaller that is the more likely it is that responses are split into multiple packets - which can then becomen interleaved with packets from different slaves. +*/ diff --git a/maxscale-system-test/rds_vpc.cpp b/maxscale-system-test/rds_vpc.cpp new file mode 100644 index 000000000..6f9ed2bcc --- /dev/null +++ b/maxscale-system-test/rds_vpc.cpp @@ -0,0 +1,796 @@ +#include "execute_cmd.h" +#include "rds_vpc.h" + +RDS::RDS(char * cluster) +{ + cluster_name_intern = cluster; + subnets_intern = NULL; + N_intern = 0; +} + +const char * RDS::get_instance_name(json_t * instance) +{ + json_t * instance_name = json_object_get(instance, "DBInstanceIdentifier"); + return json_string_value(instance_name); +} + +json_t * RDS::get_cluster_descr(char * json) +{ + json_t *root; + json_error_t error; + + root = json_loads( json, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return NULL; + } + + json_t * clusters = json_object_get(root, "DBClusters"); + //cluster_intern = + return json_array_get(clusters, 0); +} + +json_t * RDS::get_subnets_group_descr(char * json) +{ + json_t *root; + json_error_t error; + char * j; + + root = json_loads( json, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return NULL; + } + + json_t * subnets = json_object_get(root, "DBSubnetGroups"); + return json_array_get(subnets, 0); +} + +json_t * RDS::get_cluster_nodes() +{ + return get_cluster_nodes(cluster_intern); +} + +json_t * RDS::get_cluster_nodes(json_t *cluster) +{ + json_t * members = json_object_get(cluster, "DBClusterMembers"); + size_t members_N = json_array_size(members); + json_t * member; + json_t * node_names = json_array(); + + for (size_t i = 0; i < members_N; i++) + { + member = json_array_get(members, i); + json_array_append(node_names, json_string(get_instance_name(member))); + } + return node_names; +} + +json_t * RDS::get_subnets() +{ + char cmd[1024]; + char *result; + sprintf(cmd, "aws rds describe-db-subnet-groups --db-subnet-group-name %s", subnets_group_name_intern); + if (execute_cmd(cmd, &result) != 0) + { + return NULL; + } + + json_t * subnets_group = get_subnets_group_descr(result); + + json_t * members = json_object_get(subnets_group, "Subnets"); + vpc_id_intern = json_string_value(json_object_get(subnets_group, "VpcId")); + size_t members_N = json_array_size(members); + json_t * member; + json_t * subnets_names = json_array(); + + for (size_t i = 0; i < members_N; i++) + { + member = json_array_get(members, i); + json_array_append(subnets_names, json_object_get(member, "SubnetIdentifier")); + } + subnets_intern = subnets_names; + return subnets_names; +} + +const char * RDS::get_subnetgroup_name() +{ + if (cluster_intern != NULL) + { + subnets_group_name_intern = json_string_value(json_object_get(cluster_intern, "DBSubnetGroup")); + } + else + { + subnets_group_name_intern = cluster_name_intern; + } + + return subnets_group_name_intern; +} + +json_t * RDS::get_cluster() +{ + char cmd[1024]; + char *result; + sprintf(cmd, "aws rds describe-db-clusters --db-cluster-identifier=%s", cluster_name_intern); + execute_cmd(cmd , &result); + return get_cluster_descr(result); +} + +int RDS::destroy_nodes(json_t * node_names) +{ + size_t N = json_array_size(node_names); + + char cmd[1024]; + char *res; + json_t * node; + int err = 0; + for (size_t i = 0; i < N; i++) + { + node = json_array_get(node_names, i); + sprintf(cmd, "aws rds delete-db-instance --skip-final-snapshot --db-instance-identifier=%s", + json_string_value(node)); + printf("%s\n", cmd); + if (execute_cmd(cmd, &res) != 0) + { + err = -1; + fprintf( stderr, "error: can not delete node %s\n", json_string_value(node)); + } + } + return err; +} + +int RDS::destroy_subnets() +{ + size_t N = json_array_size(subnets_intern); + + char cmd[1024]; + char *res; + json_t * subnet; + int err = 0; + for (size_t i = 0; i < N; i++) + { + subnet = json_array_get(subnets_intern, i); + sprintf(cmd, "aws ec2 delete-subnet --subnet-id=%s", json_string_value(subnet)); + printf("%s\n", cmd); + execute_cmd(cmd, &res); + if (execute_cmd(cmd, &res) != 0) + { + err = -1; + fprintf( stderr, "error: can not delete subnet %s\n", json_string_value(subnet)); + } + } + return err; +} + +int RDS::destroy_route_tables() +{ + json_t *root; + json_error_t error; + char cmd[1024]; + char * json; + + sprintf(cmd, "aws ec2 describe-vpcs --vpc-ids=%s", vpc_id_intern); + if (execute_cmd(cmd, &json)) + { + fprintf( stderr, "error: can not get internet gateways description\n"); + return -1; + } + + root = get_cluster_descr(json); + if ( !root ) + { + fprintf( stderr, "error: can not get cluster description\n"); + return -1; + } + + json_t * route_tables = json_object_get(root, "RouteTables"); + + size_t i; + json_t *route_table; + const char * rt_id; + const char * vpc_id; + json_array_foreach(route_tables, i, route_table) + { + rt_id = json_string_value(json_object_get(route_table, "RouteTableId")); + vpc_id = json_string_value(json_object_get(route_table, "VpcId")); + if (strcmp(vpc_id_intern, vpc_id) == 0) + { + sprintf(cmd, "aws ec2 delete-route-table --route-table-id %s", rt_id); + system(cmd); + } + } + +} + +int RDS::detach_and_destroy_gw() +{ + json_t *root; + json_error_t error; + char cmd[1024]; + char * json; + + sprintf(cmd, "aws ec2 describe-internet-gateways --filters Name=attachment.vpc-id,Values=%s", vpc_id_intern); + if (execute_cmd(cmd, &json)) + { + fprintf( stderr, "error: can not get internet gateways description\n"); + return -1; + } + + root = json_loads( json, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + + json_t * gws = json_object_get(root, "InternetGateways"); + if (gws == NULL) + { + fprintf( stderr, "error: can not parse internet gateways description\n"); + return -1; + } + size_t i; + json_t * gw; + const char * gw_id; + json_array_foreach(gws, i, gw) + { + gw_id = json_string_value(json_object_get(gw, "InternetGatewayId")); + sprintf(cmd, "aws ec2 detach-internet-gateway --internet-gateway-id=%s --vpc-id=%s", gw_id, vpc_id_intern); + printf("%s\n", cmd); + if (system(cmd) != 0) + { + fprintf( stderr, "error: can not detach gateway %s from vpc %s\n", gw_id, vpc_id_intern ); + return -1; + } + sprintf(cmd, "aws ec2 delete-internet-gateway --internet-gateway-id=%s", gw_id); + printf("%s\n", cmd); + if (system(cmd) != 0) + { + fprintf( stderr, "error: can not delete gateway %s\n", gw_id); + return -1; + } + } + return 0; +} + +int RDS::create_vpc(const char **vpc_id) +{ + json_t *root; + json_error_t error; + char * result; + char cmd[1024]; + + if (execute_cmd((char *) "aws ec2 create-vpc --cidr-block 172.30.0.0/16", &result) != 0) + { + fprintf(stderr, "error: can not create VPC\n"); + return -1; + } + root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + *vpc_id = json_string_value(json_object_get(json_object_get(root, "Vpc"), "VpcId")); + if (*vpc_id == NULL) + { + fprintf(stderr, "error: can not parse output of create-vpc command\n"); + return -1; + } + vpc_id_intern = * vpc_id; + + sprintf(cmd, "aws ec2 modify-vpc-attribute --enable-dns-support --vpc-id %s", *vpc_id); + if (system(cmd) != 0) + { + fprintf(stderr, "error: can not enable dns support\n"); + return -1; + } + sprintf(cmd, "aws ec2 modify-vpc-attribute --enable-dns-hostnames --vpc-id %s", *vpc_id); + if (system(cmd) != 0) + { + fprintf(stderr, "error: can not enable dns hostnames\n"); + return -1; + } + + return 0; +} + +int RDS::create_subnet(const char * az, const char * cidr, const char **subnet_id) +{ + json_t *root; + json_error_t error; + char * result; + char cmd[1024]; + + *subnet_id = NULL; + sprintf(cmd, "aws ec2 create-subnet --cidr-block %s --availability-zone %s --vpc-id %s", cidr, az, + vpc_id_intern); + puts(cmd); + if (execute_cmd(cmd, &result) != 0) + { + fprintf(stderr, "error: can not create subnet\n"); + return -1; + } + root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + *subnet_id = json_string_value(json_object_get(json_object_get(root, "Subnet"), "SubnetId")); + if (*subnet_id == NULL) + { + fprintf(stderr, "error: can not parse output of create-vpc command\n"); + return -1; + } + + if (subnets_intern == NULL) + { + subnets_intern = json_array(); + } + json_array_append(subnets_intern, json_string(*subnet_id)); + + sprintf(cmd, "aws ec2 modify-subnet-attribute --map-public-ip-on-launch --subnet-id %s", *subnet_id); + if (system(cmd) != 0) + { + fprintf(stderr, "error: can not modify subnet attribute\n"); + return -1; + } + + return 0; +} + +int RDS::create_subnet_group() +{ + char cmd[1024]; + size_t i; + json_t * subnet; + + sprintf(cmd, + "aws rds create-db-subnet-group --db-subnet-group-name %s --db-subnet-group-description maxscale --subnet-ids", + cluster_name_intern); + json_array_foreach(subnets_intern, i, subnet) + { + strcat(cmd, " "); + strcat(cmd, json_string_value(subnet)); + } + subnets_group_name_intern = cluster_name_intern; + if (system(cmd) != 0) + { + fprintf(stderr, "error: can not create subnets group\n"); + return -1; + } + + return 0; +} + +int RDS::create_gw(const char **gw_id) +{ + char * result; + char cmd[1024]; + json_error_t error; + + *gw_id = NULL; + gw_intern = NULL; + if (execute_cmd((char *) "aws ec2 create-internet-gateway", &result) != 0) + { + fprintf(stderr, "error: can not create internet gateway\n"); + return -1; + } + json_t * root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + + *gw_id = json_string_value(json_object_get(json_object_get(root, "InternetGateway"), "InternetGatewayId")); + if (*gw_id == NULL) + { + fprintf(stderr, "error: can not parse output of create-internet-gateway command\n"); + return -1; + } + gw_intern = *gw_id; + + sprintf(cmd, "aws ec2 attach-internet-gateway --internet-gateway-id %s --vpc-id %s", *gw_id, vpc_id_intern); + if (system(cmd) != 0) + { + fprintf(stderr, "error: can not attach gateway to VPC\n"); + return -1; + } + + return 0; +} + +int RDS::configure_route_table(const char **rt) +{ + char * result; + char cmd[1024]; + json_error_t error; + + *rt = NULL; + if (execute_cmd((char *) "aws ec2 describe-route-tables", &result) != 0) + { + fprintf(stderr, "error: can not get route tables description\n"); + return -1; + } + json_t * root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + + json_t * route_tables = json_object_get(root, "RouteTables"); + if (route_tables == NULL) + { + fprintf( stderr, "error: can not parse route tables description\n"); + return -1; + } + size_t i; + json_t * rtb; + const char * rt_vpc; + + json_array_foreach(route_tables, i, rtb) + { + rt_vpc = json_string_value(json_object_get(rtb, "VpcId")); + if (strcmp(vpc_id_intern, rt_vpc) == 0) + { + // add route to route table which belongs to give VPC + *rt = json_string_value(json_object_get(rtb, "RouteTableId")); + sprintf(cmd, "aws ec2 create-route --route-table-id %s --gateway-id %s --destination-cidr-block 0.0.0.0/0", + *rt, gw_intern); + if (system(cmd) != 0) + { + fprintf( stderr, "error: can not create route\n"); + return -1; + } + } + } + if (*rt == NULL) + { + fprintf( stderr, "error: can not find route table\n"); + return -1; + } + return 0; +} + +int RDS::create_cluster() +{ + char cmd[1024]; + char * result; + json_error_t error; + size_t i; + + sprintf(cmd, + "aws rds create-db-cluster --database-name=test --engine=aurora --master-username=skysql --master-user-password=skysqlrds --db-cluster-identifier=%s --db-subnet-group-name=%s", + cluster_name_intern, cluster_name_intern); + + execute_cmd(cmd , &result); + json_t * root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + json_t * cluster = json_object_get(root, "DBCluster"); + cluster_intern = cluster; + json_t * security_groups = json_object_get(cluster, "VpcSecurityGroups"); + json_t * sg; + const char * sg_id; + + json_array_foreach(security_groups, i, sg) + { + sg_id = json_string_value(json_object_get(sg, "VpcSecurityGroupId")); + printf("Security group %s\n", sg_id); + sprintf(cmd, + "aws ec2 authorize-security-group-ingress --group-id %s --protocol tcp --port 3306 --cidr 0.0.0.0/0", sg_id); + system(cmd); + } + sg_intern = sg_id; + + for (size_t i = 0; i < N_intern; i++) + { + sprintf(cmd, + "aws rds create-db-instance --db-cluster-identifier=%s --engine=aurora --db-instance-class=db.t2.medium --publicly-accessible --db-instance-identifier=node%03lu", + cluster_name_intern, i); + printf("%s\n", cmd); + system(cmd); + } + return 0; +} + +int RDS::get_writer(const char ** writer_name) +{ + char * json; + char cmd[1024]; + sprintf(cmd, "aws rds describe-db-clusters --db-cluster-identifier=%s", cluster_name_intern); + execute_cmd(cmd , &json); + json_t * cluster = get_cluster_descr(json); + json_t * nodes = json_object_get(cluster, "DBClusterMembers"); + + //char * s = json_dumps(nodes, JSON_INDENT(4)); + //puts(s); + + bool writer; + json_t * node; + size_t i = 0; + + do + { + node = json_array_get(nodes, i); + writer = json_is_true(json_object_get(node, "IsClusterWriter")); + i++; + } + while (!writer); + * writer_name = json_string_value(json_object_get(node, "DBInstanceIdentifier")); + + return 0; +} + +int RDS::destroy_vpc() +{ + char cmd[1024]; + sprintf(cmd, "aws ec2 delete-vpc --vpc-id=%s", vpc_id_intern); + return system(cmd); +} + +int RDS::destroy_cluster() +{ + char cmd[1024]; + char * result; + sprintf(cmd, "aws rds delete-db-cluster --db-cluster-identifier=%s --skip-final-snapshot", + cluster_name_intern); + return execute_cmd(cmd, &result); +} + +int RDS::destroy_subnets_group() +{ + char cmd[1024]; + char * result; + sprintf(cmd, "aws rds delete-db-subnet-group --db-subnet-group-name %s", get_subnetgroup_name()); + puts(cmd); + execute_cmd(cmd, &result); +} + +int RDS::create_rds_db(int N) +{ + const char * vpc; + const char * subnet1; + const char * subnet2; + const char * gw; + const char * rt; + + N_intern = N; + + printf("Create VPC\n"); + if (create_vpc(&vpc) != 0) + { + fprintf( stderr, "error: can not create VPC\n"); + destroy_vpc(); + return -1; + } + printf("vpc id: %s\n", vpc); + + printf("Create subnets\n"); + create_subnet("eu-west-1b", "172.30.0.0/24", &subnet1); + create_subnet("eu-west-1a", "172.30.1.0/24", &subnet2); + + printf("Create subnets group\n"); + if (create_subnet_group() != 0) + { + destroy_subnets(); + destroy_subnets_group(); + destroy_vpc(); + return -1; + } + + printf("Create internet gateway\n"); + if (create_gw(&gw) != 0) + { + detach_and_destroy_gw(); + destroy_subnets(); + destroy_subnets_group(); + destroy_vpc(); + return -1; + } + printf("Gateway: %s\n", gw); + + printf("Configure route table\n"); + if (configure_route_table(&rt) != 0) + { + detach_and_destroy_gw(); + destroy_subnets(); + destroy_subnets_group(); + destroy_vpc(); + return -1; + } + printf("Route table: %s\n", rt); + + printf("Create RDS cluster\n"); + if (create_cluster() != 0) + { + destroy_nodes(get_cluster_nodes()); + destroy_cluster(); + detach_and_destroy_gw(); + destroy_subnets(); + destroy_subnets_group(); + destroy_vpc(); + return -1; + } + return 0; +} + +int RDS::delete_rds_cluster() +{ + char * result; + char cmd[1024]; + json_t * current_cluster; + + printf("Get cluster\n"); + cluster_intern = get_cluster(); + printf("Get cluster NODES\n"); + json_t * nodes = get_cluster_nodes(); + + printf("Get subnets group: %s\n", get_subnetgroup_name()); + + printf("Get subnets\n"); + get_subnets(); + + printf("Get VPC: %s\n", vpc_id_intern); + + size_t alive_nodes = json_array_size(nodes); + + printf("Destroy nodes\n"); + destroy_nodes(nodes); + + do + { + printf("Waiting for nodes to be deleted, now %lu nodes are still alive\n", alive_nodes); + sleep(5); + current_cluster = get_cluster(); + nodes = get_cluster_nodes(current_cluster); + alive_nodes = json_array_size(nodes); + } + while ( alive_nodes > 0); + + printf("Destroy cluster\n"); + destroy_cluster(); + + do + { + printf("Waiting for cluster to be deleted\n"); + sleep(5); + sprintf(cmd, "aws rds describe-db-clusters --db-cluster-identifier=%s", cluster_name_intern); + execute_cmd(cmd, &result); + + } + while (get_cluster_descr(result) != NULL); + + printf("Destroy subnets\n"); + destroy_subnets(); + + printf("Destroy subnet group\n"); + destroy_subnets_group(); + + printf("Get and destroy Internet Gateways\n"); + detach_and_destroy_gw(); + + printf("Destroy vpc\n"); + destroy_vpc(); +} + +int RDS::wait_for_nodes(size_t N) +{ + char * result; + size_t active_nodes = 0; + size_t i = 0; + json_t * node; + char cmd[1024]; + json_t * nodes; + json_t * instances; + json_t * instance; + json_error_t error; + + do + { + printf("Waiting for nodes to be active, now %lu are active\n", active_nodes); + sleep(5); + cluster_intern = get_cluster(); + nodes = get_cluster_nodes(); + + active_nodes = 0; + json_array_foreach(nodes, i, node) + { + sprintf(cmd, "aws rds describe-db-instances --db-instance-identifier=%s", json_string_value(node)); + execute_cmd(cmd, &result); + instances = json_loads( result, 0, &error ); + if ( !instances ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return -1; + } + instance = json_array_get(json_object_get(instances, "DBInstances"), 0); + //puts(json_dumps(instance, JSON_INDENT(4))); + if (strcmp(json_string_value(json_object_get(instance, "DBInstanceStatus")), "available") == 0) + { + active_nodes++; + } + } + } + while ( active_nodes != N); + return 0; +} + +int RDS::do_failover() +{ + char * result; + const char * writer; + const char * new_writer; + char cmd[1024]; + if (get_writer(&writer) != 0) + { + return -1; + } + + sprintf(cmd, "aws rds failover-db-cluster --db-cluster-identifier=%s", cluster_name_intern); + if (execute_cmd(cmd, &result) != 0) + { + return -1; + } + do + { + if (get_writer(&new_writer) != 0) + { + return -1; + } + printf("writer: %s\n", new_writer); + sleep(5); + } + while (strcmp(writer, new_writer) == 0); + return 0; +} + +json_t * RDS::get_endpoints() +{ + char cmd[1024]; + char * result; + + json_t *root; + json_error_t error; + + json_t * node; + json_t * node_json; + json_t *endpoint; + + json_t * endpoints; + + endpoints = json_array(); + + cluster_intern = get_cluster(); + json_t * nodes = get_cluster_nodes(); + //puts(json_dumps(nodes, JSON_INDENT(4))); + + size_t i; + json_array_foreach(nodes, i, node) + { + sprintf(cmd, "aws rds describe-db-instances --db-instance-identifier=%s", json_string_value(node)); + if (execute_cmd(cmd, &result) != 0) + { + fprintf( stderr, "error: executing aws rds describe-db-instances\n"); + return NULL; + } + root = json_loads( result, 0, &error ); + if ( !root ) + { + fprintf( stderr, "error: on line %d: %s\n", error.line, error.text ); + return NULL; + } + node_json = json_array_get(json_object_get(root, "DBInstances"), 0); + endpoint = json_object_get(node_json, "Endpoint"); + json_array_append(endpoints, endpoint); + } + return endpoints; +} diff --git a/maxscale-system-test/rds_vpc.h b/maxscale-system-test/rds_vpc.h new file mode 100644 index 000000000..7275798f3 --- /dev/null +++ b/maxscale-system-test/rds_vpc.h @@ -0,0 +1,263 @@ +#ifndef RDS_VPC_H +#define RDS_VPC_H + +#include +#include +#include "testconnections.h" +#include + +using namespace std; + +class RDS +{ +public: + /** + * @brief RDS Constructor + * @param cluster Name of cluster to create/destroy + */ + RDS(char * cluster); + + const char * get_instance_name(json_t * instance); + + /** + * @brief get_cluster Executes 'rds describe-bd-clusters' and creates json object with info on cluster + * Finds cluster with ID 'cluster_name_intern'. + * Does not set any internal variables + * @return JSON describption of cluster + */ + json_t * get_cluster(); + + /** + * @brief get_cluster_descr Creates JSON cluster describtion from string representation + * @param json String representation of cluster description + * Does not set any internal variables + * @return JSON describption of cluster + */ + json_t * get_cluster_descr(char * json); + + /** + * @brief get_subnets_group_descr + * @param json String representation of subnets grop description + * Does not set any internal variables + * @return JSON description of subnets group + */ + json_t * get_subnets_group_descr(char * json); + + /** + * @brief get_cluster_nodes Extract list of nodes names from cluster JSON description + * Uses 'cluster_intern' + * Does not set any internal variables + * @return JSON array of node names strings + */ + json_t * get_cluster_nodes(); + + + /** + * @brief get_cluster_nodes Extract list of nodes names from cluster JSON description + * Does not set any internal variables + * @param cluster JSON cluster description + * @return JSON array of nodes names strings + */ + json_t * get_cluster_nodes(json_t * cluster); + + /** + * @brief get_endpoints Gets list of endpoint (URLs) of cluster nodes + * Sets 'cluster_intern' + * @return JSON array of nodes endpoints (objects contaning Address and Port) + */ + json_t * get_endpoints(); + + /** + * @brief get_subnets Extracts subnets IDs from subnets group + * Uses 'subnets_group_name_intern' + * Sets 'vpc_id_intern' and 'subnets_intern' + * @return JSON array of node names strings + */ + json_t * get_subnets(); + + /** + * @brief get_subnetgroup_name Extracts subnets grop ID from cluster description + * Uses 'cluster_intern' + * Sets 'subnets_group_name_intern' + * If 'cluster_intern' is NULL function returns 'subnets_group_name_intern' value + * @return name of subnets group + */ + const char * get_subnetgroup_name(); + + /** + * @brief destroy_nodes Destroys nodes + * @param node_names JSON array with nodes names + * @return 0 in case of success + */ + int destroy_nodes(json_t * node_names); + + /** + * @brief destroy_subnets Destoys subnets + * Uses 'subnets_intern' to get subnets list + * If 'subnets_intern' is not set it is needed to run: + * - clustr_intern=get_cluster() + * - get_subnetgroup_name() + * - get_subnets() + * @return 0 in case of success + */ + int destroy_subnets(); + + /** + * @brief destroy_subnets_group Destroys subnets group + * Uses 'subnets_group_name_intern' + * If 'subnets_group_name_intern' it is needed to run: + * - clustr_intern=get_cluster() + * - get_subnetgroup_name() + * @return 0 in case of success + */ + int destroy_subnets_group(); + + /** + * @brief destroy_route_tables Destroys route tabele + * Not needed to executed directly, route table is destroyed by destroy_vpc + * Uses 'vpc_id_intern' + * If 'vpc_id_intern' is not set it is needed to run: + * - clustr_intern=get_cluster() + * - get_subnetgroup_name() + * - get_subnets() + * @return 0 in case of success + */ + int destroy_route_tables(); // is needed? + + /** + * @brief destroy_vpc Destroys VPC + * Uses 'vpc_id_intern' + * if 'vpc_id_intern' is not set it is needed to run: + * - clustr_intern=get_cluster() + * - get_subnetgroup_name() + * - get_subnets() + * @return 0 in case of success + */ + int destroy_vpc(); + + /** + * @brief destroy_cluster Destroys RDS cluster + * Uses 'cluster_name_intern' + * @return 0 in case of success + */ + int destroy_cluster(); + + /** + * @brief detach_and_destroy_gw Finds, detach and destroys internet gateways attached to VPC + * Uses 'vpc_id_intern' + * if 'vpc_id_intern' is not set it is needed to run: + * - clustr_intern=get_cluster() + * - get_subnetgroup_name() + * - get_subnets() + * @return 0 in case of success + */ + int detach_and_destroy_gw(); + + /** + * @brief create_vpc Creates VPC + * Sets 'vpc_id_intern' + * @param vpc_id Pointer to variable to place VpcID + * @return 0 in case of success + */ + int create_vpc(const char **vpc_id); + + /** + * @brief create_subnet Creates subnet inside VPC + * Adds element to 'subnets_intern' JSON array (creates it if it does not exist) + * @param az Availability zone ID (e.g. 'eu-west-1a') + * @param cidr CIDR block (e.g. '172.30.1.0/24') + * @param subnet_id Pointer to variable to place SubnetID + * @return 0 in case of success + */ + int create_subnet(const char *az, const char *cidr, const char **subnet_id); + + /** + * @brief create_subnet_group Creates subnets group for RDS + * Uses 'subnets_intern' + * Sets 'subnets_group_name_intern' + * @return 0 in case of success + */ + int create_subnet_group(); + + /** + * @brief create_gw Creates internet gateway for vpc_id_intern + * Uses 'vpc_id_intern' + * Sests 'gw_intern' + * @param gw_id Pointer to variable to place gateway ID + * @return 0 in case of success + */ + int create_gw(const char **gw_id); + + /** + * @brief configure_route_table Adds route to route tabele attched to VPC + * Finds route table attached to VPC and adds route from internet to internet gateway + * Uses 'vpc_id_intern' and 'gw_intern' + * @param rt Pointer to variable to place route table ID which was found ond modified + * @return 0 in case of success + */ + int configure_route_table(const char **rt); + + /** + * @brief create_cluster Creates RDS cluster and instances + * Also configures security group (opens port 3306) + * Uses 'cluster_name_intern', 'N_intern' + * Sets cluster_intern, sg_intern + * @return 0 in case of success + */ + int create_cluster(); + + /** + * @brief get_writer Find instance which have 'write' attribute + * Uses cluster_name_intern + * Calls 'aws rds describe-db-clusters' and does not use 'cluster_intern' + * (but does not update 'cluster_intern') + * @param writer_name Pointer to variable to place name of writer node + * @return 0 in case of success + */ + int get_writer(const char **writer_name); + + /** + * @brief create_rds_db Creates RDS DB cluster and all needed stuff (vpc, subnets, gateway, route table, ...) + * If case of error tries to destry all created stuff + * @param cluster_name Name of DB cluster + * @param N Number of nodes + * @return 0 in case if success + */ + int create_rds_db(int N); + + /** + * @brief delete_rds_cluster Destroys RDS cluster, instances and VPC in which RDS cluster was located + * Uses 'cluster_name_intern' + * Tries to get all items IDs + * @return 0 in case if success + */ + int delete_rds_cluster(); + + /** + * @brief wait_for_nodes Waits until N nodes are in 'avalable' state + * Uses 'cluster_name_intern' + * Sets 'cluster_intern' + * @param N Number of nodes expected to be active (can be less than number of nodes in cluster) + * @return 0 in case if success + */ + int wait_for_nodes(size_t N); + + /** + * @brief do_failover Does failover for RDS cluster + * @return 0 in case if success + */ + int do_failover(); + + const char * cluster_name_intern; + size_t N_intern; + json_t * cluster_intern; + const char * vpc_id_intern; + json_t * subnets_intern; + const char * subnets_group_name_intern; + const char * rt_intern; + const char * gw_intern; + const char * sg_intern; + +}; + +#endif // RDS_VPC_H diff --git a/maxscale-system-test/readconnrouter_master.cpp b/maxscale-system-test/readconnrouter_master.cpp new file mode 100644 index 000000000..5c974e1aa --- /dev/null +++ b/maxscale-system-test/readconnrouter_master.cpp @@ -0,0 +1,84 @@ +/** + * @file readconnrouter_master.cpp Connect to ReadConn in master mode and check if there is only one backend connection to master + * + * - connect to ReadCon master + * - expect only 1 connection to node 0 and no connections to ther nodes + * - close connections + * - change master to node 1 + * - connect again + * - expect only 1 connection to node 1 and no connections to ther nodes + * - close connection + * - change master back to node 0 + */ + + +#include +#include "testconnections.h" + +using namespace std; + +/** + * @brief Checks if there is only one connection to master and no connections to other nodes + * @param Test Pointer to TestConnections object that contains info about test setup + * @param master Master node index + * @return 0 if check succedded + */ +int check_connnections_only_to_master(TestConnections * Test, int master) +{ + int res = 0; + int conn_num; + printf("Checking number of connections to each node\n"); + for (int i = 0; i < Test->repl->N; i++) + { + conn_num = get_conn_num(Test->repl->nodes[i], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + printf("Connections to node %d (%s):\t%d\n", i, Test->repl->IP[i], conn_num); + if (((i == master) && (conn_num != 1)) || ((i != master) && (conn_num != 0))) + { + res++; + printf("FAILED: number of connections to node %d is wrong\n", i); + } + } + return res; +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(100); + + Test->repl->connect(); + + Test->tprintf("Connecting to ReadConnnRouter in 'master' mode\n"); + Test->connect_readconn_master(); + printf("Sleeping 10 seconds\n"); + Test->stop_timeout(); + sleep(10); + Test->set_timeout(50); + Test->add_result(check_connnections_only_to_master(Test, 0), "connections are not only to Master\n"); + Test->close_readconn_master(); + Test->tprintf("Changing master to node 1\n"); + Test->set_timeout(50); + Test->repl->change_master(1, 0); + printf("Sleeping 10 seconds\n"); + Test->stop_timeout(); + sleep(10); + Test->set_timeout(50); + printf("Connecting to ReadConnnRouter in 'master' mode\n"); + Test->connect_readconn_master(); + printf("Sleeping 10 seconds\n"); + Test->stop_timeout(); + sleep(10); + Test->set_timeout(50); + Test->add_result(check_connnections_only_to_master(Test, 1), "connections are not only to master"); + Test->close_readconn_master(); + Test->set_timeout(50); + printf("Changing master back to node 0\n"); + Test->repl->change_master(0, 1); + + Test->check_log_err((char *) "The service 'CLI' is missing a definition of the servers", false); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/readconnrouter_slave.cpp b/maxscale-system-test/readconnrouter_slave.cpp new file mode 100644 index 000000000..38b8d74fd --- /dev/null +++ b/maxscale-system-test/readconnrouter_slave.cpp @@ -0,0 +1,71 @@ +/** + * @file readconnrouter_slave.cpp Creates 100 connections to ReadConn in slave mode and check if connections are distributed among all slaves + * + * - create 100 connections to ReadConn slave + * - check if all slave have equal number of connections (+-1) + */ + + +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(100); + Test->repl->connect(); + + const int TestConnNum = 100; + MYSQL *conn[TestConnNum]; + int i; + int conn_num; + + Test->tprintf("Creating %d connections to ReadConnRouter in 'slave' mode\n", TestConnNum); + for (i = 0; i < TestConnNum; i++) + { + conn[i] = Test->open_readconn_slave_connection(); + } + Test->tprintf("Waiting 5 seconds\n"); + sleep(5); + + int ConnFloor = floor((float)TestConnNum / (Test->repl->N - 1)); + int ConnCell = ceil((float)TestConnNum / (Test->repl->N - 1)); + int TotalConn = 0; + + Test->tprintf("Checking connections to Master: should be 0\n"); + conn_num = get_conn_num(Test->repl->nodes[0], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + Test->add_result(conn_num, "number of connections to Master is %d\n", conn_num); + + Test->tprintf("Number of connections to each slave should be between %d and %d\n", ConnFloor, ConnCell); + Test->tprintf("Checking connections to each node\n"); + for (int i = 1; i < Test->repl->N; i++) + { + conn_num = get_conn_num(Test->repl->nodes[i], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + TotalConn += conn_num; + printf("Connections to node %d (%s):\t%d\n", i, Test->repl->IP[i], conn_num); + if ((conn_num > ConnCell) || (conn_num < ConnFloor)) + { + Test->add_result(1, "wrong number of connectiosn to mode %d\n", i); + } + } + + Test->tprintf("Total number of connections %d\n", TotalConn); + if (TotalConn != TestConnNum) + { + Test->add_result(1, "total number of connections is wrong\n"); + } + + for (i = 0; i < TestConnNum; i++) + { + mysql_close(conn[i]); + } + + int rval = Test->global_result; + delete Test; + return rval; +} + + + diff --git a/maxscale-system-test/regexfilter1.cpp b/maxscale-system-test/regexfilter1.cpp new file mode 100644 index 000000000..d01ab6698 --- /dev/null +++ b/maxscale-system-test/regexfilter1.cpp @@ -0,0 +1,27 @@ +/** + * @file regexfilter1.cpp Simple regexfilter tests; aslo regression case for mxs508 ("regex filter ignores username") + * + * Three services are configured with regexfilter, each with different parameters. + * All services are queried with SELECT 123. The first service should replace it + * with SELECT 0 and the second and third services should not replace it. + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * test = new TestConnections(argc, argv); + test->connect_maxscale(); + test->add_result(execute_query_check_one(test->conn_rwsplit, "SELECT 123", "0"), + "Query to first service should have replaced the query.\n"); + test->add_result(execute_query_check_one(test->conn_slave, "SELECT 123", "123"), + "Query to second service should not have replaced the query.\n"); + test->add_result(execute_query_check_one(test->conn_master, "SELECT 123", "123"), + "Query to third service should not have replaced the query.\n"); + test->close_maxscale_connections(); + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/replication_manager.cpp b/maxscale-system-test/replication_manager.cpp new file mode 100644 index 000000000..710c31a93 --- /dev/null +++ b/maxscale-system-test/replication_manager.cpp @@ -0,0 +1,157 @@ +/** + * Test replication-manager + */ + +#include "testconnections.h" +#include + +void prepare() +{ + struct termios t; + tcgetattr(STDIN_FILENO, &t); + t.c_iflag |= IGNBRK; + t.c_iflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &t); +} + +void get_output(TestConnections& test) +{ + test.tprintf("Maxadmin output:"); + char *output = test.ssh_maxscale_output(true, "maxadmin list servers"); + test.tprintf("%s", output); + free(output); + + test.tprintf("replication-manager output:"); + output = test.ssh_maxscale_output(true, + "cat /var/log/replication-manager.log && sudo truncate -s 0 /var/log/replication-manager.log"); + test.tprintf("%s", output); + free(output); +} + +static int inserts = 0; + +void check(TestConnections& test) +{ + MYSQL *conn = test.open_rwsplit_connection(); + const char *query1 = "INSERT INTO test.t1 VALUES (%d)"; + const char *query2 = "SELECT * FROM test.t1"; + + test.try_query(conn, "BEGIN"); + test.tprintf(query1, inserts); + test.try_query(conn, query1, inserts++); + mysql_query(conn, query2); + + MYSQL_RES *res = mysql_store_result(conn); + test.add_result(res == NULL, "Query shoud return a result set"); + + if (res) + { + std::string values; + MYSQL_ROW row; + int num_rows = mysql_num_rows(res); + test.add_result(num_rows != inserts, "Query returned %d rows when %d rows were expected", num_rows, inserts); + const char *separator = ""; + + while ((row = mysql_fetch_row(res))) + { + values += separator; + values += row[0]; + separator = ", "; + } + + test.tprintf("%s: %s", query2, values.c_str()); + } + + test.try_query(conn, "COMMIT"); + mysql_close(conn); +} + +static bool interactive = false; + +void get_input() +{ + if (interactive) + { + printf("--- Press any key to confinue ---\n"); + getchar(); + } +} + +int main(int argc, char** argv) +{ + interactive = strcmp(argv[argc - 1], "interactive") == 0; + prepare(); + + TestConnections test(argc, argv); + test.tprintf("Installing replication-manager"); + int rc = system("./manage_mrm.sh install > manage_mrm.log"); + if (!WIFEXITED(rc) || WEXITSTATUS(rc) != 0) + { + test.tprintf("Failed to install replication-manager, see manage_mrm.log for more details"); + return -1; + } + + // Wait a few seconds + sleep(5); + + test.tprintf("Creating table and inserting data"); + get_input(); + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "CREATE OR REPLACE TABLE test.t1(id INT)"); + + check(test); + get_output(test); + + test.tprintf("Stopping master and waiting for it to fail over"); + get_input(); + test.repl->stop_node(0); + sleep(10); + + check(test); + get_output(test); + + test.tprintf("Stopping another node and waiting for replication-manager to detect it"); + get_input(); + test.repl->stop_node(1); + sleep(10); + + check(test); + get_output(test); + get_input(); + + test.tprintf("Stopping all but one node and waiting for replication-manager to detect it"); + get_input(); + test.repl->stop_node(2); + sleep(10); + + check(test); + get_output(test); + + test.tprintf("Starting all nodes and wait for replication-manager to fix the replication"); + get_input(); + + test.repl->start_node(0, (char*)""); + sleep(5); + test.repl->start_node(1, (char*)""); + sleep(5); + test.repl->start_node(2, (char*)""); + sleep(5); + + check(test); + get_output(test); + + test.tprintf("Dropping tables"); + get_input(); + test.close_maxscale_connections(); + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "DROP TABLE test.t1"); + test.close_maxscale_connections(); + + get_output(test); + + test.tprintf("Removing replication-manager"); + get_input(); + system("./manage_mrm.sh remove >> manage_mrm.log"); + test.repl->fix_replication(); + return test.global_result; +} diff --git a/maxscale-system-test/replication_manager_2nodes.cpp b/maxscale-system-test/replication_manager_2nodes.cpp new file mode 100644 index 000000000..3ce636c9d --- /dev/null +++ b/maxscale-system-test/replication_manager_2nodes.cpp @@ -0,0 +1,202 @@ +/** + * Test replication-manager - Two node setup + */ + +#include "testconnections.h" +#include + +void prepare(TestConnections& test) +{ + struct termios t; + tcgetattr(STDIN_FILENO, &t); + t.c_iflag |= IGNBRK; + t.c_iflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &t); + + test.ssh_maxscale(true, "pcs resource disable maxscale-clone; pcs resource disable replication-manager"); + + test.repl->fix_replication(); + system("./manage_mrm.sh configure 2"); + test.copy_from_maxscale((char*)"/etc/maxscale.cnf", (char*)"."); + test.copy_to_maxscale("./config.toml", "~"); + test.ssh_maxscale(false, "sudo cp ~/maxscale.cnf /etc/; sudo cp ~/config.toml /etc/replication-manager/"); + + system("sed -i 's/version_string=.*/version_string=10.1.19-maxscale-standby/' ./maxscale.cnf"); + test.galera->copy_to_node("./maxscale.cnf", "~", 0); + test.galera->copy_to_node("./config.toml", "~", 0); + test.galera->ssh_node(0, "sudo cp ~/config.toml /etc/replication-manager", false); + test.galera->ssh_node(0, "sudo cp ~/maxscale.cnf /etc/", false); + test.ssh_maxscale(true, "replication-manager bootstrap --clean-all;pcs resource enable maxscale-clone; pcs resource enable replication-manager"); + sleep(5); +} + +void get_output(TestConnections& test) +{ + test.tprintf("Maxadmin output:"); + char *output = test.ssh_maxscale_output(true, "maxadmin list servers"); + test.tprintf("%s", output); + free(output); +} + +static int inserts = 0; + +void check(TestConnections& test) +{ + MYSQL *conn = test.open_rwsplit_connection(); + const char *query1 = "INSERT INTO test.t1 VALUES (%d)"; + const char *query2 = "SELECT * FROM test.t1"; + + printf("\nExecuting queries though MaxScale:\n\n"); + printf("BEGIN\n"); + test.try_query(conn, "BEGIN"); + printf(query1, inserts); + printf("\n"); + test.try_query(conn, query1, inserts++); + mysql_query(conn, query2); + + MYSQL_RES *res = mysql_store_result(conn); + test.add_result(res == NULL, "Query shoud return a result set"); + + if (res) + { + std::string values; + MYSQL_ROW row; + int num_rows = mysql_num_rows(res); + test.add_result(num_rows != inserts, "Query returned %d rows when %d rows were expected", num_rows, inserts); + const char *separator = ""; + + while ((row = mysql_fetch_row(res))) + { + values += separator; + values += row[0]; + separator = ", "; + } + + printf("%s\n%s\n", query2, values.c_str()); + mysql_free_result(res); + } + + mysql_query(conn, "SELECT @@server_id, @@hostname"); + res = mysql_store_result(conn); + if (res) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) + { + printf("SELECT @@server_id, @@hostname\n%s, %s\n", row[0], row[1]); + } + mysql_free_result(res); + } + + printf("COMMIT\n"); + test.try_query(conn, "COMMIT"); + + mysql_query(conn, "SELECT @@server_id, @@hostname"); + res = mysql_store_result(conn); + if (res) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) + { + printf("SELECT @@server_id, @@hostname\n%s, %s\n", row[0], row[1]); + } + mysql_free_result(res); + } + printf("\n"); + + get_output(test); + + mysql_close(conn); +} + +static bool interactive = false; + +void get_input() +{ + if (interactive) + { + printf("--- Press any key to confinue ---\n"); + getchar(); + } +} + +void do_sleep(int s) +{ + printf("Waiting for %d seconds.", s); + fflush(stdout); + + for (int i = 0; i < s; i++) + { + printf("."); + fflush(stdout); + sleep(1); + } + + printf(" Done!\n"); + fflush(stdout); +} + +int main(int argc, char** argv) +{ + interactive = strcmp(argv[argc - 1], "interactive") == 0; + TestConnections::check_nodes(false); + TestConnections::skip_maxscale_start(true); + TestConnections test(argc, argv); + prepare(test); + + test.tprintf("Creating table and inserting data"); + get_input(); + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "CREATE OR REPLACE TABLE test.t1(id INT)"); + test.close_maxscale_connections(); + + check(test); + + test.tprintf("Stopping the slave"); + get_input(); + test.repl->stop_node(1); + do_sleep(15); + + check(test); + + test.tprintf("Restarting the slave"); + get_input(); + test.repl->start_node(1, (char*)""); + do_sleep(15); + + check(test); + + test.tprintf("Stopping master and waiting for it to fail over"); + get_input(); + test.repl->stop_node(0); + do_sleep(15); + + check(test); + + test.tprintf("Restarting old master"); + get_input(); + test.repl->start_node(0, (char*)""); + do_sleep(15); + + check(test); + + test.tprintf("Stopping the promoted master and waiting for replication-manager to detect it"); + get_input(); + test.repl->stop_node(1); + do_sleep(15); + + check(test); + + test.tprintf("Restarting the promoted master"); + get_input(); + test.repl->start_node(1, (char*)""); + do_sleep(15); + + check(test); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "DROP TABLE test.t1"); + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/replication_manager_3nodes.cpp b/maxscale-system-test/replication_manager_3nodes.cpp new file mode 100644 index 000000000..83332d7c8 --- /dev/null +++ b/maxscale-system-test/replication_manager_3nodes.cpp @@ -0,0 +1,224 @@ +/** + * Test replication-manager - Three node setup + */ + +#include "testconnections.h" +#include + +void prepare(TestConnections& test) +{ + struct termios t; + tcgetattr(STDIN_FILENO, &t); + t.c_iflag |= IGNBRK; + t.c_iflag &= ~ECHO; + tcsetattr(STDIN_FILENO, TCSANOW, &t); + + test.ssh_maxscale(true, "pcs resource disable maxscale-clone; pcs resource disable replication-manager"); + + test.repl->fix_replication(); + system("./manage_mrm.sh configure 3"); + test.copy_from_maxscale((char*)"/etc/maxscale.cnf", (char*)"."); + test.copy_to_maxscale("./config.toml", "~"); + test.ssh_maxscale(false, "sudo cp ~/maxscale.cnf /etc/; sudo cp ~/config.toml /etc/replication-manager/"); + + system("sed -i 's/version_string=.*/version_string=10.1.19-maxscale-standby/' ./maxscale.cnf"); + test.galera->copy_to_node("./maxscale.cnf", "~", 0); + test.galera->copy_to_node("./config.toml", "~", 0); + test.galera->ssh_node(0, "sudo cp ~/config.toml /etc/replication-manager", false); + test.galera->ssh_node(0, "sudo cp ~/maxscale.cnf /etc/", false); + test.ssh_maxscale(true, "replication-manager bootstrap --clean-all;pcs resource enable maxscale-clone; pcs resource enable replication-manager"); + sleep(5); +} + +void get_output(TestConnections& test) +{ + test.tprintf("Maxadmin output:"); + char *output = test.ssh_maxscale_output(true, "maxadmin list servers"); + test.tprintf("%s", output); + free(output); +} + +static int inserts = 0; + +void check(TestConnections& test) +{ + MYSQL *conn = test.open_rwsplit_connection(); + const char *query1 = "INSERT INTO test.t1 VALUES (%d)"; + const char *query2 = "SELECT * FROM test.t1"; + + printf("\nExecuting queries though MaxScale:\n\n"); + printf("BEGIN\n"); + test.try_query(conn, "BEGIN"); + printf(query1, inserts); + printf("\n"); + test.try_query(conn, query1, inserts++); + mysql_query(conn, query2); + + MYSQL_RES *res = mysql_store_result(conn); + test.add_result(res == NULL, "Query shoud return a result set"); + + if (res) + { + std::string values; + MYSQL_ROW row; + int num_rows = mysql_num_rows(res); + test.add_result(num_rows != inserts, "Query returned %d rows when %d rows were expected", num_rows, inserts); + const char *separator = ""; + + while ((row = mysql_fetch_row(res))) + { + values += separator; + values += row[0]; + separator = ", "; + } + + printf("%s\n%s\n", query2, values.c_str()); + mysql_free_result(res); + } + + mysql_query(conn, "SELECT @@server_id, @@hostname"); + res = mysql_store_result(conn); + if (res) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) + { + printf("SELECT @@server_id, @@hostname\n%s, %s\n", row[0], row[1]); + } + mysql_free_result(res); + } + + printf("COMMIT\n"); + test.try_query(conn, "COMMIT"); + + mysql_query(conn, "SELECT @@server_id, @@hostname"); + res = mysql_store_result(conn); + if (res) + { + MYSQL_ROW row; + while ((row = mysql_fetch_row(res))) + { + printf("SELECT @@server_id, @@hostname\n%s, %s\n", row[0], row[1]); + } + mysql_free_result(res); + } + printf("\n"); + + get_output(test); + + mysql_close(conn); +} + +static bool interactive = false; + +void get_input() +{ + if (interactive) + { + printf("--- Press any key to confinue ---\n"); + getchar(); + } +} + +void do_sleep(int s) +{ + printf("Waiting for %d seconds.", s); + fflush(stdout); + + for (int i = 0; i < s; i++) + { + printf("."); + fflush(stdout); + sleep(1); + } + + printf(" Done!\n"); + fflush(stdout); +} + +int main(int argc, char** argv) +{ + interactive = strcmp(argv[argc - 1], "interactive") == 0; + TestConnections::check_nodes(false); + TestConnections::skip_maxscale_start(true); + TestConnections test(argc, argv); + prepare(test); + + test.tprintf("Creating table and inserting data"); + get_input(); + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "CREATE OR REPLACE TABLE test.t1(id INT)"); + test.close_maxscale_connections(); + + check(test); + + // test.tprintf("Stopping master and waiting for it to fail over"); + // get_input(); + // test.repl->stop_node(0); + // do_sleep(15); + + // check(test); + + // test.tprintf("Restarting old master"); + // get_input(); + // test.repl->start_node(0, (char*)""); + // do_sleep(15); + + // check(test); + + test.tprintf("Stopping the first slave"); + get_input(); + test.repl->stop_node(1); + do_sleep(15); + + check(test); + + + test.tprintf("Stopping the second slave"); + get_input(); + test.repl->stop_node(2); + do_sleep(15); + + check(test); + + test.tprintf("Restarting the second slave"); + get_input(); + test.repl->start_node(2, (char*)""); + do_sleep(15); + + check(test); + + test.tprintf("Stopping the master and waiting for it to fail over"); + get_input(); + test.repl->stop_node(0); + do_sleep(15); + + check(test); + + // test.tprintf("Stopping first slave, the second slave is promoted as the master"); + // get_input(); + // test.repl->stop_node(1); + // do_sleep(15); + + // check(test); + + // test.tprintf("Restarting the first slave"); + // get_input(); + test.repl->start_node(1, (char*)""); + // do_sleep(15); + + // check(test); + + // test.tprintf("Restarting the original master"); + // get_input(); + test.repl->start_node(0, (char*)""); + do_sleep(5); + + // check(test); + + test.connect_maxscale(); + test.try_query(test.conn_rwsplit, "DROP TABLE test.t1"); + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/run_ctrl_c.sh b/maxscale-system-test/run_ctrl_c.sh new file mode 100755 index 000000000..d636eed1e --- /dev/null +++ b/maxscale-system-test/run_ctrl_c.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +### +## @file run_ctrl_c.sh +## check that Maxscale is reacting correctly on ctrc+c signal and termination does not take ages + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` + +if [ $maxscale_IP == "127.0.0.1" ] ; then + echo local test is not supporte + exit 0 +fi + +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] ; then + echo "configuring maxscale failed" + exit 1 +fi + +scp -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -r $test_dir/test_ctrl_c/* $maxscale_access_user@$maxscale_IP:./ +ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $maxscale_access_user@$maxscale_IP "export maxscale_access_sudo=$maxscale_access_sudo; ./test_ctrl_c.sh" + +res=$? + +ssh -i $maxscale_sshkey -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no $maxscale_access_user@$maxscale_IP "sudo rm -f /tmp/maxadmin.sock" + +$test_dir/copy_logs.sh run_ctrl_c +exit $res diff --git a/maxscale-system-test/run_session_hang.sh b/maxscale-system-test/run_session_hang.sh new file mode 100755 index 000000000..f0a12d231 --- /dev/null +++ b/maxscale-system-test/run_session_hang.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +### +## @file run_session_hang.sh +## run a set of queries in the loop (see setmix.sql) using Perl client + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` + +$test_dir/non_native_setup $test_name + +if [ $? -ne 0 ] +then + echo "configuring maxscale failed" + exit 1 +fi +export ssl_options="--ssl-cert=$test_dir/ssl-cert/client-cert.pem --ssl-key=$test_dir/ssl-cert/client-key.pem" + +echo "drop table if exists t1; create table t1(id integer primary key); " | mysql -u$node_user -p$node_password -h$maxscale_IP -P 4006 $ssl_options test + +if [ $? -ne 0 ] +then + echo "Failed to create table test.t1" + exit 1 +fi + +res=0 + +$test_dir/session_hang/run_setmix.sh & +perl $test_dir/session_hang/simpletest.pl +if [ $? -ne 0 ] +then + res=1 +fi + +sleep 15 + +echo "show databases;" | mysql -u$node_user -p$node_password -h$maxscale_IP -P 4006 $ssl_options +if [ $? -ne 0 ] +then + res=1 +fi + +echo "Waiting for jobs" +wait + +if [ $res -eq 1 ] +then + echo "Test FAILED" +else + echo "Test PASSED" +fi + +$test_dir/copy_logs.sh run_session_hang + +exit $res diff --git a/maxscale-system-test/rw_galera_select_insert.cpp b/maxscale-system-test/rw_galera_select_insert.cpp new file mode 100644 index 000000000..ed2ff8c34 --- /dev/null +++ b/maxscale-system-test/rw_galera_select_insert.cpp @@ -0,0 +1,62 @@ +/** + * @file rw_galera_select_insert.cpp NOT IMPLEMENTET YET + * + */ + + +#include "testconnections.h" +#include "get_com_select_insert.h" +#include "maxadmin_operations.h" + +long int selects[256]; +long int inserts[256]; +long int new_selects[256]; +long int new_inserts[256]; +int silent = 0; +int tolerance; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + + Test->galera->connect(); + + tolerance = 0; + + // connect to the MaxScale server (rwsplit) + Test->connect_rwsplit(); + + Test->execute_maxadmin_command((char *) "shutdown monitor \"Galera Monitor\""); + + if (Test->conn_rwsplit == NULL ) + { + Test->add_result(1, "Can't connect to MaxScale\n"); + int rval = Test->global_result; + delete Test; + exit(1); + } + else + { + + Test->try_query(Test->conn_rwsplit, "DROP TABLE IF EXISTS t1;"); + Test->try_query(Test->conn_rwsplit, "create table t1 (x1 int);"); + + get_global_status_allnodes(&selects[0], &inserts[0], Test->galera, silent); + Test->try_query(Test->conn_rwsplit, "select * from t1;"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->galera, silent); + print_delta(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->galera->N); + + Test->try_query(Test->conn_rwsplit, "insert into t1 values(1);"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->galera, silent); + print_delta(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->galera->N); + + // close connections + Test->close_rwsplit(); + } + Test->galera->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/rw_select_insert.cpp b/maxscale-system-test/rw_select_insert.cpp new file mode 100644 index 000000000..fd31594a6 --- /dev/null +++ b/maxscale-system-test/rw_select_insert.cpp @@ -0,0 +1,224 @@ +/** + * @file rw_select_insert.cpp Checks changes of COM_SELECT and COM_INSERT after queris to check if RWSplit sends queries to master or to slave depending on if it is write or read only query + * - connect to RWSplit, create table + * - execute SELECT using RWSplit + * - check COM_SELECT and COM_INSERT change on all nodes + * - execute INSERT using RWSplit + * - check COM_SELECT and COM_INSERT change on all nodes + * - repeat previous steps one more time (now SELECT extracts real date, in the first case table was empty) + * - execute SELECT 100 times, check COM_SELECT and COM_INSERT after every query (tolerate 2*N+1 queries) + * - execute INSERT 100 times, check COM_SELECT and COM_INSERT after every query (tolerate 2*N+1 queries) + */ + + +#include "testconnections.h" +#include "get_com_select_insert.h" +#include "maxadmin_operations.h" + +/** + * @brief check_com_select Checks if COM_SELECT increase takes place only on one slave node and there is no COM_INSERT increase + * @param new_selects COM_SELECT after query + * @param new_inserts COM_INSERT after query + * @param selects COM_SELECT before query + * @param inserts COM_INSERT before query + * @param Nodes pointer to Mariadb_nodes object that contains references to Master/Slave setup + * @return 0 if COM_SELECT increased only on slave node and there is no COM_INSERT increase anywhere + */ +int check_com_select(long int *new_selects, long int *new_inserts, long int *selects, long int *inserts, + Mariadb_nodes * Nodes, int expected) +{ + int i; + int result = 0; + int sum_selects = 0; + int NodesNum = Nodes->N; + + if (new_selects[0] - selects[0] != 0) + { + result = 1; + printf("SELECT query executed, but COM_INSERT increased on master\n"); + } + + for (i = 0; i < NodesNum; i++) + { + + if (new_inserts[i] - inserts[i] != 0) + { + result = 1; + printf("SELECT query executed, but COM_INSERT increased\n"); + } + + int diff = new_selects[i] - selects[i]; + sum_selects += diff; + selects[i] = new_selects[i]; + inserts[i] = new_inserts[i]; + } + + if (sum_selects != expected) + { + printf("Expected %d SELECT queries executed, got %d\n", expected, sum_selects); + result = 1; + } + + if (result) + { + printf("COM_SELECT increase FAIL\n"); + } + + return result; +} + +/** + * @brief Checks if COM_INSERT increase takes places on all nodes and there is no COM_SELECT increase + * @param new_selects COM_SELECT after query + * @param new_inserts COM_INSERT after query + * @param selects COM_SELECT before query + * @param inserts COM_INSERT before query + * @param Nodes pointer to Mariadb_nodes object that contains references to Master/Slave setup + * @return 0 if COM_INSERT increases on all nodes and there is no COM_SELECT increate anywhere + */ +int check_com_insert(long int *new_selects, long int *new_inserts, long int *selects, long int *inserts, + Mariadb_nodes * Nodes, int expected) +{ + int i; + int result = 0; + int NodesNum = Nodes->N; + + int diff_ins = new_inserts[0] - inserts[0]; + int diff_sel = new_selects[0] - selects[0]; + + if (diff_ins == 0) + { + result = 1; + printf("INSERT query executed, but COM_INSERT did not increase\n"); + } + + if (diff_sel != 0) + { + printf("INSERT query executed, but COM_SELECT increase is %d\n", diff_sel); + result = 1; + } + + selects[0] = new_selects[0]; + inserts[0] = new_inserts[0]; + + if (diff_ins != expected) + { + printf("Expected %d INSERT queries executed, got %d\n", expected, diff_ins); + result = 1; + } + + if (result) + { + printf("COM_INSERT increase FAIL\n"); + } + + return result; +} + + +int main(int argc, char *argv[]) +{ + long int selects[256]; + long int inserts[256]; + long int new_selects[256]; + long int new_inserts[256]; + + int silent = 1; + int i; + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(120); + Test->repl->connect(); + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + Test->connect_rwsplit(); + + Test->execute_maxadmin_command((char *) "shutdown monitor MySQL-Monitor"); + + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + + Test->tprintf("Creating table t1\n"); + fflush(stdout); + Test->try_query(Test->conn_rwsplit, "DROP TABLE IF EXISTS t1;"); + Test->try_query(Test->conn_rwsplit, "create table t1 (x1 int);"); + + Test->repl->sync_slaves(); + + printf("Trying SELECT * FROM t1\n"); + fflush(stdout); + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + Test->try_query(Test->conn_rwsplit, "select * from t1;"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_select(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, 1), + "Wrong check_com_select result\n"); + + printf("Trying INSERT INTO t1 VALUES(1);\n"); + fflush(stdout); + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + Test->try_query(Test->conn_rwsplit, "insert into t1 values(1);"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_insert(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, 1), + "Wrong check_com_insert result\n"); + + Test->stop_timeout(); + Test->repl->sync_slaves(); + + printf("Trying SELECT * FROM t1\n"); + fflush(stdout); + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + execute_query(Test->conn_rwsplit, "select * from t1;"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_select(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, 1), + "Wrong check_com_select result\n"); + + printf("Trying INSERT INTO t1 VALUES(1);\n"); + fflush(stdout); + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + execute_query(Test->conn_rwsplit, "insert into t1 values(1);"); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_insert(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, 1), + "Wrong check_com_insert result\n"); + + Test->stop_timeout(); + Test->repl->sync_slaves(); + Test->tprintf("Doing 100 selects\n"); + + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + + for (i = 0; i < 100; i++) + { + Test->set_timeout(20); + Test->try_query(Test->conn_rwsplit, "select * from t1;"); + } + + Test->stop_timeout(); + Test->repl->sync_slaves(); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_select(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, + 100), + "Wrong check_com_select result\n"); + + + Test->set_timeout(20); + + get_global_status_allnodes(&selects[0], &inserts[0], Test->repl, silent); + Test->tprintf("Doing 100 inserts\n"); + + for (i = 0; i < 100; i++) + { + Test->set_timeout(20); + Test->try_query(Test->conn_rwsplit, "insert into t1 values(1);"); + } + + Test->stop_timeout(); + Test->repl->sync_slaves(); + get_global_status_allnodes(&new_selects[0], &new_inserts[0], Test->repl, silent); + Test->add_result(check_com_insert(&new_selects[0], &new_inserts[0], &selects[0], &inserts[0], Test->repl, + 100), + "Wrong check_com_insert result\n"); + + Test->close_rwsplit(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/rwsplit_conn_num.cpp b/maxscale-system-test/rwsplit_conn_num.cpp new file mode 100644 index 000000000..cf7437840 --- /dev/null +++ b/maxscale-system-test/rwsplit_conn_num.cpp @@ -0,0 +1,85 @@ +/** + * @file rwsplit_conn_num.cpp Checks connections are distributed equaly among backends + * - create 100 connections to RWSplit + * - check all slaves have equal number of connections + * - check sum of number of connections to all slaves is equal to 100 + */ + + +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + + Test->repl->connect(); + + const int TestConnNum = 100; + MYSQL *conn[TestConnNum]; + int i; + int conn_num; + int res = 0; + + MYSQL * backend_conn; + for (i = 0; i < Test->repl->N; i++) + { + backend_conn = open_conn(Test->repl->port[i], Test->repl->IP[i], Test->repl->user_name, Test->repl->password, + Test->repl->ssl); + execute_query(backend_conn, "SET GLOBAL max_connections = 200;"); + mysql_close(backend_conn); + } + + Test->tprintf("Creating %d connections to RWSplit router\n", TestConnNum); + for (i = 0; i < TestConnNum; i++) + { + conn[i] = Test->open_rwsplit_connection(); + } + Test->tprintf("Waiting 5 seconds\n"); + sleep(5); + + int ConnFloor = floor((float)TestConnNum / (Test->repl->N - 1)); + int ConnCell = ceil((float)TestConnNum / (Test->repl->N - 1)); + int TotalConn = 0; + + Test->tprintf("Checking connections to Master: should be %d\n", TestConnNum); + conn_num = get_conn_num(Test->repl->nodes[0], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + if (conn_num != TestConnNum) + { + Test->add_result(1, "number of connections to Master is %d\n", conn_num); + } + + Test->tprintf("Number of connections to each slave should be between %d and %d\n", ConnFloor, ConnCell); + Test->tprintf("Checking connections to each node\n"); + for (int i = 1; i < Test->repl->N; i++) + { + conn_num = get_conn_num(Test->repl->nodes[i], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + TotalConn += conn_num; + Test->tprintf("Connections to node %d (%s):\t%d\n", i, Test->repl->IP[i], conn_num); + if ((conn_num > ConnCell) || (conn_num < ConnFloor)) + { + Test->add_result(1, "wrong number of connections to node %d\n", i); + } + } + Test->tprintf("Total number of connections %d\n", TotalConn); + if (TotalConn != TestConnNum) + { + Test->add_result(1, "total number of connections is wrong\n"); + + } + for (i = 0; i < TestConnNum; i++) + { + mysql_close(conn[i]); + } + + int rval = Test->global_result; + delete Test; + return rval; +} + + + + diff --git a/maxscale-system-test/rwsplit_connect.cpp b/maxscale-system-test/rwsplit_connect.cpp new file mode 100644 index 000000000..e94381753 --- /dev/null +++ b/maxscale-system-test/rwsplit_connect.cpp @@ -0,0 +1,46 @@ +/** + * @file rwsplit_connect.cpp Check that there is one connection to Master and one connection to one of slaves + * - connecto to RWSplit + * - check number of connections on every backend, expect one active Slave and one connection to Master + */ + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + Test->repl->connect(); + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_ip()); + Test->connect_rwsplit(); + + unsigned int conn_num; + unsigned int all_conn = 0; + Test->tprintf("Sleeping 5 seconds\n"); + sleep(5); + Test->tprintf("Checking number of connections ot backend servers\n"); + for (int i = 0; i < Test->repl->N; i++) + { + conn_num = get_conn_num(Test->repl->nodes[i], Test->maxscale_ip(), Test->maxscale_hostname, (char *) "test"); + Test->tprintf("connections: %u\n", conn_num); + if ((i == 0) && (conn_num != 1)) + { + Test->add_result(1, " Master should have only 1 connection, but it has %d connection(s)\n", conn_num); + } + all_conn += conn_num; + } + if (all_conn != 2) + { + Test->add_result(1, + "there should be two connections in total: one to master and one to one of slaves, but number of connections is %d\n", + all_conn); + } + + Test->close_rwsplit(); + Test->repl->close_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/rwsplit_multi_stmt.cpp b/maxscale-system-test/rwsplit_multi_stmt.cpp new file mode 100644 index 000000000..8f5715a03 --- /dev/null +++ b/maxscale-system-test/rwsplit_multi_stmt.cpp @@ -0,0 +1,71 @@ +/** + * Readwritesplit multi-statment test + * + * - Configure strict multi-statement mode + * - Execute multi-statment query + * - All queries should go to the master + * - Configure for relaxed multi-statement mode + * - Execute multi-statment query + * - Only the multi-statement query should go to the master + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + + char master_id[200]; + char slave_id[200]; + + // Get the server IDs of the master and the slave + test.repl->connect(); + sprintf(master_id, "%d", test.repl->get_server_id(0)); + sprintf(slave_id, "%d", test.repl->get_server_id(1)); + + test.connect_maxscale(); + test.tprintf("Configuration: strict_multi_stmt=true"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "SELECT @@server_id", + slave_id), + "Query should be routed to slave"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "USE test; SELECT @@server_id", + master_id), + "Query should be routed to master"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "SELECT @@server_id", + master_id), + "All queries should be routed to master"); + + test.close_maxscale_connections(); + + // Reconfigure MaxScale + test.ssh_maxscale(true, "sed -i 's/strict_multi_stmt=true/strict_multi_stmt=false/' /etc/maxscale.cnf"); + test.restart_maxscale(); + + test.connect_maxscale(); + test.tprintf("Configuration: strict_multi_stmt=false"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "SELECT @@server_id", + slave_id), + "Query should be routed to slave"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "USE test; SELECT @@server_id", + master_id), + "Query should be routed to master"); + + test.add_result(execute_query_check_one(test.conn_rwsplit, + "SELECT @@server_id", + slave_id), + "Query should be routed to slave"); + + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/rwsplit_read_only_trx.cpp b/maxscale-system-test/rwsplit_read_only_trx.cpp new file mode 100644 index 000000000..644dc002c --- /dev/null +++ b/maxscale-system-test/rwsplit_read_only_trx.cpp @@ -0,0 +1,56 @@ +/** + * Readwritesplit read-only transaction test + * + * - Check that read-only transactions are routed to slaves + */ + +#include "testconnections.h" + +int main(int argc, char** argv) +{ + TestConnections test(argc, argv); + + char master_id[200]; + char slave_id[200]; + + // Get the server IDs of the master and the slave + test.repl->connect(); + sprintf(master_id, "%d", test.repl->get_server_id(0)); + sprintf(slave_id, "%d", test.repl->get_server_id(1)); + + test.connect_maxscale(); + + execute_query_silent(test.conn_rwsplit, "DROP TABLE test.t1"); + execute_query_silent(test.conn_rwsplit, "CREATE TABLE test.t1(id int)"); + + // Test read-only transaction with commit + test.try_query(test.conn_rwsplit, "START TRANSACTION READ ONLY"); + test.add_result(execute_query_check_one(test.conn_rwsplit, "SELECT @@server_id", slave_id), + "Query should be routed to slave"); + test.try_query(test.conn_rwsplit, "COMMIT"); + + // Test read-only transaction with rollback + test.try_query(test.conn_rwsplit, "START TRANSACTION READ ONLY"); + test.add_result(execute_query_check_one(test.conn_rwsplit, "SELECT @@server_id", slave_id), + "Query should be routed to slave"); + test.try_query(test.conn_rwsplit, "ROLLBACK"); + + // Test normal transaction + test.try_query(test.conn_rwsplit, "START TRANSACTION"); + test.add_result(execute_query_check_one(test.conn_rwsplit, "SELECT @@server_id", master_id), + "Query should be routed to master"); + test.try_query(test.conn_rwsplit, "COMMIT"); + + // Test writes in read-only transaction + test.try_query(test.conn_rwsplit, "START TRANSACTION READ ONLY"); + test.add_result(execute_query_check_one(test.conn_rwsplit, "SELECT @@server_id", slave_id), + "Query should be routed to slave"); + test.add_result(execute_query(test.conn_rwsplit, "UPDATE test.t1 SET id=0") == 0, + "Query should fail"); + test.try_query(test.conn_rwsplit, "COMMIT"); + + + test.close_maxscale_connections(); + + return test.global_result; +} diff --git a/maxscale-system-test/rwsplit_readonly.cpp b/maxscale-system-test/rwsplit_readonly.cpp new file mode 100644 index 000000000..fcb92569d --- /dev/null +++ b/maxscale-system-test/rwsplit_readonly.cpp @@ -0,0 +1,255 @@ +/** + * @file rwsplit_readonly.cpp Test of the read-only mode for readwritesplit when master fails + * - check INSERTs via RWSplit + * - block Master + * - check SELECT and INSERT with -- fail_instantly, -- error_on_write, -- fail_on_write + */ + + +#include +#include +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +void test_all_ok(TestConnections *Test) +{ + /** Insert should work */ + Test->set_timeout(30); + Test->tprintf("Testing that writes and reads to all services work\n"); + Test->add_result(execute_query_silent(Test->conn_rwsplit, + "INSERT INTO test.readonly VALUES (1) -- fail_instantly"), + "Query to service with 'fail_instantly' should succeed\n"); + Test->set_timeout(30); + Test->add_result(execute_query_silent(Test->conn_master, + "INSERT INTO test.readonly VALUES (1) -- fail_on_write"), + "Query to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->add_result(execute_query_silent(Test->conn_slave, + "INSERT INTO test.readonly VALUES (1) -- error_on_write"), + "Query to service with 'error_on_write' should succeed\n"); + Test->set_timeout(30); + Test->add_result(execute_query_silent(Test->conn_rwsplit, "SELECT * FROM test.readonly -- fail_instantly"), + "Query to service with 'fail_instantly' should succeed\n"); + Test->set_timeout(30); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "Query to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "Query to service with 'error_on_write' should succeed\n"); +} + +void test_basic(TestConnections *Test) +{ + /** Check that everything is OK before blocking the master */ + Test->connect_maxscale(); + test_all_ok(Test); + + /** Block master */ + Test->stop_timeout(); + Test->repl->block_node(0); + sleep(10); + + /** Select to service with 'fail_instantly' should close the connection */ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_instantly'\n"); + Test->add_result(!execute_query_silent(Test->conn_rwsplit, "SELECT * FROM test.readonly -- fail_instantly"), + "SELECT to service with 'fail_instantly' should fail\n"); + + /** Other services should still work */ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + /** Insert to 'fail_on_write' should fail and close the connection */ + Test->set_timeout(30); + Test->tprintf("INSERT to 'fail_on_write'\n"); + Test->add_result(!execute_query_silent(Test->conn_master, + "INSERT INTO test.readonly VALUES (1) -- fail_on_write"), + "INSERT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(!execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should fail after an INSERT\n"); + + /** Insert to 'error_on_write' should fail but subsequent SELECTs should work */ + Test->set_timeout(30); + Test->tprintf("INSERT to 'error_on_write'\n"); + Test->add_result(!execute_query_silent(Test->conn_slave, + "INSERT INTO test.readonly VALUES (1) -- error_on_write"), + "INSERT to service with 'error_on_write' should fail\n"); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'fail_on_write' should succeed after an INSERT\n"); + + /** Close connections and try to create new ones */ + Test->set_timeout(30); + Test->close_maxscale_connections(); + Test->tprintf("Opening connections while master is blocked\n"); + Test->add_result(Test->connect_rwsplit() == 0, "Connection to 'fail_instantly' service should fail\n"); + Test->add_result(Test->connect_readconn_master() != 0, + "Connection to 'fail_on_write' service should succeed\n"); + Test->add_result(Test->connect_readconn_slave() != 0, + "Connection to 'error_on_write' service should succeed\n"); + + + /** The {fail|error}_on_write services should work and allow reads */ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + Test->close_maxscale_connections(); + Test->stop_timeout(); + Test->repl->unblock_node(0); + sleep(10); + + /** Check that everything is OK after unblocking */ + Test->connect_maxscale(); + test_all_ok(Test); + Test->close_maxscale_connections(); +} + +void test_complex(TestConnections *Test) +{ + /** Check that everything works before test */ + Test->connect_maxscale(); + test_all_ok(Test); + + /** Block master */ + Test->stop_timeout(); + Test->repl->block_node(0); + sleep(10); + + /** Select to service with 'fail_instantly' should close the connection */ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_instantly'\n"); + Test->add_result(!execute_query_silent(Test->conn_rwsplit, "SELECT * FROM test.readonly -- fail_instantly"), + "SELECT to service with 'fail_instantly' should fail\n"); + + /** The {fail|error}_on_write services should allow reads */ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + /** Unblock node and try to read */ + Test->stop_timeout(); + Test->repl->unblock_node(0); + sleep(10); + + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + /** Block slaves */ + Test->stop_timeout(); + Test->close_maxscale_connections(); + Test->repl->block_node(1); + Test->repl->block_node(2); + Test->repl->block_node(3); + sleep(20); + + /** Reconnect to MaxScale */ + Test->set_timeout(30); + Test->connect_maxscale(); + + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + Test->stop_timeout(); + Test->repl->unblock_node(1); + Test->repl->unblock_node(2); + Test->repl->unblock_node(3); + sleep(10); + + + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should succeed\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should succeed\n"); + + /** Block all nodes */ + Test->stop_timeout(); + Test->repl->block_node(0); + Test->repl->block_node(1); + Test->repl->block_node(2); + Test->repl->block_node(3); + sleep(10); + + /** SELECTs should fail*/ + Test->set_timeout(30); + Test->tprintf("SELECT to 'fail_on_write'\n"); + Test->add_result(!execute_query_silent(Test->conn_master, "SELECT * FROM test.readonly -- fail_on_write"), + "SELECT to service with 'fail_on_write' should fail\n"); + Test->set_timeout(30); + Test->tprintf("SELECT to 'error_on_write'\n"); + Test->add_result(!execute_query_silent(Test->conn_slave, "SELECT * FROM test.readonly -- error_on_write"), + "SELECT to service with 'error_on_write' should fail\n"); + Test->stop_timeout(); + Test->repl->unblock_node(0); + Test->repl->unblock_node(1); + Test->repl->unblock_node(2); + Test->repl->unblock_node(3); + sleep(10); + + /** Reconnect and check that everything works after the test */ + Test->close_maxscale_connections(); + Test->connect_maxscale(); + test_all_ok(Test); + Test->close_maxscale_connections(); +} + +int main(int argc, char *argv[]) +{ + + TestConnections *Test = new TestConnections(argc, argv); + + /** Prepare for tests */ + Test->stop_timeout(); + Test->connect_maxscale(); + execute_query_silent(Test->conn_rwsplit, "DROP TABLE IF EXISTS test.readonly\n"); + execute_query_silent(Test->conn_rwsplit, "CREATE TABLE test.readonly(id int)\n"); + Test->close_maxscale_connections(); + + /** Basic tests */ + test_basic(Test); + + /** More complex tests */ + test_complex(Test); + + /** Clean up test environment */ + Test->repl->flush_hosts(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/rwsplit_readonly_stress.cpp b/maxscale-system-test/rwsplit_readonly_stress.cpp new file mode 100644 index 000000000..3bed746e9 --- /dev/null +++ b/maxscale-system-test/rwsplit_readonly_stress.cpp @@ -0,0 +1,95 @@ +/** + * @file rwsplit_readonly.cpp Test of the read-only mode for readwritesplit when master fails with load + * - start query threads which does SELECTs in the loop + * - every 10 seconds block Master and then after another 10 seconds unblock master + */ + + +#include +#include +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" + +#define THREADS 16 + +static int running = 0; + +void* query_thread(void *data) +{ + TestConnections *Test = (TestConnections*)data; + int iter = 0; + + while (!running) + { + sleep(1); + } + + while (running) + { + MYSQL* mysql = iter % 200 == 0 ? + Test->open_readconn_master_connection() : + Test->open_readconn_slave_connection(); + + if (!mysql) + { + Test->tprintf("Failed to connect to MaxScale.\n"); + } + + for (int i = 0; i < 100; i++) + { + if (execute_query_silent(mysql, "select repeat('a', 1000)")) + { + Test->add_result(1, "Query number %d failed: %s\n", iter + i, mysql_error(mysql)); + } + } + mysql_close(mysql); + iter += 100; + } + + return NULL; +} + +int main(int argc, char *argv[]) +{ + + TestConnections *Test = new TestConnections(argc, argv); + pthread_t threads[THREADS]; + + Test->stop_timeout(); + Test->log_copy_interval = 300; + Test->execute_maxadmin_command((char *) "disable log-priority info"); + + for (int i = 0; i < THREADS; i++) + { + pthread_create(&threads[i], NULL, query_thread, Test); + } + + running = 1; + + int iterations = (Test->smoke ? 5 : 25); + + for (int i = 0; i < iterations; i++) + { + Test->repl->block_node(0); + sleep(10); + Test->repl->unblock_node(0); + sleep(10); + } + + Test->tprintf("Waiting for all threads to finish\n"); + running = 0; + + for (int i = 0; i < THREADS; i++) + { + void* val; + pthread_join(threads[i], &val); + } + + /** Clean up test environment */ + Test->repl->flush_hosts(); + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/schemarouter_duplicate_db.cpp b/maxscale-system-test/schemarouter_duplicate_db.cpp new file mode 100644 index 000000000..882fb5da2 --- /dev/null +++ b/maxscale-system-test/schemarouter_duplicate_db.cpp @@ -0,0 +1,35 @@ +/** + * @file schemarouter_duplicate_db.cpp - Schemarouter duplicate database detection test + * + * - Start MaxScale + * - create DB on all nodes (directly via Master) + * - Connect to schemarouter + * - Execute query and expect failure + * - Check that message about duplicate databases is logged into error log + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + Test->set_timeout(30); + Test->connect_maxscale(); + + /** Create a database on all nodes */ + execute_query(Test->conn_master, "DROP DATABASE IF EXISTS duplicate;"); + execute_query(Test->conn_master, "CREATE DATABASE duplicate;"); + + Test->add_result(execute_query(Test->conn_rwsplit, "SELECT 1") == 0, + "Query should fail when duplicate database is found."); + Test->stop_timeout(); + sleep(10); + Test->check_log_err((char *) "Duplicate databases found", true); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/script.cpp b/maxscale-system-test/script.cpp new file mode 100644 index 000000000..03359f899 --- /dev/null +++ b/maxscale-system-test/script.cpp @@ -0,0 +1,182 @@ +/** + * @file script.cpp - test for running external script feature (MXS-121) + * - setup Maxscale to execute script on folowing events: + * - for MariaDB monitor: master_down,master_up, slave_up, server_down ,server_up,lost_master,lost_slave,new_master,new_slave + * - for Galera monitor: events=master_down,master_up, slave_up, server_down ,server_up,lost_master,lost_slave,new_master,new_slave,server_down,server_up,synced_down,synced_up + * - for Galera monitor set also 'disable_master_role_setting=true' + * - block master, unblock master, block node1, unblock node1 + * - expect following as a script output: + * @verbatim +--event=master_down --initiator=server1_IP:port --nodelist=server1_IP:port,server2_IP:port,server3_IP:port,server4_IP:port +--event=master_up --initiator=server1_IP:port --nodelist=server1_IP:port,server2_IP:port,server3_IP:port,server4_IP:port +--event=slave_up --initiator=server2_IP:port --nodelist=server1_IP:port,server2_IP:port,server3_IP:port,server4_IP:port +@endverbatim + * - repeat test for Galera monitor: block node0, unblock node0, block node1, unblock node1 + * - expect following as a script output: + * @verbatim +--event=synced_down --initiator=gserver1_IP:port --nodelist=gserver1_IP:port,gserver2_IP:port,gserver3_IP:port,gserver4_IP:port +--event=synced_down --initiator=gserver2_IP:port --nodelist=gserver1_IP:port,gserver2_IP:port,gserver3_IP:port,gserver4_IP:port +--event=synced_up --initiator=gserver2_IP:port --nodelist=gserver1_IP:port,gserver2_IP:port,gserver3_IP:port,gserver4_IP:port + @endverbatim + * - make script non-executable + * - block and unblock node1 + * - check error log for 'The file cannot be executed: /home/$maxscale_access_user/script.sh' error + * - check if Maxscale still alive + */ + + +#include +#include "testconnections.h" + +void test_script_monitor(TestConnections* Test, Mariadb_nodes* nodes, char * expected_filename) +{ + Test->set_timeout(200); + Test->ssh_maxscale(true, "cd %s;" + "truncate -s 0 script_output;" + "chown maxscale:maxscale script_output", + Test->maxscale_access_homedir); + sleep(10); + + Test->tprintf("Block master node"); + nodes->block_node(0); + + Test->tprintf("Sleeping"); + sleep(10); + + Test->tprintf("Unblock master node"); + nodes->unblock_node(0); + + Test->tprintf("Sleeping"); + sleep(10); + + Test->tprintf("Block node1"); + nodes->block_node(1); + + Test->tprintf("Sleeping"); + sleep(10); + + Test->tprintf("Unblock node1"); + nodes->unblock_node(1); + + Test->tprintf("Sleeping"); + sleep(10); + + Test->tprintf("Comparing results"); + + if (Test->ssh_maxscale(false, "diff %s/script_output %s", Test->maxscale_access_homedir, + expected_filename) != 0) + { + Test->ssh_maxscale(true, "cat %s/script_output", Test->maxscale_access_homedir); + Test->add_result(1, "Wrong script output!"); + } + else + { + Test->tprintf("Script output is OK!"); + } +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(100); + + Test->tprintf("Creating script on Maxscale machine"); + + + Test->ssh_maxscale(false, + "%s rm -rf %s/script; mkdir %s/script; echo \"echo \\$* >> %s/script_output\" > %s/script/script.sh; \ + chmod a+x %s/script/script.sh; chmod a+x %s; %s chown maxscale:maxscale %s/script -R", + Test->maxscale_access_sudo, Test->maxscale_access_homedir, + Test->maxscale_access_homedir, Test->maxscale_access_homedir, + Test->maxscale_access_homedir, Test->maxscale_access_homedir, Test->maxscale_access_homedir, + Test->maxscale_access_sudo, + Test->maxscale_access_homedir); + + Test->restart_maxscale(); + + FILE * f; + f = fopen("script_output_expected", "w"); + fprintf(f, "--event=master_down --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->repl->IP_private[0], Test->repl->port[0], + Test->repl->IP_private[1], Test->repl->port[1], + Test->repl->IP_private[2], Test->repl->port[2], + Test->repl->IP_private[3], Test->repl->port[3]); + fprintf(f, "--event=master_up --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->repl->IP_private[0], Test->repl->port[0], + Test->repl->IP_private[0], Test->repl->port[0], + Test->repl->IP_private[1], Test->repl->port[1], + Test->repl->IP_private[2], Test->repl->port[2], + Test->repl->IP_private[3], Test->repl->port[3]); + fprintf(f, "--event=slave_up --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->repl->IP_private[1], Test->repl->port[1], + Test->repl->IP_private[0], Test->repl->port[0], + Test->repl->IP_private[1], Test->repl->port[1], + Test->repl->IP_private[2], Test->repl->port[2], + Test->repl->IP_private[3], Test->repl->port[3]); + fclose(f); + + f = fopen("script_output_expected_galera", "w"); + fprintf(f, "--event=synced_down --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->galera->IP_private[0], Test->galera->port[0], + Test->galera->IP_private[1], Test->galera->port[1], + Test->galera->IP_private[2], Test->galera->port[2], + Test->galera->IP_private[3], Test->galera->port[3]); + fprintf(f, "--event=synced_up --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->galera->IP_private[0], Test->galera->port[0], + Test->galera->IP_private[0], Test->galera->port[0], + Test->galera->IP_private[1], Test->galera->port[1], + Test->galera->IP_private[2], Test->galera->port[2], + Test->galera->IP_private[3], Test->galera->port[3]); + fprintf(f, "--event=synced_down --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->galera->IP_private[1], Test->galera->port[1], + Test->galera->IP_private[0], Test->galera->port[0], + Test->galera->IP_private[2], Test->galera->port[2], + Test->galera->IP_private[3], Test->galera->port[3]); + fprintf(f, "--event=synced_up --initiator=[%s]:%d --nodelist=[%s]:%d,[%s]:%d,[%s]:%d,[%s]:%d\n", + Test->galera->IP_private[1], Test->galera->port[1], + Test->galera->IP_private[0], Test->galera->port[0], + Test->galera->IP_private[1], Test->galera->port[1], + Test->galera->IP_private[2], Test->galera->port[2], + Test->galera->IP_private[3], Test->galera->port[3]); + fclose(f); + + Test->tprintf("Copying expected script output to Maxscale machine"); + char str[2048]; + sprintf(str, + "scp -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet script_output_expected* %s@%s:%s/", + Test->maxscale_keyfile, Test->maxscale_access_user, Test->maxscale_IP, Test->maxscale_access_homedir); + system(str); + + sprintf(str, "%s/script_output_expected", Test->maxscale_access_homedir); + test_script_monitor(Test, Test->repl, str); + sprintf(str, "%s/script_output_expected_galera", Test->maxscale_access_homedir); + test_script_monitor(Test, Test->galera, str); + + Test->set_timeout(200); + + Test->tprintf("Making script non-executable"); + Test->ssh_maxscale(true, "chmod a-x %s/script/script.sh", Test->maxscale_access_homedir); + + sleep(3); + + Test->tprintf("Block node1"); + Test->repl->block_node(1); + + Test->tprintf("Sleeping"); + sleep(10); + + Test->tprintf("Unblock node1"); + Test->repl->unblock_node(1); + + sleep(15); + + Test->tprintf("Cheching Maxscale logs"); + Test->check_log_err((char *) "Cannot execute file" , true); + + Test->tprintf("checking if Maxscale is alive"); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/server_weight.cpp b/maxscale-system-test/server_weight.cpp new file mode 100644 index 000000000..d358c9118 --- /dev/null +++ b/maxscale-system-test/server_weight.cpp @@ -0,0 +1,158 @@ +/** + * @file server_weight.cpp Checks if 'weightby' parameter works + * - use Galera setup, configure Maxscale + * @verbatim +[RW Split Router] +type=service +router=readwritesplit +servers=server1,server2,server3,server4 +weightby=serversize_rws +user=skysql +passwd=skysql + +[Read Connection Router] +type=service +router=readconnroute +router_options=synced +servers=server1,server2,server3,server4 +weightby=serversize +user=skysql +passwd=skysql + +[server1] +type=server +address=###server_IP_1### +port=###server_port_1### +protocol=MySQLBackend +serversize=1 +serversize_rws=1 + +[server2] +type=server +address=###server_IP_2### +port=###server_port_2### +protocol=MySQLBackend +serversize=2 +serversize_rws=3000000 + +[server3] +type=server +address=###server_IP_3### +port=###server_port_3### +protocol=MySQLBackend +serversize=3 +serversize_rws=2000000 + +[server4] +type=server +address=###server_IP_4### +port=###server_port_4### +protocol=MySQLBackend +serversize=0 +serversize_rws=1000000 +@endverbatim + * - create 60 connections to ReadConn master + * - expect: node1 - 10, node2 - 20, node3 - 30, node4 - 0 + * - create 60 connections to RWSplit + * - expect all connections on only one slave + * - check error log, it should not contain "Unexpected parameter 'weightby'" + */ + + +#include "testconnections.h" + +void check_conn_num(TestConnections* Test, int * Nc, unsigned int conn_num) +{ + for (int i = 0; i < 4; i++) + { + conn_num = get_conn_num(Test->galera->nodes[i], Test->maxscale_IP, Test->maxscale_hostname, (char *) "test"); + Test->tprintf("connections to node %d: %u (expected: %u)\n", i, conn_num, Nc[i]); + if ((i < 4) && (Nc[i] != conn_num)) + { + Test->add_result(1, "Read: Expected number of connections to node %d is %d\n", i, Nc[i]); + } + } +} + +int main(int argc, char *argv[]) +{ + int maxscale_conn_num = 60; + MYSQL *conn_read[maxscale_conn_num]; + MYSQL *conn_rwsplit[maxscale_conn_num]; + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + int i; + + Test->galera->connect(); + + Test->tprintf("Connecting to ReadConnMaster on %s\n", Test->maxscale_IP); + for (i = 0; i < maxscale_conn_num; i++) + { + conn_read[i] = Test->open_readconn_master_connection(); + } + + Test->stop_timeout(); + Test->tprintf("Sleeping 15 seconds\n"); + sleep(15); + + unsigned int conn_num; + int Nc[4]; + + Nc[0] = maxscale_conn_num / 6; + Nc[1] = maxscale_conn_num / 3; + Nc[2] = maxscale_conn_num / 2; + Nc[3] = 0; + + Test->set_timeout(30); + check_conn_num(Test, Nc, conn_num); + + for (i = 0; i < maxscale_conn_num; i++) + { + mysql_close(conn_read[i]); + } + + Test->stop_timeout(); + Test->tprintf("Sleeping 15 seconds\n"); + sleep(15); + + Test->set_timeout(30); + Test->tprintf("Connecting to RWSplit on %s\n", Test->maxscale_IP); + for (i = 0; i < maxscale_conn_num; i++) + { + conn_rwsplit[i] = Test->open_rwsplit_connection(); + } + + Test->stop_timeout(); + Test->tprintf("Sleeping 15 seconds\n"); + sleep(15); + + /** Readwritesplit should always create a connection to the master. For + * this test we use the priority mechanism to force the first node as + * the master since Galera clusters don't have a deterministic master node. */ + Nc[1] = maxscale_conn_num / 2; + Nc[2] = maxscale_conn_num / 3; + Nc[3] = maxscale_conn_num / 6; + Nc[0] = maxscale_conn_num; + + Test->set_timeout(30); + check_conn_num(Test, Nc, conn_num); + + + for (i = 0; i < maxscale_conn_num; i++) + { + mysql_close(conn_rwsplit[i]); + } + Test->galera->close_connections(); + + Test->check_log_err((char *) "Unexpected parameter 'weightby'", false); + Test->check_log_err((char *) + "Weighting parameter 'serversize' with a value of 0 for server 'server4' rounds down to zero", true); + + // Pre-1.3.0 failure message + //Test->check_log_err((char *) "Server 'server4' has no value for weighting parameter 'serversize', no queries will be routed to this server", true); + + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/ses_bigmem.cpp b/maxscale-system-test/ses_bigmem.cpp new file mode 100644 index 000000000..85fd69316 --- /dev/null +++ b/maxscale-system-test/ses_bigmem.cpp @@ -0,0 +1,53 @@ +/** + * @file ses_bigmem Executes a lot of session commands with "disable_sescmd_history=true" and check that memory consumption is not increasing + * (relates to MXS-672 "maxscale possible memory leak" + */ + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + unsigned long maxscale_mem; + + Test->set_timeout(10); + + Test->connect_maxscale(); + int iterations = Test->smoke ? 100000 : 1000000; + int r = Test->smoke ? 1 : 3; + + for (int j = 0; j < r; j++) + { + for (int i = 0; i < iterations; i++) + { + Test->set_timeout(10); + Test->try_query(Test->routers[j], (char*) "set autocommit=0;"); + Test->try_query(Test->routers[j], (char*) "select 1;"); + Test->try_query(Test->routers[j], (char*) "set autocommit=1;"); + Test->try_query(Test->routers[j], (char*) "select 2;"); + if ((i / 100) * 100 == i) + { + Test->tprintf("i=%d\n", i); + } + } + + maxscale_mem = Test->get_maxscale_memsize(); + Test->tprintf("Maxscale process uses %lu KBytes\n", maxscale_mem); + + if (maxscale_mem > 2000000) + { + Test->add_result(1, "Maxscale consumes too much memory\n"); + } + } + + Test->check_maxscale_alive(); + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/session_hang/run_setmix.sh b/maxscale-system-test/session_hang/run_setmix.sh new file mode 100755 index 000000000..2510d2637 --- /dev/null +++ b/maxscale-system-test/session_hang/run_setmix.sh @@ -0,0 +1,6 @@ +#!/bin/bash +for ((i=0 ; i<100 ; i++)) ; +do + mysql --host=$maxscale_IP -P 4006 -u $node_user -p$node_password --verbose --force --unbuffered=true --disable-reconnect $ssl_options > /dev/null < $test_dir/session_hang/setmix.sql >& /dev/null +done + diff --git a/maxscale-system-test/session_hang/setmix.sql b/maxscale-system-test/session_hang/setmix.sql new file mode 100644 index 000000000..1dce05d68 --- /dev/null +++ b/maxscale-system-test/session_hang/setmix.sql @@ -0,0 +1,37 @@ +set autocommit=0; +use mysql; +set autocommit=1; +use test; +set autocommit=0; +use mysql; +set autocommit=1; +select user,host from user; +set autocommit=0; +use fakedb; +use test; +use mysql; +use dontuse; +use mysql; +drop table if exists t1; +commit; +use test; +use mysql; +set autocommit=1; +create table t1(id integer primary key); +insert into t1 values(5); +use test; +use mysql; +select user from user; +set autocommit=0; +set autocommit=1; +set autocommit=0; +insert into mysql.t1 values(7); +use mysql; +rollback work; +commit; +delete from mysql.t1 where id=7; +insert into mysql.t1 values(7); +select host,user from mysql.user; +set autocommit=1; +delete from mysql.t1 where id = 7; +select 1 as "endof cycle" from dual; diff --git a/maxscale-system-test/session_hang/simpletest.pl b/maxscale-system-test/session_hang/simpletest.pl new file mode 100644 index 000000000..2d43da40f --- /dev/null +++ b/maxscale-system-test/session_hang/simpletest.pl @@ -0,0 +1,31 @@ +#!/usr/bin/perl + +my $host = $ENV{'node_000_network'}; +my $port = $ENV{'node_000_port'}; +my $user = $ENV{'node_user'}; +my $password = $ENV{'node_password'}; +my $test_dir = $ENV{'test_dir'}; + +use strict; +use DBI; + +my $dsn = "DBI:mysql:database=test;host=$host;port=$port;mysql_use_result=0;mysql_server_prepare=1;mysql_ssl_client_key=$test_dir/ssl-cert/client-key.pem;mysql_ssl_client_cert=$test_dir/ssl-cert/client-cert.pem;"; +my $dbh = DBI->connect($dsn, $user, $password) or die "Failed to connect!"; +my $sth = $dbh->prepare("SELECT id, \@\@server_id from test.t1 where id=(?)"); + +$sth->bind_param(1, "%"); # placeholders are numbered from 1 + +for (my $i=0; $i<100000; $i++) { + if ($i % 5000 == 0) { + print "$i\n"; + } + if (defined($sth)) { + $sth->execute($i) or warn "Did not execute successfully: ".$dbh->errstr; + #DBI::dump_results($sth); + } +} + +$sth->finish(); +$dbh->disconnect() or warn "Did not successfully disconnect from backend!"; + + diff --git a/maxscale-system-test/session_limits.cpp b/maxscale-system-test/session_limits.cpp new file mode 100644 index 000000000..3ed6574a4 --- /dev/null +++ b/maxscale-system-test/session_limits.cpp @@ -0,0 +1,62 @@ +/** + * @file session_limits.cpp - test for 'max_sescmd_history' and 'connection_timeout' parameters + * - add follwoling to router configuration + * @verbatim +connection_timeout=30 +router_options=max_sescmd_history=10 +@endverbatim + * - open session + * - wait 20 seconds, check if session is alive, expect ok + * - wait 20 seconds more, check if session is alive, expect failure + * - open new session + * - execute 10 session commands + * - check if session is alive, expect ok + * - execute one more session commad, excpect failure + */ + + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(200); + int i; + char sql[256]; + + Test->tprintf("Open session and wait 20 seconds\n"); + Test->connect_maxscale(); + sleep(20); + Test->tprintf("Execute query to check session\n"); + Test->try_query(Test->conn_rwsplit, "SELECT 1"); + + Test->tprintf("Wait 35 seconds more and try quiry again expecting failure\n"); + sleep(35); + if (execute_query(Test->conn_rwsplit, "SELECT 1") == 0) + { + Test->add_result(1, "Session was not closed after 40 seconds\n"); + } + Test->close_maxscale_connections(); + + Test->tprintf("Open session and execute 10 session commands\n"); + fflush(stdout); + Test->connect_maxscale(); + for (i = 0; i < 10; i++) + { + sprintf(sql, "set @test=%d", i); + Test->try_query(Test->conn_rwsplit, sql); + } + Test->tprintf("done!\n"); + + Test->tprintf("Execute one more session command and expect message in error log\n"); + execute_query(Test->conn_rwsplit, "set @test=11"); + sleep(5); + Test->check_log_err((char *) "Router session exceeded session command history limit", true); + Test->close_maxscale_connections(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/setup_binlog.cpp b/maxscale-system-test/setup_binlog.cpp new file mode 100644 index 000000000..f0858b316 --- /dev/null +++ b/maxscale-system-test/setup_binlog.cpp @@ -0,0 +1,69 @@ +/** + * @file setup_binlog.cpp test of simple binlog router setup + * - setup one master, one slave directly connected to real master and two slaves connected to binlog router + * - create table and put data into it using connection to master + * - check data using direct commection to all backend + * - compare sha1 checksum of binlog file on master and on Maxscale machine + * - START TRANSACTION + * - SET autocommit = 0 + * - INSERT INTO t1 VALUES(111, 10) + * - check SELECT * FROM t1 WHERE fl=10 - expect one row x=111 + * - ROLLBACK + * - INSERT INTO t1 VALUES(112, 10) + * - check SELECT * FROM t1 WHERE fl=10 - expect one row x=112 and no row with x=111 + * - DELETE FROM t1 WHERE fl=10 + * - START TRANSACTION + * - INSERT INTO t1 VALUES(111, 10) + * - check SELECT * FROM t1 WHERE fl=10 - expect one row x=111 from master and slave + * - DELETE FROM t1 WHERE fl=10 + * - compare sha1 checksum of binlog file on master and on Maxscale machine + * - Re-create t1 table via master + * - STOP SLAVE against Maxscale binlog + * - put data to t1 + * - START SLAVE against Maxscale binlog + * - wait to let replication happens + * - check data on all nodes + * - chack sha1 + * - repeat last test with FLUSH LOGS on master 1. before putting data to Master 2. after putting data to master + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" + + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(3000); + int options_set = 3; + if (Test->smoke) + { + options_set = 1; + } + + Test->repl->connect(); + execute_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1;"); + Test->repl->close_connections(); + sleep(5); + + for (int option = 0; option < options_set; option++) + { + Test->binlog_cmd_option = option; + Test->start_binlog(); + test_binlog(Test); + } + + Test->check_log_err("SET NAMES utf8mb4", false); + Test->check_log_err("set autocommit=1", false); + Test->check_log_err("select USER()", false); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/setup_binlog_crc_32.cpp b/maxscale-system-test/setup_binlog_crc_32.cpp new file mode 100644 index 000000000..e1fbd7d62 --- /dev/null +++ b/maxscale-system-test/setup_binlog_crc_32.cpp @@ -0,0 +1,46 @@ +/** + * @file setup_binlog test of simple binlog router setup + * setup one master, one slave directly connected to real master and two slaves connected to binlog router + * create table and put data into it using connection to master + * check data using direct commection to all backend + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + + if (!Test->smoke) + { + Test->set_timeout(1000); + + Test->binlog_cmd_option = 1; + Test->start_binlog(); + + Test->repl->connect(); + + create_t1(Test->repl->nodes[0]); + Test->add_result(insert_into_t1(Test->repl->nodes[0], 4), "error inserting data into t1\n"); + Test->tprintf("Sleeping to let replication happen\n"); + sleep(30); + + for (int i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->add_result(select_from_t1(Test->repl->nodes[i], 4), "error SELECT for t1\n"); + } + + Test->repl->close_connections(); + } + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/setup_binlog_crc_none.cpp b/maxscale-system-test/setup_binlog_crc_none.cpp new file mode 100644 index 000000000..fe5c2f991 --- /dev/null +++ b/maxscale-system-test/setup_binlog_crc_none.cpp @@ -0,0 +1,42 @@ +/** + * @file setup_binlog test of simple binlog router setup + * setup one master, one slave directly connected to real master and two slaves connected to binlog router + * create table and put data into it using connection to master + * check data using direct commection to all backend + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + + if (!Test->smoke) + { + Test->binlog_cmd_option = 2; + Test->start_binlog(); + + Test->repl->connect(); + + create_t1(Test->repl->nodes[0]); + Test->add_result(insert_into_t1(Test->repl->nodes[0], 4), "error inserting data into t1\n"); + Test->tprintf("Sleeping to let replication happen\n"); + sleep(30); + + for (int i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->add_result(select_from_t1(Test->repl->nodes[i], 4), "error SELECT for t1\n"); + } + + Test->repl->close_connections(); + } + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/sharding.cpp b/maxscale-system-test/sharding.cpp new file mode 100644 index 000000000..7c8138387 --- /dev/null +++ b/maxscale-system-test/sharding.cpp @@ -0,0 +1,152 @@ +/** + * @file sharding.cpp - Schema router test and regression test for MXS-78, MXS-79 + * + * @verbatim +[MySQL Monitor] +type=monitor +module=mysqlmon +servers= server1, server2,server3 ,server4 +user=skysql +passwd= skysql + +[Sharding router] +type=service +router=schemarouter +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +auth_all_servers=1 +filters=QLA + + @endverbatim + * - stop all slaves in Master/Slave setup + * - restrt Maxscale + * - using direct connection to backend nodes + * - create user0...userN users on all nodes + * - create sharddb on all nodes + * - create database 'shard_db%d" on node %d (% from 0 to N) + * - GRANT SELECT,USAGE,CREATE ON shard_db.* TO 'user%d'@'%%' only on node %d + * - for every user%d + * - open connection to schemarouter using user%d + * - CREATE TABLE table%d (x1 int, fl int) + * - check if Maxscale alive + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(30); + int i, j; + char str[256]; + char str1[256]; + char user_str[256]; + char pass_str[256]; + + Test->repl->stop_slaves(); + + Test->restart_maxscale(); + + Test->repl->connect(); + + for (i = 0; i < Test->repl->N; i++) //nodes + { + for (j = 0; j < Test->repl->N; j++) //users + { + Test->set_timeout(30); + execute_query(Test->repl->nodes[i], "DROP USER 'user%d'@'%%';", j); + execute_query(Test->repl->nodes[i], "CREATE USER 'user%d'@'%%' IDENTIFIED BY 'pass%d';", j, j); + execute_query(Test->repl->nodes[i], "DROP DATABASE IF EXISTS shard_db"); + } + + execute_query(Test->repl->nodes[i], "DROP DATABASE IF EXISTS shard_db%d", i); + execute_query(Test->repl->nodes[i], "CREATE DATABASE shard_db%d", i); + } + Test->stop_timeout(); + + sleep(10); + for (i = 0; i < Test->repl->N; i++) //nodes + { + Test->set_timeout(30); + Test->tprintf("Node %d\t", i); + Test->tprintf("Creating shard_db\t"); + execute_query(Test->repl->nodes[i], "CREATE DATABASE shard_db"); + Test->add_result(execute_query(Test->repl->nodes[i], + "GRANT SELECT,USAGE,CREATE ON shard_db.* TO 'user%d'@'%%'", i), "Query should succeed."); + } + + Test->repl->close_connections(); + Test->stop_timeout(); + sleep(30); + MYSQL * conn; + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(30); + sprintf(user_str, "user%d", i); + sprintf(pass_str, "pass%d", i); + Test->tprintf("Open connection to Sharding router using %s %s\n", user_str, pass_str); + conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, (char *) "shard_db", user_str, pass_str, + Test->ssl); + Test->add_result(execute_query(conn, "CREATE TABLE table%d (x1 int, fl int);", i), "Query should succeed."); + } + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(30); + sprintf(user_str, "user%d", i); + sprintf(pass_str, "pass%d", i); + Test->tprintf("Open connection to Sharding router using %s %s\n", user_str, pass_str); + conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, (char *) "shard_db", user_str, pass_str, + Test->ssl); + + sprintf(str, "SHOW TABLES;"); + Test->tprintf("%s\n", str); + sprintf(str1, "table%d", i); + Test->tprintf("Table should be %s\n", str1); + Test->add_result(execute_query_check_one(conn, str, str1), "check failed\n"); + mysql_ping(conn); + mysql_close(conn); + } + + Test->connect_rwsplit(); + + Test->tprintf("Trying USE shard_db\n"); + execute_query(Test->conn_rwsplit, "USE shard_db"); + + for (i = 0; i < Test->repl->N; i++) + { + Test->add_result(execute_query(Test->conn_rwsplit, "USE shard_db%d", i), "Query should succeed."); + } + + mysql_close(Test->conn_rwsplit); + + Test->tprintf("Trying to connect with empty database name\n"); + conn = open_conn_db(Test->rwsplit_port, Test->maxscale_IP, (char *) "", user_str, pass_str, Test->ssl); + mysql_close(conn); + + Test->stop_timeout(); + Test->check_log_err((char *) "Length (0) is 0", false); + Test->check_log_err((char *) "Unable to parse query", false); + Test->check_log_err((char *) "query string allocation failed", false); + + /** Cleanup */ + for (i = 0; i < Test->repl->N; i++) + { + for (j = 0; j < Test->repl->N; j++) + { + Test->set_timeout(30); + execute_query(Test->repl->nodes[i], "DROP USER 'user%d'@'%%';", j); + execute_query(Test->repl->nodes[i], "DROP DATABASE IF EXISTS shard_db"); + } + + execute_query(Test->repl->nodes[i], "DROP DATABASE IF EXISTS shard_db%d", i); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/sharding_load_data.cpp b/maxscale-system-test/sharding_load_data.cpp new file mode 100644 index 000000000..385a6f507 --- /dev/null +++ b/maxscale-system-test/sharding_load_data.cpp @@ -0,0 +1,53 @@ +/** + * MXS-1160: LOAD DATA LOCAL INFILE with schemarouter + */ + +#include "testconnections.h" + +#include +#include +#include + +int main(int argc, char** argv) +{ + // Create a CSV file + unlink("data.csv"); + std::ofstream output("data.csv"); + std::stringstream ss; + + for (int i = 0; i < 100; i++) + { + ss << i << "\n"; + } + + output << ss.str(); + output.close(); + + TestConnections test(argc, argv); + test.repl->execute_query_all_nodes("DROP DATABASE db1"); + test.repl->connect(); + execute_query(test.repl->nodes[0], "CREATE DATABASE db1"); + execute_query(test.repl->nodes[0], "CREATE TABLE db1.t1(id INT)"); + test.connect_maxscale(); + + test.tprintf("Loading local data file"); + + test.try_query(test.conn_rwsplit, "LOAD DATA LOCAL INFILE 'data.csv' INTO TABLE db1.t1"); + + test.tprintf("Verifying that data was loaded"); + + long total = execute_query_count_rows(test.conn_rwsplit, "SELECT * FROM db1.t1"); + test.add_result(total != 100, "Expected 100 rows, got %ld", total); + + test.tprintf("Dropping tables and databases"); + + test.try_query(test.conn_rwsplit, "DROP TABLE db1.t1"); + test.try_query(test.conn_rwsplit, "DROP DATABASE db1"); + + test.close_maxscale_connections(); + + // Remove the test data + unlink("data.csv"); + + return test.global_result; +} diff --git a/maxscale-system-test/short_sessions.cpp b/maxscale-system-test/short_sessions.cpp new file mode 100644 index 000000000..6eb25789e --- /dev/null +++ b/maxscale-system-test/short_sessions.cpp @@ -0,0 +1,69 @@ +/** + * @file short_sessions.cpp Executes a lof of short queries, use own short session for every query (some relations to bug#424) + * + * - using RSplit create table + * - close connection + * - do 100 times: open connections to RWSplit, execute short INSERT, close connection + * - Select inserted rows through all services + * - check if Maxscale alive + */ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + int iterations = 100; + + TestConnections test(argc, argv); + test.set_timeout(20); + test.repl->connect(); + + MYSQL *conn = test.open_rwsplit_connection(); + execute_query(conn, "DROP DATABASE IF EXISTS test;"); + execute_query(conn, "CREATE DATABASE test;"); + execute_query(conn, "USE test;"); + create_t1(conn); + mysql_close(conn); + + test.tprintf("Executing %d inserts", iterations); + + for (int i = 0; i < iterations; i++) + { + char sql[100]; + sprintf(sql, "INSERT INTO t1 (x1, fl) VALUES(%d, 1);", i); + + test.set_timeout(15); + conn = test.open_rwsplit_connection(); + execute_query(conn, sql); + mysql_close(conn); + } + + test.set_timeout(20); + test.add_result(test.connect_maxscale(), "Failed to connect to MaxScale"); + + test.tprintf("Checking t1 table using RWSplit router"); + test.set_timeout(240); + test.add_result(execute_select_query_and_check(test.conn_rwsplit, (char *) "SELECT * FROM t1;", + iterations), "t1 is wrong"); + + test.tprintf("Checking t1 table using ReadConn router in master mode"); + test.set_timeout(240); + test.add_result(execute_select_query_and_check(test.conn_master, (char *) "SELECT * FROM t1;", + iterations), "t1 is wrong"); + + test.tprintf("Checking t1 table using ReadConn router in slave mode"); + test.set_timeout(240); + test.add_result(execute_select_query_and_check(test.conn_slave, (char *) "SELECT * FROM t1;", iterations), + "t1 is wrong"); + + test.set_timeout(20); + test.close_maxscale_connections(); + test.check_maxscale_alive(); + + return test.global_result; +} diff --git a/maxscale-system-test/show_monitor_crash.cpp b/maxscale-system-test/show_monitor_crash.cpp new file mode 100644 index 000000000..6a31e46fb --- /dev/null +++ b/maxscale-system-test/show_monitor_crash.cpp @@ -0,0 +1,31 @@ +/** + * @file show_monitor_crash.cpp show_monitor_crash regression case for crash if maxadmin 'show monitors' command is issued, but no monitor is not running + * + * - maxscale.cnf contains wrong monitor config (user name is wrong) + * - issue 'show monitors' maxadmin command + * - check for crash + */ + + + +#include +#include +#include "testconnections.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(100); + Test->execute_maxadmin_command((char *) "show monitors"); + sleep(5); + Test->check_log_err((char *) "Failed to start monitor", true); + Test->check_log_err((char *) "fatal signal 11", false); + + Test->check_maxscale_processes(1); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/slave_failover.cpp b/maxscale-system-test/slave_failover.cpp new file mode 100644 index 000000000..ecad48b09 --- /dev/null +++ b/maxscale-system-test/slave_failover.cpp @@ -0,0 +1,74 @@ +/** + * @file slave_failover.cpp Check how Maxscale works in case of one slave failure, only one slave is configured + * + * - Connect to RWSplit + * - find which backend slave is used for connection + * - blocm mariadb on the slave with firewall + * - wait 60 seconds + * - check which slave is used for connection now, expecting any other slave + * - check warning in the error log about broken slave + * - unblock mariadb backend (restore slave firewall settings) + * - check if Maxscale still alive + */ + + +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->set_timeout(20); + int res = 0; + + unsigned int current_slave; + unsigned int old_slave; + + printf("Connecting to RWSplit %s\n", Test->maxscale_IP); + if (Test->connect_rwsplit() != 0) + { + Test->add_result(1, "Error connection to RWSplit! Exiting\n"); + } + else + { + + Test->tprintf("Checking current slave\n"); + old_slave = Test->find_connected_slave( &res); + + Test->add_result(res, "no current slave\n"); + + Test->tprintf("Setup firewall to block mysql on old slave (oldslave is node %d)\n", old_slave); + if ((old_slave < 0) || (old_slave >= Test->repl->N)) + { + Test->add_result(1, "Active slave is not found\n"); + } + else + { + Test->repl->block_node(old_slave); + + Test->tprintf("Sleeping 60 seconds to let MaxScale to find new slave\n"); + Test->stop_timeout(); + sleep(60); + Test->set_timeout(20); + + current_slave = Test->find_connected_slave(&res); + if ((current_slave == old_slave) || (current_slave < 0)) + { + Test->add_result(1, "No failover happened\n"); + } + + Test->tprintf("Setup firewall back to allow mysql\n"); + Test->repl->unblock_node(old_slave); + + Test->close_rwsplit(); + + Test->check_maxscale_alive(); + Test->set_timeout(20); + } + Test->set_timeout(200); + Test->repl->start_replication(); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/slave_lag.cpp b/maxscale-system-test/slave_lag.cpp new file mode 100644 index 000000000..a0ffb9be2 --- /dev/null +++ b/maxscale-system-test/slave_lag.cpp @@ -0,0 +1,196 @@ +/** + * @file server_lag.cpp Create high INSERT load to create slave lag and check that Maxscale start routing queries to Master + * + * - in Maxscqale.cnf set max_slave_replication_lag=20 + * - in parallel thread execute as many INSERTs as possible + * - using "select @@server_id;" check that queries go to one of the slave + * - wait when slave lag > 20 (control lag using maxadmin interface) + * - check that now queries go to Master + */ + + +#include "testconnections.h" +#include "sql_t1.h" +#include "maxadmin_operations.h" + +char sql[1000000]; + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; +int exited = 0; +void *query_thread( void *ptr ); +void *checks_thread( void *ptr); + +TestConnections * Test; + +int check_lag(int * min_lag) +{ + char result[1024]; + char server_id[1024]; + char ma_cmd[256]; + int res_d; + int server1_id_d; + int server_id_d; + int i; + int ret = 0; + + *min_lag = 0; + for (i = 1; i < Test->repl->N; i++ ) + { + sprintf(ma_cmd, "show server server%d", i + 1); + get_maxadmin_param(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, ma_cmd, + (char *) "Slave delay:", result); + sscanf(result, "%d", &res_d); + Test->tprintf("server%d lag: %d\n", i + 1, res_d); + if (i == 1) + { + *min_lag = res_d; + } + if (*min_lag > res_d) + { + *min_lag = res_d; + } + } + Test->tprintf("Minimum lag: %d\n", *min_lag); + Test->connect_rwsplit(); + find_field(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale max_slave_replication_lag=20", + (char *) "@@server_id", &server_id[0]); + Test->close_rwsplit(); + sscanf(server_id, "%d", &server_id_d); + Test->tprintf("Connected to the server with server_id %d\n", server_id_d); + if ((server1_id_d == server_id_d)) + { + Test->add_result(1, "Connected to the master!\n"); + ret = 0; + } + else + { + Test->tprintf("Connected to slave\n"); + ret = 1; + } + return ret; +} + +int main(int argc, char *argv[]) +{ + + char server1_id[1024]; + int server1_id_d; + int i; + int min_lag = 0; + int ms; + + Test = new TestConnections(argc, argv); + Test->set_timeout(2000); + + Test->repl->connect(); + Test->connect_rwsplit(); + + // connect to the MaxScale server (rwsplit) + + if (Test->conn_rwsplit == NULL ) + { + printf("Can't connect to MaxScale\n"); + int rval = Test->global_result; + delete Test; + exit(1); + } + else + { + for ( i = 0; i < Test->repl->N; i++) + { + Test->tprintf("set max_connections = 200 for node %d\n", i); + execute_query(Test->repl->nodes[i], (char *) "set global max_connections = 200;"); + } + + create_t1(Test->conn_rwsplit); + create_t2(Test->conn_rwsplit); + + create_insert_string(sql, 50000, 1); + Test->tprintf("sql_len=%lu\n", strlen(sql)); + /* for ( i = 0; i < 100; i++) { + Test->try_query(Test->conn_rwsplit, sql); + }*/ + + pthread_t threads[1000]; + //pthread_t check_thread; + int iret[1000]; + //int check_iret; + int j; + exit_flag = 0; + /* Create independent threads each of them will execute function */ + for (j = 0; j < 100; j++) + { + iret[j] = pthread_create( &threads[j], NULL, query_thread, &sql); + } + + execute_query(Test->conn_rwsplit, (char *) "select @@server_id; -- maxscale max_slave_replication_lag=10"); + + find_field(Test->repl->nodes[0], (char *) "select @@server_id;", (char *) "@@server_id", &server1_id[0]); + sscanf(server1_id, "%d", &server1_id_d); + Test->tprintf("Master server_id: %d\n", server1_id_d); + + Test->close_rwsplit(); + + for (i = 0; i < 1000; i++) + { + ms = check_lag(&min_lag); + if ((ms = 0) && (min_lag < 20)) + { + Test->add_result(1, "Lag is small, but connected to master\n"); + } + if ((ms = 1) && (min_lag > 20)) + { + Test->add_result(1, "Lag is big, but connected to slave\n"); + } + } + + exit_flag = 1; + } + while (exited == 0) + { + Test->tprintf("Waiting for load thread end\n"); + sleep(5); + } + Test->repl->close_connections(); + Test->repl->start_replication(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + +void *query_thread( void *ptr ) +{ + MYSQL * conn; + conn = open_conn(Test->repl->port[0], Test->repl->IP[0], Test->repl->user_name, Test->repl->password, + Test->repl->ssl); + while (exit_flag == 0) + { + //execute_query(conn, (char *) "INSERT INTO t2 (x1, fl) SELECT x1,fl FROM t1"); + execute_query_silent(conn, (char *) ptr); + } + exited = 1; + return NULL; +} + +void *checks_thread( void *ptr ) +{ + char result[1024]; + for (int i = 0; i < 1000; i++) + { + get_maxadmin_param(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, + (char *) "show server server2", (char *) "Slave delay:", result); + printf("server2: %s\n", result); + get_maxadmin_param(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, + (char *) "show server server3", (char *) "Slave delay:", result); + printf("server3: %s\n", result); + get_maxadmin_param(Test->maxscale_IP, (char *) "admin", Test->maxadmin_password, + (char *) "show server server4", (char *) "Slave delay:", result); + printf("server4: %s\n", result); + } + exit_flag = 1; + return NULL; +} + diff --git a/maxscale-system-test/sql_const.h b/maxscale-system-test/sql_const.h new file mode 100644 index 000000000..2fb34007e --- /dev/null +++ b/maxscale-system-test/sql_const.h @@ -0,0 +1,25 @@ +#ifndef SQL_CONST_H +#define SQL_CONST_H + +const char * create_repl_user = + "grant replication slave on *.* to repl@'%%' identified by 'repl'; " + "FLUSH PRIVILEGES"; +const char * setup_slave = + "change master to MASTER_HOST='%s', " + "MASTER_USER='repl', " + "MASTER_PASSWORD='repl', " + "MASTER_LOG_FILE='%s', " + "MASTER_LOG_POS=%s, " + "MASTER_PORT=%d; " + "start slave;"; + +const char * setup_slave_no_pos = + "change master to MASTER_HOST='%s', " + "MASTER_USER='repl', " + "MASTER_PASSWORD='repl', " + "MASTER_LOG_FILE='mar-bin.000001', " + "MASTER_LOG_POS=4, " + "MASTER_PORT=%d"; + + +#endif // SQL_CONST_H diff --git a/maxscale-system-test/sql_queries.cpp b/maxscale-system-test/sql_queries.cpp new file mode 100644 index 000000000..729560af1 --- /dev/null +++ b/maxscale-system-test/sql_queries.cpp @@ -0,0 +1,112 @@ +/** + * @file sql_queries.cpp Execute long sql queries as well as "use" command (also used for bug648 "use database is sent forever with tee filter to a readwrite split service") + * - also used for 'sql_queries_pers1' and 'sql_queries_pers10' tests (with 'persistpoolmax=1' and 'persistpoolmax=10' for all servers) + * - for bug648: + * @verbatim +[RW Split Router] +type=service +router= readwritesplit +servers=server1, server2, server3,server4 +user=skysql +passwd=skysql +filters=TEE + +[TEE] +type=filter +module=tee +service=RW Split Router +@endverbatim + * + * - create t1 table and INSERT a lot of date into it + * @verbatim +INSERT INTO t1 (x1, fl) VALUES (0, 0), (1, 0), ...(15, 0); +INSERT INTO t1 (x1, fl) VALUES (0, 1), (1, 1), ...(255, 1); +INSERT INTO t1 (x1, fl) VALUES (0, 2), (1, 2), ...(4095, 2); +INSERT INTO t1 (x1, fl) VALUES (0, 3), (1, 3), ...(65535, 3); +@endverbatim + * - check date in t1 using all Maxscale services and direct connections to backend nodes + * - using RWSplit connections: + * + DROP TABLE t1 + * + DROP DATABASE IF EXISTS test1; + * + CREATE DATABASE test1; + * - execute USE test1 for all Maxscale service and backend nodes + * - create t1 table and INSERT a lot of date into it + * - check that 't1' exists in 'test1' DB and does not exist in 'test' + * - executes queries with syntax error against all Maxscale services + * + "DROP DATABASE I EXISTS test1;" + * + "CREATE TABLE " + * - check if Maxscale is alive + */ + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + int i, j; + int N = 4; + int iterations = 4; + + if (Test->smoke) + { + iterations = 1; + N = 2; + } + + Test->tprintf("Starting test\n"); + for (i = 0; i < iterations; i++) + { + + Test->tprintf("Connection to backend\n"); + Test->repl->connect(); + Test->tprintf("Connection to Maxscale\n"); + if (Test->connect_maxscale() != 0) + { + Test->add_result(1, "Error connecting to MaxScale"); + break; + } + + Test->tprintf("Filling t1 with data\n"); + Test->add_result(Test->insert_select(N), "insert-select check failed\n"); + + Test->tprintf("Creating database test1\n"); + Test->try_query(Test->conn_rwsplit, "DROP TABLE t1"); + Test->try_query(Test->conn_rwsplit, "DROP DATABASE IF EXISTS test1;"); + Test->try_query(Test->conn_rwsplit, "CREATE DATABASE test1;"); + Test->repl->sync_slaves(); + + Test->tprintf("Testing with database 'test1'\n"); + Test->add_result(Test->use_db( (char *) "test1"), "use_db failed\n"); + Test->add_result(Test->insert_select(N), "insert-select check failed\n"); + + Test->add_result(Test->check_t1_table(false, (char *) "test"), "t1 is found in 'test'\n"); + Test->add_result(Test->check_t1_table(true, (char *) "test1"), "t1 is not found in 'test1'\n"); + + Test->tprintf("Trying queries with syntax errors\n"); + for (j = 0; j < 3; j++) + { + execute_query(Test->routers[j], "DROP DATABASE I EXISTS test1;"); + execute_query(Test->routers[j], "CREATE TABLE "); + } + + // close connections + Test->close_maxscale_connections(); + Test->repl->close_connections(); + } + + Test->stop_timeout(); + Test->check_log_err((char *) "Length (0) is 0", false); + Test->check_log_err((char *) "Unable to parse query", false); + Test->check_log_err((char *) "query string allocation failed", false); + + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/sql_t1.cpp b/maxscale-system-test/sql_t1.cpp new file mode 100644 index 000000000..d3967dba1 --- /dev/null +++ b/maxscale-system-test/sql_t1.cpp @@ -0,0 +1,258 @@ +#include "sql_t1.h" + +#include + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static char** sql = NULL; +static size_t sql_size = 0; + +int execute_select_query_and_check(MYSQL *conn, char *sql, unsigned long long int rows) +{ + MYSQL_RES *res; + MYSQL_ROW row; + unsigned long long int i; + unsigned long long int num_fields; + unsigned long long int int_res; + unsigned long long int row_i = 0; + int test_result = 0; + unsigned long long int rows_from_select = 0; + int wait_i = 0; + + printf("Trying SELECT, num_of_rows=%llu\n", rows); + int res_alloc = 0; + if (conn != NULL) + { + rows_from_select = 0; + wait_i = 0; + while ((rows_from_select != rows) && (wait_i < 100)) + { + if (mysql_query(conn, sql) != 0) + { + printf("Error: can't execute SQL-query: %s\n", mysql_error(conn)); + } + + res = mysql_store_result(conn); + res_alloc = 1; + if (res == NULL) + { + printf("Error: can't get the result description\n"); + test_result = 1; + mysql_free_result(res); + res_alloc = 0; + wait_i++; + sleep(1); + } + else + { + rows_from_select = mysql_num_rows(res); + printf("rows=%llu\n", rows_from_select); + wait_i++; + if (rows_from_select != rows) + { + printf("Waiting 1 second and trying again...\n"); + mysql_free_result(res); + res_alloc = 0; + sleep(1); + } + } + } + + if (rows_from_select != rows) + { + printf("SELECT returned %llu rows instead of %llu!\n", rows_from_select, rows); + test_result = 1; + printf("sql was %s\n", sql); + } + else + { + num_fields = mysql_num_fields(res); + if (num_fields != 2) + { + printf("SELECT returned %llu fileds instead of 2!\n", num_fields); + test_result = 1; + } + if (mysql_num_rows(res) > 0) + { + while ((row = mysql_fetch_row(res)) != NULL) + { + for (i = 0; i < num_fields; i++) + { + sscanf(row[i], "%llu", &int_res); + if ((i == 0 ) && (int_res != row_i)) + { + printf("SELECT returned wrong result! %llu instead of expected %llu\n", int_res, row_i); + test_result = 1; + printf("sql was %s\n", sql); + } + } + row_i++; + } + } + } + if (res_alloc != 0) + { + mysql_free_result(res); + } + } + else + { + printf("FAILED: broken connection\n"); + test_result = 1; + } + + return test_result; +} +int create_t1(MYSQL * conn) +{ + int result = 0; + result += execute_query(conn, "DROP TABLE IF EXISTS t1;"); + printf("Creating test table\n"); + result += execute_query(conn, "CREATE TABLE t1 (x1 int, fl int);"); + return result; +} + +int create_t2(MYSQL * conn) +{ + int result = 0; + result += execute_query(conn, "DROP TABLE IF EXISTS t2;"); + printf("Creating test table\n"); + result += execute_query(conn, "CREATE TABLE t2 (x1 int, fl int);"); + return result; +} + +static const char ins1[] = "INSERT INTO t1 (x1, fl) VALUES "; + +int create_insert_string(char* sql, int N, int fl) +{ + char *wptr = sql; + + strcpy(wptr, ins1); + for (int i = 0; i < N; i++) + { + wptr = strchr(wptr, '\0'); + sprintf(wptr, "(%d, %d),", i, fl); + } + wptr = strrchr(wptr, ','); + sprintf(wptr, ";"); + return 0; +} + +char* allocate_insert_string(int fl, int N) +{ + char* rval = NULL; + + pthread_mutex_lock(&mutex); + + if (sql == NULL) + { + sql = (char**)calloc(16, sizeof(char*)); + sql_size = 16; + } + + if (fl >= sql_size) + { + fprintf(stderr, "Insert index %d is too large, setting it to %lu", fl, sql_size - 1); + fl = sql_size - 1; + } + + if (sql[fl] == NULL) + { + char tmpstr[256]; + sprintf(tmpstr, "(%d, %d),", N, fl); + sql[fl] = (char*)malloc(sizeof(ins1) + N * strlen(tmpstr) + 60); + create_insert_string(sql[fl], N, fl); + } + + rval = sql[fl]; + pthread_mutex_unlock(&mutex); + + return rval; +} + +int insert_into_t1(MYSQL *conn, int N) +{ + + int x = 16; + int result = 0; + + printf("Generating long INSERTs\n"); + for (int i = 0; i < N; i++) + { + printf("sql %d, rows=%d\n", i, x); + char *sqlstr = allocate_insert_string(i, x); + printf("INSERT: rwsplitter\n"); + printf("Trying INSERT, len=%d\n", x); + fflush(stdout); + result += execute_query(conn, sqlstr); + fflush(stdout); + x *= 16; + } + return result; +} + +int select_from_t1(MYSQL *conn, int N) +{ + int x = 16; + int result = 0; + int i; + char sq[100]; + + for (i = 0; i < N; i++) + { + sprintf(&sq[0], "select * from t1 where fl=%d;", i); + result += execute_select_query_and_check(conn, sq, x); + x = x * 16; + } + return result; +} + +// 0 - if it does not exist +// -1 - in case of error +int check_if_t1_exists(MYSQL *conn) +{ + MYSQL_RES *res; + MYSQL_ROW row; + unsigned long long int num_fields; + + int t1 = 0; + if (conn != NULL) + { + if (mysql_query(conn, "show tables;") != 0) + { + printf("Error: can't execute SQL-query: %s\n", mysql_error(conn)); + t1 = 0; + } + else + { + res = mysql_store_result(conn); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + t1 = - 1; + } + else + { + num_fields = mysql_num_fields(res); + if (mysql_num_rows(res) > 0) + { + while ((row = mysql_fetch_row(res)) != NULL) + { + if ( (row[0] != NULL ) && (strcmp(row[0], "t1") == 0 ) ) + { + t1 = 1; + } + } + } + } + mysql_free_result(res); + } + } + else + { + printf("FAILED: broken connection\n"); + t1 = -1; + } + return t1; +} + + diff --git a/maxscale-system-test/sql_t1.h b/maxscale-system-test/sql_t1.h new file mode 100644 index 000000000..512243f66 --- /dev/null +++ b/maxscale-system-test/sql_t1.h @@ -0,0 +1,77 @@ +#ifndef SQL_T1_H +#define SQL_T1_H + +#include "mariadb_func.h" +#include "testconnections.h" + +/** + * @brief execute_select_query_and_check Execute query and check that result contains expected number of rows + * @param conn MYSQL handler + * @param sql Query + * @param rows Expected number of rows + * @return 0 in case of success + */ +int execute_select_query_and_check(MYSQL *conn, char *sql, unsigned long long int rows); + +/** + * @brief create_t1 Create t1 table, fileds: (x1 int, fl int) + * @param conn MYSQL handler + * @return 0 in case of success + */ +int create_t1(MYSQL * conn); + + +/** + * @brief create_t1 Create t2 table, fileds: (x1 int, fl int) + * @param conn MYSQL handler + * @return 0 in case of success + */ +int create_t2(MYSQL * conn); + +/** + * @brief create_insert_string Create SQL query string to insert N rows into t1 + * fl is equal to given value and x1 is incrementing value (row index) + * @param sql pointer to buffer to put result + * @param N Number of rows to insert + * @param fl value to fill 'fl' field + * @return 0 + */ +int create_insert_string(char *sql, int N, int fl); + +/** + * @brief create_insert_string Create SQL query string to insert N rows into t1 + * fl is equal to given value and x1 is incrementing value (row index) + * (same as create_insert_string(), but allocates buffer for SQL string by itself) + * @param sql pointer to buffer to put result + * @param N Number of rows to insert + * @param fl value to fill 'fl' field + * @return pointer to insert SQL string + */ +char* allocate_insert_string(int fl, int N); + +/** + * @brief insert_into_t1 Insert N blocks of 16^i rows into t1 + * first block has fl=0, second - fl=1, ..., N-block fl=N-1 + * first block has 16 row, second - 256, ..., N-block 16^N rows + * @param conn MYSQL handler + * @param N Number of blocks to insert + * @return 0 in case of success + */ +int insert_into_t1(MYSQL *conn, int N); + +/** + * @brief select_from_t1 Check that t1 contains data as inserted by insert_into_t1() + * @param conn MYSQL handler + * @param N Number of blocks to insert + * @return 0 in case of success + */ +int select_from_t1(MYSQL *conn, int N); + +/** + * @brief check_if_t1_exists + * @param conn MYSQL handler + * @return 0 if content of t1 is ok + */ +int check_if_t1_exists(MYSQL *conn); + +#endif // SQL_T1_H diff --git a/maxscale-system-test/ssl-cert/ca-key.pem b/maxscale-system-test/ssl-cert/ca-key.pem new file mode 100644 index 000000000..a6643b9b1 --- /dev/null +++ b/maxscale-system-test/ssl-cert/ca-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuVDvGcbgfKB/MSNLmG7GZcqjSFjTXM+EFFInlpmm3DzLrTw0 +GBaRw3OQ05+YTZv0ANrv1v3MjjK/lgxzIGlDp4yXpDiblcITjVPLQ1IdhrNUaT/a +b4MJovZhQXQ6Qy7HYyyIF7YQslcZFtsuF6I6NE/jSl2pk+GoVZEjUfwEAgWm6vKE +XMyehSpD3O7WdoeyROrEfUXNoijKrWYGpXGvWGrMVhubNrGrP4uARiD6l0RH8ggs +Fc18vEPVeCH4mWAwVurPsYaNRX6KX5K/nrOnHVRryuHz4FEWkfjEmw2oiPD0cxGv +v52h5vRzsxZ4Gyy0eyfDAo+QV0CKTdLV2mKFzwIDAQABAoIBAQCpVkmGdnK/rzFK +x4PWgLMOZQKbo39QedepcgD23uTYrz02yaKYuFbuEPYwSs+za2SmO2maPUTkU386 +L5or7cSCoDgZOhkB2dxf8kf09l5ArjHeExkZeNYfcaAhXYG3zlEoUJLzh8zuCiTE ++/e8ZONm1fFODRReK3/U4NEzo2ROKylQDReOJVabYtfQlhZU4K45m5eVGrThSxaE +NZUnFWOBNZTXl/g0iyCi6cK5sP8kpLQHIyeipro0nWcMOz2hv8UWB/r4GeENysx5 +cZe7oIJXkhz0LeibrVHIh6UKU0r5URItFkYhWg0mMZNlqoP8EYueS+jY+DHE0NsE +4B/0uijBAoGBAOZtdq9vkfijsa5z5zppALdNnd45S6ZFVxxMv//1aui/xyN8IMbH +akQK1Fs0E5rPJNIss5IyoDvTrRlT2D6Z54f9zdDttpf90E5LMhXuvavRpNrLhG6B +Kdph3G0YG7pZiqZ9358tXN3+GbGCGdAUAUakndlfEsAjcJwGiSReRp5/AoGBAM3h +1kD2X5IEWNwMoFXW7xNFb43rdk+QDFuT7+FOVjC2BzGLsBccU0GhXsuGSyH6dqVQ +0gDGG03oHskqEPdsI5XzRCGAYNE18wyRjToM4Gty26eB3vVN9drk9l3QCzBZBM6r +x0Z6XVyJHP5vVuGs4Ss0DCbrEZcCjAFNa+wY2RCxAoGASenG7lNdgWIQtqLbAvN3 +lVTCVOwQBg+r95nu5I22c15A5HMGmHyS66yFnZPAziGv7/MCztyKZWZq2fr5HGuF +xyt0f5YgwfRagyK/uCVSlfCzbrNlkYTBBhnp+NyI2Gesf+dUedy1M0g0uam/2ezw +09YuA/HpM9SqZIH8L7xF7sUCgYBydZKa7R323MnWFILzFho2GY1KVlShKRFlvFpJ +ZTbPv/NB3UoZnxq+SwublN6iVk+t2r/VxE2bw2eSKPvjwlAKZoNDKM8qoJauLyFX +257Mvb9WYgJlWGFNv6skPqxpxaPBjoisKw1Ki8P9xEKGFoncbgQvgtJ1pOnXM4bD +bJEJEQKBgQDTD3JWEzzc2NCToPc0KbAxbCmenGVpnrcDTuKdtoWCIOtEXIunzYrd +Hul9YGMw4q5d8EGXM+lN1FNVHXM6KZ0WP2C+U2Knap/TmH3Luqx8ZJNSQtcNtfRK +rnP+DC+c80nNvPsIvgZbeNWGRUQABK13LUQak5Jno9MwMRJvVemVFA== +-----END RSA PRIVATE KEY----- diff --git a/maxscale-system-test/ssl-cert/ca.pem b/maxscale-system-test/ssl-cert/ca.pem new file mode 100644 index 000000000..27eec5ff8 --- /dev/null +++ b/maxscale-system-test/ssl-cert/ca.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIJAInnpU6AKeXBMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV +BAYTAkFBMQswCQYDVQQIDAJBQTELMAkGA1UEBwwCQUExCzAJBgNVBAoMAkFBMQsw +CQYDVQQLDAJBQTELMAkGA1UEAwwCQUExETAPBgkqhkiG9w0BCQEWAkFBMB4XDTE1 +MDYwMzEyNTk1MFoXDTI1MDQxMTEyNTk1MFowYTELMAkGA1UEBhMCQUExCzAJBgNV +BAgMAkFBMQswCQYDVQQHDAJBQTELMAkGA1UECgwCQUExCzAJBgNVBAsMAkFBMQsw +CQYDVQQDDAJBQTERMA8GCSqGSIb3DQEJARYCQUEwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQC5UO8ZxuB8oH8xI0uYbsZlyqNIWNNcz4QUUieWmabcPMut +PDQYFpHDc5DTn5hNm/QA2u/W/cyOMr+WDHMgaUOnjJekOJuVwhONU8tDUh2Gs1Rp +P9pvgwmi9mFBdDpDLsdjLIgXthCyVxkW2y4Xojo0T+NKXamT4ahVkSNR/AQCBabq +8oRczJ6FKkPc7tZ2h7JE6sR9Rc2iKMqtZgalca9YasxWG5s2sas/i4BGIPqXREfy +CCwVzXy8Q9V4IfiZYDBW6s+xho1Ffopfkr+es6cdVGvK4fPgURaR+MSbDaiI8PRz +Ea+/naHm9HOzFngbLLR7J8MCj5BXQIpN0tXaYoXPAgMBAAGjUDBOMB0GA1UdDgQW +BBSdb/8ndJ7e31ZawqNYFbthiIyNqTAfBgNVHSMEGDAWgBSdb/8ndJ7e31ZawqNY +FbthiIyNqTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBIHMX8Af9f +W0tlsV6GCNnIstYyAZk6VP4Z+R4jD/Ie8NmGtuKrrnPaYNPeJK2jGbB6kMJBoi2A +hc5LoQbWYKGmKEzZw5rQ0BjIlXe79XIoPugIXnU+DzENXKx+c9x2BcMCYsAQ/n4J +e0Yg1ngfTJsvb1RoHZpQJFyn0hmqrpL/Ru3s1llcKff5wLw+vzGAJsORPyNx4o52 +dmql30wuqarmo7IhsDKk2c3KlUZd7BRXGJWy7TFvKtiHTbBH1AbVTQnLs2/+StOC +1rJM0jNFVgppI2/H//ASop5+YdSbrDmQBkSQIs0KA2/dsuA/jtwY/dY9JCsJctwc +jPYjkyWr6sZh +-----END CERTIFICATE----- diff --git a/maxscale-system-test/ssl-cert/client-cert.pem b/maxscale-system-test/ssl-cert/client-cert.pem new file mode 100644 index 000000000..de0f4d936 --- /dev/null +++ b/maxscale-system-test/ssl-cert/client-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CAQEwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCQUExCzAJBgNV +BAgMAkFBMQswCQYDVQQHDAJBQTELMAkGA1UECgwCQUExCzAJBgNVBAsMAkFBMQsw +CQYDVQQDDAJBQTERMA8GCSqGSIb3DQEJARYCQUEwHhcNMTUwNjAzMTMwMDE5WhcN +MjUwNDExMTMwMDE5WjBhMQswCQYDVQQGEwJDQzELMAkGA1UECAwCQ0MxCzAJBgNV +BAcMAkNDMQswCQYDVQQKDAJDQzELMAkGA1UECwwCQ0MxCzAJBgNVBAMMAkNDMREw +DwYJKoZIhvcNAQkBFgJDQzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ALyZ9a/PvFO+534qrAByvdrG261UosTrAO14wf9YgMcptqUH8vcJFXSQJx3HWQ/F +YB/FwJ3idwOUt084FkA5SEaTHSQrp8HtHL7/qRaWqZ2icYwymXmj9vHU0UV0JcWe +RWyAAiXLhuQmLHDW4w6Y5gpR1wzmvHV961EI1PkF+zoz4CzNm9bdOJ8ffi/7oGK5 +9OwlVytnwYG+tUFhVEaIoBypNnzCcSlIOM0Y5P6y2drfvybAuHcQlVpoUy9AVMQa +2Wamv/wUHiJ1XJaCTJ4QbNvsL6f+hXRLBReGZDwMXk45e0deFqjsd2UWG1zHSY0F +tn7Vc8Sn6C0f/+wB/tg1f5UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAQP52x5fE +Qtl1uOVob0TCrNqe4vQNpw3iCtTSaWfXGP2Ntuh2egfNgnMF9Wd+HavpbFCn538h +8Qz5cG8VtDMkU74Y0aWuzxfA20mTi8jui9Z6dLCLkE6U83wp+LZjmGx5iD9kKMOS +TDQVylmuhLtz0g0MBCTJtsYM7XZMWFiPlHG5MtwTNQtDGpIz6/1hLXxqeO1XLCDC +xf/Ra5FztLXadWJYwg5eoKPsa1qTrGhM4MRbYJ1DPc73Jlk2piNp9iuw/Xp9kspb +fwqAIBUynHc/FtcmkzrOQW0IufV53Mk+G9aOVj2A6lz6VEQ8KcGwWapd1pMT2vAm +vIZTN/nN0NxDlA== +-----END CERTIFICATE----- diff --git a/maxscale-system-test/ssl-cert/client-key.pem b/maxscale-system-test/ssl-cert/client-key.pem new file mode 100644 index 000000000..e36328f2b --- /dev/null +++ b/maxscale-system-test/ssl-cert/client-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvJn1r8+8U77nfiqsAHK92sbbrVSixOsA7XjB/1iAxym2pQfy +9wkVdJAnHcdZD8VgH8XAneJ3A5S3TzgWQDlIRpMdJCunwe0cvv+pFpapnaJxjDKZ +eaP28dTRRXQlxZ5FbIACJcuG5CYscNbjDpjmClHXDOa8dX3rUQjU+QX7OjPgLM2b +1t04nx9+L/ugYrn07CVXK2fBgb61QWFURoigHKk2fMJxKUg4zRjk/rLZ2t+/JsC4 +dxCVWmhTL0BUxBrZZqa//BQeInVcloJMnhBs2+wvp/6FdEsFF4ZkPAxeTjl7R14W +qOx3ZRYbXMdJjQW2ftVzxKfoLR//7AH+2DV/lQIDAQABAoIBAFEtM0Jo93ZURO9Z +Do9i07TGwubn9ucq/0s3Hsj5HvSaT7LYqQsLLeDt6ug2VW8kVStXwoAyaWdxKzU9 +vtYF0cYOrAxVfe9KT4UmJXFDh94BfT4KyWRB3phYMXrjcx6L8PWyYzpBT94DUD2B +QFmYFet7LkSvS46GlDJcys4kdO2niT5icTxIiG/l5JLtloY5VYcz++o0DxKTQhlx +3oMFVmX08eg3hStndwiNDEX8aGaDFKVAF4oO2BkMI9uGeKsvxWsRSluY2ruuMQWS +cZPRSQcyiqvPnD2JDez5hhBIzvVAcP8BHi8FKm6nn2qZ69PnS943IYsn3LI704jC +TZIaRkECgYEA2+7jEyaLmxspaPlHQJWVj3LD56BUHmTtO+aZjht+QTqPnlzbm2sN +K4tIEjq0iKioyDhs06oIG1xHrFZq5hZcE/OhEittCorK8FoQ6Gy27j48UwxUhg1w +W7GUe+2gJ84RqwOvNMkV8KTcdXFGwRZ/627a/khaueU5WQjmZmuJE/MCgYEA24e4 +cOLoTB9+Pxk1O3cJItG5sXvUiHq+/YXe71zPxEfJg4ft+UrSVdxHaeYJzZyxRdkV +oQrVWBZ73q6hQ9PbqXif1u/XzwN5OkAv1o8gmiDZX761+g4VqC6Nr+CC3z+wZYrQ +vbdwpoQ8bLAsQS+iHFQNoOG3QYB0UlXEtEOnaFcCgYEApYjdErntU9YSyfbt4Td1 +NV3tA7aR+bp/MJm1M9ePpKreFBTSGd1uMTsJCOd0oHNJbkHB72JH7cf8DkOQ/BMS +RNYXgqR60vd6HYO8vCOvYAwpvVxHdgpBalJzbv+AjtU0SSJhyfob8F81q5OR7Jzx +tRNf8wokd0yHaHMhby87Im8CgYEAzhMJQG7AQTUO/YxcPAXxvY3CEql7SOJwhuwz +6d3l6wq4T5A6A9oIuDCfmPkoGqsMfdTaSOIygw4YGWE0YMdvXBavdMSQgPNxyQyX +24FfyU8svoP3r1OpYMbSmlbWg1r161vztyOLy8Q2NJDr+gUM2CrO4EcCD6rVhrSc +gdtAOuECgYEAu2AYuFFIiYbpuNKmqJS++wbYsxGPLFI3yf8bgrDbfbIiGmsA9iWR +QP6HOywUiv9AZkVktYFFUL7gvJqSKq4mopk3oOFHi4DTmQrcxQRZXsxoaPr6wGL7 +kT8+JoxRXAw+6sV8aSRlAmqxXSf8EyJFkZ2rdkVZ+I2rQf5AvCSHmcg= +-----END RSA PRIVATE KEY----- diff --git a/maxscale-system-test/ssl-cert/client-req.pem b/maxscale-system-test/ssl-cert/client-req.pem new file mode 100644 index 000000000..fa0e952a0 --- /dev/null +++ b/maxscale-system-test/ssl-cert/client-req.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCQ0MxCzAJBgNVBAgMAkNDMQswCQYDVQQH +DAJDQzELMAkGA1UECgwCQ0MxCzAJBgNVBAsMAkNDMQswCQYDVQQDDAJDQzERMA8G +CSqGSIb3DQEJARYCQ0MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8 +mfWvz7xTvud+KqwAcr3axtutVKLE6wDteMH/WIDHKbalB/L3CRV0kCcdx1kPxWAf +xcCd4ncDlLdPOBZAOUhGkx0kK6fB7Ry+/6kWlqmdonGMMpl5o/bx1NFFdCXFnkVs +gAIly4bkJixw1uMOmOYKUdcM5rx1fetRCNT5Bfs6M+AszZvW3TifH34v+6BiufTs +JVcrZ8GBvrVBYVRGiKAcqTZ8wnEpSDjNGOT+stna378mwLh3EJVaaFMvQFTEGtlm +pr/8FB4idVyWgkyeEGzb7C+n/oV0SwUXhmQ8DF5OOXtHXhao7HdlFhtcx0mNBbZ+ +1XPEp+gtH//sAf7YNX+VAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAfpGQrjUW +vSNQp2YDEz1VdfFtgQPinEjib3KCKyG6QP7ZEkCrVR54fn6J1CkBVaDKMu1L4ohz +vcTD4fyNw7p60hWMaDsgut07eZrO3T4lfRFpdjEKFk9QAIpX/wFHPOreBXDv2/xV +VxIFV5vgudC4tl+riEwnmRSxXQlEXLODtC+OD2qwNI5GaIgOniBti66FVFFi/yYs +LAz/PgzFlHkuYBveDs+t+j3mGisMADM6b2lqVQdXFTa0CTAIq065xF27Ivn9Gqy3 +vJUUVtw8b4ouGLZlBKenPczlqlKIwpUqg9q7BelEIbBnZX1MDM7qPNd0YCz5mvap +/JPFIZCyVy2IFA== +-----END CERTIFICATE REQUEST----- diff --git a/maxscale-system-test/ssl-cert/server-cert.pem b/maxscale-system-test/ssl-cert/server-cert.pem new file mode 100644 index 000000000..d01db36ad --- /dev/null +++ b/maxscale-system-test/ssl-cert/server-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNjCCAh4CAQEwDQYJKoZIhvcNAQELBQAwYTELMAkGA1UEBhMCQUExCzAJBgNV +BAgMAkFBMQswCQYDVQQHDAJBQTELMAkGA1UECgwCQUExCzAJBgNVBAsMAkFBMQsw +CQYDVQQDDAJBQTERMA8GCSqGSIb3DQEJARYCQUEwHhcNMTUwNjAzMTMwMDA1WhcN +MjUwNDExMTMwMDA1WjBhMQswCQYDVQQGEwJCQjELMAkGA1UECAwCQkIxCzAJBgNV +BAcMAkJCMQswCQYDVQQKDAJCQjELMAkGA1UECwwCQkIxCzAJBgNVBAMMAkJCMREw +DwYJKoZIhvcNAQkBFgJCQjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANXIh96YvWjlAVfOyz42p0gA/v1Qi8b0AvjckTPhwKAsplzYW+P+jbumr36v2Jhe +Dwmv6lwHS9MWvWj13yzzH9LjJAkiu/erVB8ESExjVGN+lCMlfSdfMXxfucfJRFaf +1P8vo+8b9cgQRzVsXgRURQQxG0dF/s5wjeqwVfL25WFS0e1cIy3Vm0zF2duLIcu9 +KPtq56wK2cmMKsi2UOZZ6eX64lCBqOIpPaqfhs+8Epd66Z3qzCSmg/Qo4v6de0H1 +Z1Uu97FXC/QB/fXLmZHFF/uzII5DVQdM7Hhd2mbYw7qi+bvGfL5h3SQ9ClUwF+Ru +g5Bzr1Ho+gN3AqL2BwENa8UCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEATGc19U6i +9l0CxogxNgdZzOxDCD23kFWkKZmLQK4xxk7rM6GUsEMP1DwysxS2P0dN33Fo2yLC +NKgFkj/MpptWXApbqFjDGcagfPowEjvdeUvUGFpI9dMVnJN6RJpgHMLESyZIL7SH +K1uetvpTi0RWhlB6RlHM+OJ6kNMGFe9Hs1T3B1/O4q4JmQR3bzczjEfTY9yYWKZL +V2GKSq4eyztQVOKnQ+GueAG6DM5tueIDt4LZNd369Zl1w+jIx5/0nMvs9tAwHnCP +IjZdWmmU18vXc8Koo2fZYhDPvhYKrumquLXSxJtLVmh7OnEztEN5I4STbYMvPuc5 +9VekdTY6q7ZGhw== +-----END CERTIFICATE----- diff --git a/maxscale-system-test/ssl-cert/server-key.pem b/maxscale-system-test/ssl-cert/server-key.pem new file mode 100644 index 000000000..56a3fe5a0 --- /dev/null +++ b/maxscale-system-test/ssl-cert/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA1ciH3pi9aOUBV87LPjanSAD+/VCLxvQC+NyRM+HAoCymXNhb +4/6Nu6avfq/YmF4PCa/qXAdL0xa9aPXfLPMf0uMkCSK796tUHwRITGNUY36UIyV9 +J18xfF+5x8lEVp/U/y+j7xv1yBBHNWxeBFRFBDEbR0X+znCN6rBV8vblYVLR7Vwj +LdWbTMXZ24shy70o+2rnrArZyYwqyLZQ5lnp5friUIGo4ik9qp+Gz7wSl3rpnerM +JKaD9Cji/p17QfVnVS73sVcL9AH99cuZkcUX+7MgjkNVB0zseF3aZtjDuqL5u8Z8 +vmHdJD0KVTAX5G6DkHOvUej6A3cCovYHAQ1rxQIDAQABAoIBAQCaeE0EYCTsOBxi +T/+3MlJoyp90ddCprwK+wtEAReyjT9/dUlADS5kpr+U6iuDcJ4qCG2QhnvwAyHuP +7aiWmA20mX8eQsTCzj74MmXzHAqL4Zg0VRA7dKMTFS/t1dXVKaQDx26ShQdwyaJK +TcUZJ7K+tqTZORBGJjnFDCjuZ8uSJ6yXpl4UX22chjHLXkggIJxTGDDqoeqXpXUy +o/qK4m63au++MV7sKEiCmXJH9OEu5367+/YMpB4OfjPflg1M5GEiW4yv80b80NMl +aKJbMD7NIxA8z+SxkX8sHugDCQnMEQrAm/g3VBHs4FmvSiYno9Dhosae8QWppATc +47ziNeV5AoGBAPwP55GIce44G3Cdmg33saqJjrk7KSxgAmCnoc7Ql91OmBfzjc9F +FDQaDqn4mOkEgAdzXHTt04B43QkpybyfsJu1F+IRiB9gG/Y0bOFwIITw5XLVgIil +PXj5blECpJJ4YJB0lSWCh8r7IaJPVUgxt+ooKjngSV2I0Do9jB5RZJ5zAoGBANkf +iMG5TBhF33m+Y9c2h3E730CDs3hnClS9p98sVuGJvihvC83G3V9l2OC5pXFpU+x5 +Gk3egm04sR2lgKt9vzX1i8ONO7CJhRanPPcEGcoO04gH+GLesPsrTZSPi1LCgkvG +BspVUB9THye2xnzH1ybjQXeY1JOsphPI2Z8hlUbnAoGBANoskg3AAo1ldyrU34Fz +gg7Qnf7qnErSl3w93Kp8ltZxrugcYgSkEEbAvyyUBxa6VG8ehUqYiFdNRlUmHH3t +4xoDd7av4hj2QX2sBwpi2WL4eEIbVvPvwvOxQrFQDk7RbxPaNTaQcyVYldY9wN2K +W1yMg3AxGI+jWkju+RdM8EDNAoGAU8CWQCP59mL9kTifyqR+Gm+97mTfFdKpaYa+ ++pdf7B/1+iLbqsTLH8Fpp0X1S4oVlWIhg2gRZ5A2wfjVaVFYMLhG8WfJXo+ths97 +9MzgJdESR6bLaVf9bOWHR4cXdxUQran1dJb0ESE/I1KdvgRYOefvmPKbDVvLtFYF +nByYOIECgYARyEOPI7tVkZeUh1+s0BGUyYRczEYnDV5IS4iLIeKn9GCvmB2J9zIc +gO2QBS5f/6Brd3ztwlm5xiLCXSwx5qfQkf3UYFrehkR4QAT1fW67p6KImQZgAyVj +quJfVrFvtulQS9jwHuU4qPn0yST+DXHkqvguDzo4YDmXutVH5m4+rQ== +-----END RSA PRIVATE KEY----- diff --git a/maxscale-system-test/ssl-cert/server-req.pem b/maxscale-system-test/ssl-cert/server-req.pem new file mode 100644 index 000000000..035a57d6b --- /dev/null +++ b/maxscale-system-test/ssl-cert/server-req.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICpjCCAY4CAQAwYTELMAkGA1UEBhMCQkIxCzAJBgNVBAgMAkJCMQswCQYDVQQH +DAJCQjELMAkGA1UECgwCQkIxCzAJBgNVBAsMAkJCMQswCQYDVQQDDAJCQjERMA8G +CSqGSIb3DQEJARYCQkIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDV +yIfemL1o5QFXzss+NqdIAP79UIvG9AL43JEz4cCgLKZc2Fvj/o27pq9+r9iYXg8J +r+pcB0vTFr1o9d8s8x/S4yQJIrv3q1QfBEhMY1RjfpQjJX0nXzF8X7nHyURWn9T/ +L6PvG/XIEEc1bF4EVEUEMRtHRf7OcI3qsFXy9uVhUtHtXCMt1ZtMxdnbiyHLvSj7 +auesCtnJjCrItlDmWenl+uJQgajiKT2qn4bPvBKXeumd6swkpoP0KOL+nXtB9WdV +LvexVwv0Af31y5mRxRf7syCOQ1UHTOx4Xdpm2MO6ovm7xny+Yd0kPQpVMBfkboOQ +c69R6PoDdwKi9gcBDWvFAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAw55rZga+ +iAry/VFUBojLevgZYMOH2xHbgJjcEGbbBh6ONjC2aTM9pjrWxBLpFvD95KyHxhp8 +1GbKDP9B9NA5oN0kQTKYBl7Qnz5gumqacNE6N++jcMlDK9LYxlkdFPcLQ+p99wYi +6qEXNSXAEq8qaRzU4mawjZt9zEwCezX647p9fp939+cJt6GX7ZGCBTPP0TMbUUu/ +aCcm4hiU8/OZ8PgyKgxUhuKTvPflEfwoxjuAMKm9A3AgWN850mehqcaDG62H1mpM +5wqGKCp6ihn0mt2Jqgx0S/VzZTZaOBXy5P1iicsq36J021ppENwoStlRop+h32M4 +4r5lJiyYVnW+Eg== +-----END CERTIFICATE REQUEST----- diff --git a/maxscale-system-test/ssl.cnf b/maxscale-system-test/ssl.cnf new file mode 100644 index 000000000..0cf30edcb --- /dev/null +++ b/maxscale-system-test/ssl.cnf @@ -0,0 +1,4 @@ +[mysqld] +ssl_cert=/etc/ssl-cert/server-cert.pem +ssl_key=/etc/ssl-cert/server-key.pem +ssl_ca=/etc/ssl-cert/ca.pem diff --git a/maxscale-system-test/stale_slaves.cpp b/maxscale-system-test/stale_slaves.cpp new file mode 100644 index 000000000..fc91649e7 --- /dev/null +++ b/maxscale-system-test/stale_slaves.cpp @@ -0,0 +1,106 @@ +/** + * @file stale_slaves.cpp Testing slaves who have lost their master and how MaxScale works with them + * + * When the master server is blocked and slaves lose their master, they should + * still be available for read queries. When a slave with no master fails, it should not + * be assigned slave status again. Once the master comes back, all slaves should get slave + * status if replication is running. + */ + + +#include +#include "testconnections.h" + +int main(int argc, char **argv) +{ + TestConnections *test = new TestConnections(argc, argv); + + char server_id[test->repl->N][1024]; + + test->repl->connect(); + /** Get server_id for each node */ + for (int i = 0; i < test->repl->N; i++) + { + sprintf(server_id[i], "%d", test->repl->get_server_id(i)); + } + + test->tprintf("Block the master and try a read query\n"); + test->repl->block_node(0); + sleep(15); + test->connect_readconn_slave(); + char first_slave[1024]; + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", first_slave); + + int found = -1; + + for (int i = 0; i < test->repl->N; i++) + { + if (strcmp(server_id[i], first_slave) == 0) + { + found = i; + break; + } + } + + test->add_result(found < 0, "No server with ID '%s' found.", first_slave); + + test->tprintf("Blocking node %d\n", found + 1); + test->repl->block_node(found); + sleep(15); + + test->tprintf("Blocked the slave that replied to us, expecting a different slave\n"); + test->connect_readconn_slave(); + char second_slave[1024]; + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", second_slave); + test->add_result(strcmp(first_slave, second_slave) == 0, + "Server IDs match when they shouldn't: %s - %s", + first_slave, second_slave); + + test->tprintf("Unblocking the slave that replied\n"); + test->repl->unblock_node(found); + sleep(15); + + test->tprintf("Unblocked the slave, still expecting a different slave\n"); + test->connect_readconn_slave(); + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", second_slave); + test->add_result(strcmp(first_slave, second_slave) == 0, + "Server IDs match when they shouldn't: %s - %s", + first_slave, second_slave); + + test->tprintf("Unblocking all nodes\n"); + test->repl->unblock_all_nodes(); + sleep(15); + + test->tprintf("Unblocked all nodes, expecting the server ID of the first slave server\n"); + test->connect_readconn_slave(); + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", second_slave); + test->add_result(strcmp(first_slave, second_slave) != 0, + "Server IDs don't match when they should: %s - %s", + first_slave, second_slave); + + test->tprintf("Stopping replication on node %d\n", found + 1); + execute_query(test->repl->nodes[found], "stop slave"); + sleep(15); + + test->tprintf("Stopped replication, expecting a different slave\n"); + test->connect_readconn_slave(); + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", second_slave); + test->add_result(strcmp(first_slave, second_slave) == 0, + "Server IDs match when they shouldn't: %s - %s", + first_slave, second_slave); + + test->tprintf("Starting replication on node %d\n", found + 1); + execute_query(test->repl->nodes[found], "start slave"); + sleep(15); + + test->tprintf("Started replication, expecting the server ID of the first slave server\n"); + test->connect_readconn_slave(); + find_field(test->conn_slave, "SELECT @@server_id", "@@server_id", second_slave); + test->add_result(strcmp(first_slave, second_slave) != 0, + "Server IDs don't match when they should: %s - %s", + first_slave, second_slave); + + int rval = test->global_result; + delete test; + return rval; +} diff --git a/maxscale-system-test/start_without_root.sh b/maxscale-system-test/start_without_root.sh new file mode 100755 index 000000000..f9c3a1a0a --- /dev/null +++ b/maxscale-system-test/start_without_root.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +rp=`realpath $0` +export test_dir=`dirname $rp` +export test_name=`basename $rp` + +$test_dir/non_native_setup $test_name + +errmsg="MaxScale doesn't have write permission to MAXSCALE_HOME. Exiting" +ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "$maxscale_access_sudo service maxscale stop" & +sleep 5 + +conf_dir=$(dirname "${maxscale_cnf}") +ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "maxscale -d -c $conf_dir" 2>&1 | grep "$errmsg" +res=$? +ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "maxscale -d -c $conf_dir" 2>&1 | grep "$errmsg" +res1=$? + +if [[ $res != 0 || $res1 != 0 ]] ; then + echo "FAILED: no proper error message" + ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "maxscale -d -c $conf_dir" + ssh -i $maxscale_sshkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $maxscale_access_user@$maxscale_IP "maxscale -c $cond_dir" + $test_dir/copy_logs.sh start_without_root + exit 1 +fi +$test_dir/copy_logs.sh start_without_root +exit 0 diff --git a/maxscale-system-test/sysbench_commands.h b/maxscale-system-test/sysbench_commands.h new file mode 100644 index 000000000..692d0ff7b --- /dev/null +++ b/maxscale-system-test/sysbench_commands.h @@ -0,0 +1,71 @@ +#ifndef SYSBENCH_COMMANDS_H +#define SYSBENCH_COMMANDS_H + +/*const char * sysbench_prepare = + "sysbench --test=oltp \ + --oltp-table-size=1000000 --mysql-db=test --mysql-user=skysql --mysql-password=skysql \ + --mysql-port=4006 --mysql-host=%s prepare"; + + + +const char * sysbench_command = + "sysbench --test=oltp \ + --mysql-host=%s --mysql-port=%d --mysql-user=skysql --mysql-password=skysql \ + --mysql-db=test --mysql-table-engine=innodb \ + --num-threads=32 --oltp-table-size=1000000 --oltp-read-only=off \ + --oltp-dist-type=uniform --oltp-skip-trx=off --init-rng=on --oltp-test-mode=complex \ + --max-requests=0 --max-time=600 run";*/ + +const char * sysbench_prepare = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --oltp-table-size=1000000 --mysql-db=test --mysql-user=skysql --mysql-password=skysql \ + --mysql-port=4006 --mysql-host=%s --oltp-tables-count=4 prepare"; + +const char * sysbench_command = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --mysql-host=%s --mysql-port=%d --mysql-user=skysql --mysql-password=skysql \ + --mysql-db=test --mysql-table-engine=innodb --mysql-ignore-duplicates=on \ + --num-threads=32 --oltp-table-size=1000000 --oltp-tables-count=2 --oltp-read-only=%s \ + --oltp-dist-type=uniform --oltp-skip-trx=off --init-rng=on --oltp-test-mode=complex \ + --max-requests=0 --report-interval=5 --max-time=100 run"; + + +const char * sysbench_prepare1 = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --oltp-table-size=1000 --mysql-db=test --mysql-user=skysql --mysql-password=skysql \ + --mysql-port=4006 --mysql-host=%s --oltp-tables-count=1 prepare"; + +const char * sysbench_command1 = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --mysql-host=%s --mysql-port=%d --mysql-user=skysql --mysql-password=skysql \ + --mysql-db=test --mysql-table-engine=innodb --mysql-ignore-duplicates=on \ + --num-threads=32 --oltp-table-size=1000 --oltp-tables-count=1 --oltp-read-only=%s \ + --oltp-dist-type=uniform --oltp-skip-trx=off --init-rng=on --oltp-test-mode=complex \ + --max-requests=0 --report-interval=5 --max-time=100 run"; + + +const char * sysbench_command_long = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --mysql-host=%s --mysql-port=%d --mysql-user=skysql --mysql-password=skysql \ + --mysql-db=test --mysql-table-engine=innodb --mysql-ignore-duplicates=on \ + --num-threads=32 --oltp-table-size=1000000 --oltp-tables-count=2 --oltp-read-only=%s \ + --oltp-dist-type=uniform --oltp-skip-trx=off --init-rng=on --oltp-test-mode=complex \ + --max-requests=0 --report-interval=5 --max-time=2592000 run"; + + +const char * sysbench_prepare_short = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --oltp-table-size=10000 --mysql-db=test --mysql-user=skysql --mysql-password=skysql \ + --mysql-port=4006 --mysql-host=%s --oltp-tables-count=4 prepare"; + +const char * sysbench_command_short = + "%s/sysbench --test=%s/tests/db/oltp.lua \ + --mysql-host=%s --mysql-port=%d --mysql-user=skysql --mysql-password=skysql \ + --mysql-db=test --mysql-table-engine=innodb --mysql-ignore-duplicates=on \ + --num-threads=32 --oltp-table-size=10000 --oltp-tables-count=2 --oltp-read-only=%s \ + --oltp-dist-type=uniform --oltp-skip-trx=off --init-rng=on --oltp-test-mode=complex \ + --max-requests=0 --report-interval=5 --max-time=300 run"; + + + +#endif // SYSBENCH_COMMANDS_H diff --git a/maxscale-system-test/sysbench_example.cpp b/maxscale-system-test/sysbench_example.cpp new file mode 100644 index 000000000..a15062222 --- /dev/null +++ b/maxscale-system-test/sysbench_example.cpp @@ -0,0 +1,69 @@ +/** + * @file sysbanch_example.cpp Run 'sysbench' + * + * - start sysbanch test + * - repeat for all services + * - DROP sysbanch tables + * - check if Maxscale is alive + */ + + +#include "testconnections.h" +#include "sysbench_commands.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + char sys1[4096]; + + Test->ssh_maxscale(false, "maxscale --version-full"); + fflush(stdout); + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + + sprintf(&sys1[0], sysbench_prepare_short, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP); + + Test->tprintf("Preparing sysbench tables\n%s\n", sys1); + Test->set_timeout(10000); + Test->add_result(system(sys1), "Error executing sysbench prepare\n"); + + Test->stop_timeout(); + + sprintf(&sys1[0], sysbench_command_short, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP, + Test->rwsplit_port, "off"); + Test->set_log_copy_interval(300); + Test->tprintf("Executing sysbench \n%s\n", sys1); + if (system(sys1) != 0) + { + Test->tprintf("Error executing sysbench test\n"); + } + + Test->connect_maxscale(); + + printf("Dropping sysbanch tables!\n"); + fflush(stdout); + + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest1"); + if (!Test->smoke) + { + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest2"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest3"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest4"); + } + + //global_result += execute_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest"); + + printf("closing connections to MaxScale!\n"); + fflush(stdout); + + Test->close_maxscale_connections(); + + Test->tprintf("Checking if MaxScale is still alive!\n"); + fflush(stdout); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + diff --git a/maxscale-system-test/sysbench_kill_slave.cpp b/maxscale-system-test/sysbench_kill_slave.cpp new file mode 100644 index 000000000..a3dad48f4 --- /dev/null +++ b/maxscale-system-test/sysbench_kill_slave.cpp @@ -0,0 +1,145 @@ +/** + * @file sysbanch_kill_slave.cpp Kill slave during sysbanch test + * + * - start sysbanch test + * - wait 20 seconds and kill active slave + * - repeat for all services + * - DROP sysbanch tables + * - check if Maxscale is alive + */ + +#include "testconnections.h" +#include "sysbench_commands.h" + +TestConnections * Test ; + +pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; +int exit_flag = 0; +int start_flag = 0; +unsigned int old_slave; +void *kill_vm_thread( void *ptr ); + +int main(int argc, char *argv[]) +{ + Test = new TestConnections(argc, argv); + pthread_t kill_vm_thread1; + int check_iret; + char sys1[4096]; + int port[3]; + + port[0] = Test->rwsplit_port; + port[1] = Test->readconn_master_port; + port[2] = Test->readconn_slave_port; + + Test->tprintf("Connecting to RWSplit %s\n", Test->maxscale_IP); + + if (Test->smoke) + { + sprintf(&sys1[0], sysbench_prepare1, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP); + } + else + { + sprintf(&sys1[0], sysbench_prepare, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP); + } + + Test->tprintf("Preparing sysbench tables\n%s\n", sys1); + Test->set_timeout(5000); + Test->add_result(system(sys1), "Error executing sysbench prepare\n"); + + char *readonly; + char *ro_on = (char *) "on"; + char *ro_off = (char *) "off"; + Test->set_timeout(2000); + for (int k = 0; k < 3; k++) + { + Test->tprintf("Trying test with port %d\n", port[k]); + check_iret = pthread_create( &kill_vm_thread1, NULL, kill_vm_thread, NULL); + + if (port[k] == Test->readconn_slave_port ) + { + readonly = ro_on; + } + else + { + readonly = ro_off; + } + if (Test->smoke) + { + sprintf(&sys1[0], sysbench_command1, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP, port[k], + readonly); + } + else + { + sprintf(&sys1[0], sysbench_command, Test->sysbench_dir, Test->sysbench_dir, Test->maxscale_IP, port[k], + readonly); + } + Test->tprintf("Executing sysbench tables\n%s\n", sys1); + if (system(sys1) != 0) + { + Test->tprintf("Error executing sysbench test\n"); + } + + Test->tprintf("Starting VM back\n"); + if ((old_slave >= 1) && (old_slave <= Test->repl->N)) + { + Test->repl->unblock_node(old_slave); + } + sleep(60); + Test->tprintf("Restarting replication\n"); + Test->repl->start_replication(); + sleep(30); + } + + Test->connect_maxscale(); + + printf("Dropping sysbanch tables!\n"); + fflush(stdout); + + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest1"); + if (!Test->smoke) + { + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest2"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest3"); + Test->try_query(Test->conn_rwsplit, (char *) "DROP TABLE sbtest4"); + } + + printf("closing connections to MaxScale!\n"); + fflush(stdout); + + Test->close_maxscale_connections(); + + Test->tprintf("Checxking if MaxScale is still alive!\n"); + fflush(stdout); + Test->check_maxscale_alive(); + + int rval = Test->global_result; + delete Test; + return rval; +} + + +void *kill_vm_thread( void *ptr ) +{ + //int global_result = 0; + sleep(20); + printf("Checking current slave\n"); + fflush(stdout); + old_slave = Test->find_connected_slave1(); + + if ((old_slave >= 1) && (old_slave <= Test->repl->N)) + { + printf("Active slave is %d\n", old_slave); + fflush(stdout); + } + else + { + printf("Active slave is not found, killing slave1\n"); + fflush(stdout); + old_slave = 1; + } + char sys1[4096]; + printf("Killing VM %s\n", Test->repl->IP[old_slave]); + fflush(stdout); + Test->repl->block_node(old_slave); + return NULL; +} diff --git a/maxscale-system-test/t.cpp b/maxscale-system-test/t.cpp new file mode 100644 index 000000000..b19ec637d --- /dev/null +++ b/maxscale-system-test/t.cpp @@ -0,0 +1,28 @@ +#include +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int main(int argc, char *argv[]) +{ + char time_str[100]; + time_t curr_time = time(NULL); + time_t end_time = curr_time + 120; + + printf("%lu %lu\n", curr_time, end_time); + + // current time and 'current time + 2 minutes': block delete quries for 2 minutes + struct tm * timeinfo1 = localtime (&curr_time); + + + printf("%02d:%02d:%02d\n", timeinfo1->tm_hour, timeinfo1->tm_min, timeinfo1->tm_sec); + struct tm * timeinfo2 = localtime (&end_time); + printf("%02d:%02d:%02d\n", timeinfo2->tm_hour, timeinfo2->tm_min, timeinfo2->tm_sec); + + sprintf(time_str, "%02d:%02d:%02d-%02d:%02d:%02d", timeinfo1->tm_hour, timeinfo1->tm_min, timeinfo1->tm_sec, + timeinfo2->tm_hour, timeinfo2->tm_min, timeinfo2->tm_sec); + + printf("%s\n", time_str); + +} diff --git a/maxscale-system-test/templates.h.in b/maxscale-system-test/templates.h.in new file mode 100644 index 000000000..261b0052e --- /dev/null +++ b/maxscale-system-test/templates.h.in @@ -0,0 +1,17 @@ +#ifndef TEMPLATES_H +#define TEMPLATES_H + +static struct +{ + const char* test_name; + const char* test_template; +} cnf_templates[] = { +@CNF_TEMPLATES@ {NULL, NULL}}; + +/** The default template to use */ +static const char * default_template = "replication"; + +/** This is the working directory for all tests */ +static const char *test_dir = "@CMAKE_SOURCE_DIR@"; + +#endif diff --git a/maxscale-system-test/temporal_tables.cpp b/maxscale-system-test/temporal_tables.cpp new file mode 100644 index 000000000..f0773adf9 --- /dev/null +++ b/maxscale-system-test/temporal_tables.cpp @@ -0,0 +1,99 @@ +/** + * @file temporal_tables.cpp Check temporal tables commands functionality (relates to bug 430) + * - create t1 table and put some data into it + * - create tempral table t1 + * - insert different data into t1 + * - check that SELECT FROM t1 gives data from tempral table + * - create other connections using all Maxscale services and check that SELECT via these connections gives data from main t1, not temporal + * - dropping tempral t1 + * - check that data from main t1 is not affected + */ + + +#include +#include "testconnections.h" +#include "sql_t1.h" + +using namespace std; + +int main(int argc, char *argv[]) +{ + + TestConnections * Test = new TestConnections(argc, argv); + + Test->repl->connect(); + + MYSQL * conn; + char sql[100]; + + Test->set_timeout(40); + conn = Test->open_rwsplit_connection(); + + Test->tprintf("Cleaning up DB\n"); + execute_query(conn, (char *) "DROP DATABASE IF EXISTS test"); + execute_query(conn, (char *) "CREATE DATABASE test"); + execute_query(conn, (char *) "USE test"); + + Test->tprintf("creating table t1\n"); + Test->set_timeout(40); + create_t1(conn); + + Test->tprintf("Inserting two rows into t1\n"); + Test->set_timeout(40); + execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); + execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(1, 1);"); + + Test->tprintf("Creating temporal table t1\n"); + execute_query(conn, "create temporary table t1 as (SELECT * FROM t1 WHERE fl=3);"); + + Test->tprintf("Inserting one row into temporal table\n"); + execute_query(conn, "INSERT INTO t1 (x1, fl) VALUES(0, 1);"); + + Test->tprintf("Checking t1 temporal table\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(conn, (char *) "SELECT * FROM t1;", 1), "check failed\n"); + + + Test->tprintf("Connecting to all MaxScale routers and checking main t1 table (not temporal)\n"); + Test->set_timeout(240); + Test->add_result(Test->connect_maxscale(), "Connectiong to Maxscale failed\n"); + Test->tprintf("Checking t1 table using RWSplit router\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_rwsplit, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->tprintf("Checking t1 table using ReadConn router in master mode\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_master, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->tprintf("Checking t1 table using ReadConn router in slave mode\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_slave, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->close_maxscale_connections(); + + + printf("Dropping temparal table and check main table again\n"); + execute_query(conn, "DROP TABLE t1;"); + + printf("Connecting to all MaxScale routers and checking main t1 table (not temporal)\n"); + Test->add_result(Test->connect_maxscale(), "Connectiong to Maxscale failed\n"); + Test->tprintf("Checking t1 table using RWSplit router\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_rwsplit, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->tprintf("Checking t1 table using ReadConn router in master mode\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_master, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->tprintf("Checking t1 table using ReadConn router in slave mode\n"); + Test->set_timeout(240); + Test->add_result(execute_select_query_and_check(Test->conn_slave, (char *) "SELECT * FROM t1;", 2), + "check failed\n"); + Test->close_maxscale_connections(); + + mysql_close(conn); + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/test_binlog_fnc.cpp b/maxscale-system-test/test_binlog_fnc.cpp new file mode 100644 index 000000000..f76d0aaa0 --- /dev/null +++ b/maxscale-system-test/test_binlog_fnc.cpp @@ -0,0 +1,238 @@ +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +#include "test_binlog_fnc.h" + +int check_sha1(TestConnections* Test) +{ + char sys[1024]; + char * x; + int local_result = 0; + int i; + int exit_code; + + char *s_maxscale; + char *s; + + Test->set_timeout(50); + Test->tprintf("ls before FLUSH LOGS\n"); + Test->tprintf("Maxscale\n"); + Test->ssh_maxscale(true, "ls -la %s/mar-bin.0000*", Test->maxscale_binlog_dir); + Test->tprintf("Master\n"); + Test->set_timeout(50); + Test->ssh_maxscale(false, "ls -la /var/lib/mysql/mar-bin.0000*"); + + Test->tprintf("FLUSH LOGS\n"); + Test->set_timeout(100); + local_result += execute_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + Test->tprintf("Logs flushed\n"); + Test->set_timeout(100); + sleep(20); + Test->tprintf("ls after first FLUSH LOGS\n"); + Test->tprintf("Maxscale\n"); + Test->set_timeout(50); + Test->ssh_maxscale(true, "ls -la %s/mar-bin.0000*", Test->maxscale_binlog_dir); + + Test->tprintf("Master\n"); + Test->set_timeout(50); + Test->ssh_maxscale(false, "ls -la /var/lib/mysql/mar-bin.0000*"); + + Test->set_timeout(100); + Test->tprintf("FLUSH LOGS\n"); + local_result += execute_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + Test->tprintf("Logs flushed\n"); + + Test->set_timeout(50); + sleep(20); + Test->set_timeout(50); + Test->tprintf("ls before FLUSH LOGS\n"); + Test->tprintf("Maxscale\n"); + + Test->ssh_maxscale(true, "ls -la %s/mar-bin.0000*", Test->maxscale_binlog_dir); + + Test->tprintf("Master\n"); + Test->set_timeout(50); + Test->ssh_maxscale(false, "ls -la /var/lib/mysql/mar-bin.0000*"); + + + for (i = 1; i < 3; i++) + { + Test->tprintf("\nFILE: 000000%d\n", i); + Test->set_timeout(50); + s_maxscale = Test->ssh_maxscale_output(true, "sha1sum %s/mar-bin.00000%d", Test->maxscale_binlog_dir, i); + if (s_maxscale != NULL) + { + x = strchr(s_maxscale, ' '); + if (x != NULL ) + { + x[0] = 0; + } + Test->tprintf("Binlog checksum from Maxscale %s\n", s_maxscale); + } + + sprintf(sys, "sha1sum /var/lib/mysql/mar-bin.00000%d", i); + Test->set_timeout(50); + s = Test->repl->ssh_node_output(0, sys, true, &exit_code); + if (s != NULL) + { + x = strchr(s, ' '); + if (x != NULL ) + { + x[0] = 0; + } + Test->tprintf("Binlog checksum from master %s\n", s); + } + if (strcmp(s_maxscale, s) != 0) + { + Test->tprintf("Binlog from master checksum is not eqiual to binlog checksum from Maxscale node\n"); + local_result++; + } + } + return local_result; +} + +int start_transaction(TestConnections* Test) +{ + int local_result = 0; + Test->tprintf("Transaction test\n"); + Test->tprintf("Start transaction\n"); + execute_query(Test->repl->nodes[0], (char *) "DELETE FROM t1 WHERE fl=10;"); + local_result += execute_query(Test->repl->nodes[0], (char *) "START TRANSACTION"); + local_result += execute_query(Test->repl->nodes[0], (char *) "SET autocommit = 0"); + Test->tprintf("INSERT data\n"); + local_result += execute_query(Test->repl->nodes[0], (char *) "INSERT INTO t1 VALUES(111, 10)"); + Test->stop_timeout(); + sleep(20); + return local_result; +} + +void test_binlog(TestConnections* Test) +{ + int i; + MYSQL* binlog; + Test->repl->connect(); + + Test->set_timeout(100); + Test->try_query(Test->repl->nodes[0], (char *) "SET NAMES utf8mb4"); + Test->try_query(Test->repl->nodes[0], (char *) "set autocommit=1"); + Test->try_query(Test->repl->nodes[0], (char *) "select USER()"); + + Test->set_timeout(100); + create_t1(Test->repl->nodes[0]); + Test->add_result(insert_into_t1(Test->repl->nodes[0], 4), "Data inserting to t1 failed\n"); + Test->stop_timeout(); + Test->tprintf("Sleeping to let replication happen\n"); + sleep(60); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->set_timeout(100); + Test->add_result(select_from_t1(Test->repl->nodes[i], 4), "Selecting from t1 failed\n"); + Test->stop_timeout(); + } + + Test->set_timeout(10); + Test->tprintf("First transaction test (with ROLLBACK)\n"); + start_transaction(Test); + + Test->set_timeout(50); + + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values\n"); + Test->add_result(execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "SELECT check failed\n"); + + //Test->add_result(check_sha1(Test), "sha1 check failed\n"); + + Test->tprintf("ROLLBACK\n"); + Test->try_query(Test->repl->nodes[0], (char *) "ROLLBACK"); + Test->tprintf("INSERT INTO t1 VALUES(112, 10)\n"); + Test->try_query(Test->repl->nodes[0], (char *) "INSERT INTO t1 VALUES(112, 10)"); + Test->try_query(Test->repl->nodes[0], (char *) "COMMIT"); + Test->stop_timeout(); + sleep(20); + + Test->set_timeout(20); + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values\n"); + Test->add_result(execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "112"), "SELECT check failed\n"); + + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values from slave\n"); + Test->add_result(execute_query_check_one(Test->repl->nodes[2], (char *) "SELECT * FROM t1 WHERE fl=10", + "112"), "SELECT check failed\n"); + Test->tprintf("DELETE FROM t1 WHERE fl=10\n"); + Test->try_query(Test->repl->nodes[0], (char *) "DELETE FROM t1 WHERE fl=10"); + Test->tprintf("Checking t1\n"); + Test->add_result(select_from_t1(Test->repl->nodes[0], 4), "SELECT from t1 failed\n"); + + Test->tprintf("Second transaction test (with COMMIT)\n"); + start_transaction(Test); + + Test->tprintf("COMMIT\n"); + Test->try_query(Test->repl->nodes[0], (char *) "COMMIT"); + + Test->tprintf("SELECT, checking inserted values\n"); + Test->add_result(execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "SELECT check failed\n"); + + Test->tprintf("SELECT, checking inserted values from slave\n"); + Test->add_result(execute_query_check_one(Test->repl->nodes[2], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "SELECT check failed\n"); + Test->tprintf("DELETE FROM t1 WHERE fl=10\n"); + Test->try_query(Test->repl->nodes[0], (char *) "DELETE FROM t1 WHERE fl=10"); + + Test->stop_timeout(); + + Test->set_timeout(50); + Test->add_result(check_sha1(Test), "sha1 check failed\n"); + Test->repl->close_connections(); + + Test->stop_timeout(); + + // test SLAVE STOP/START + for (int j = 0; j < 3; j++) + { + Test->set_timeout(100); + Test->repl->connect(); + + Test->tprintf("Dropping and re-creating t1\n"); + Test->try_query(Test->repl->nodes[0], (char *) "DROP TABLE IF EXISTS t1"); + create_t1(Test->repl->nodes[0]); + + Test->tprintf("Connecting to MaxScale binlog router\n"); + binlog = open_conn(Test->binlog_port, Test->maxscale_IP, Test->repl->user_name, Test->repl->password, + Test->ssl); + + Test->tprintf("STOP SLAVE against Maxscale binlog\n"); + execute_query(binlog, (char *) "STOP SLAVE"); + + if (j == 1) + { + Test->tprintf("FLUSH LOGS on master\n"); + execute_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + } + Test->add_result(insert_into_t1(Test->repl->nodes[0], 4), "INSERT into t1 failed\n"); + + Test->tprintf("START SLAVE against Maxscale binlog\n"); + Test->try_query(binlog, (char *) "START SLAVE"); + + Test->tprintf("Sleeping to let replication happen\n"); + Test->stop_timeout(); + sleep(30); + + for (i = 0; i < Test->repl->N; i++) + { + Test->set_timeout(50); + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->add_result(select_from_t1(Test->repl->nodes[i], 4), "SELECT from t1 failed\n"); + } + + Test->set_timeout(100); + Test->add_result(check_sha1(Test), "sha1 check failed\n"); + Test->repl->close_connections(); + Test->stop_timeout(); + } +} + diff --git a/maxscale-system-test/test_binlog_fnc.h b/maxscale-system-test/test_binlog_fnc.h new file mode 100644 index 000000000..86a2ca411 --- /dev/null +++ b/maxscale-system-test/test_binlog_fnc.h @@ -0,0 +1,30 @@ +#ifndef TEST_BINLOG_FNC_H +#define TEST_BINLOG_FNC_H + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +/** + * @brief check_sha1 Check that checksum of binlog files on Maxscale machines and all backends are equal + * @param Test TestConnections object + * @return 0 if binlog files checksums are identical + */ +int check_sha1(TestConnections* Test); + +/** + * @brief start_transaction Template for test transaction (used by test_binlog() + * @param Test TestConnections object + * @return 0 in case of success + */ +int start_transaction(TestConnections* Test); + +/** + * @brief test_binlog Execute a number of tests for check if binlog router is ok + * (see test description in setup_binlog.cpp) + * @param Test TestConnections object + */ +void test_binlog(TestConnections* Test); + +#endif // TEST_BINLOG_FNC_H diff --git a/maxscale-system-test/test_ctrl_c/start_killer.sh b/maxscale-system-test/test_ctrl_c/start_killer.sh new file mode 100755 index 000000000..76b6fe82f --- /dev/null +++ b/maxscale-system-test/test_ctrl_c/start_killer.sh @@ -0,0 +1,3 @@ +sleep 5 +$maxscale_access_sudo /usr/bin/killall maxscale -s INT + diff --git a/maxscale-system-test/test_ctrl_c/test_ctrl_c.sh b/maxscale-system-test/test_ctrl_c/test_ctrl_c.sh new file mode 100755 index 000000000..85e0952d6 --- /dev/null +++ b/maxscale-system-test/test_ctrl_c/test_ctrl_c.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +$maxscale_access_sudo service maxscale stop + +hm=`pwd` +$hm/start_killer.sh & +if [ $? -ne 0 ] ; then + exit 1 +fi + +T="$(date +%s)" + +$maxscale_access_sudo maxscale -d +if [ $? -ne 0 ] ; then + exit 1 +fi + +T="$(($(date +%s)-T))" +echo "Time in seconds: ${T} (including 5 seconds before kill)" + +if [ "$T" -lt 10 ] ; then + echo "PASSED" + exit 0 +else + echo "FAILED" + exit 1 +fi + diff --git a/maxscale-system-test/test_hints.cpp b/maxscale-system-test/test_hints.cpp new file mode 100644 index 000000000..9e5be74f7 --- /dev/null +++ b/maxscale-system-test/test_hints.cpp @@ -0,0 +1,101 @@ +/** + * @file routing_hints.cpp - Test routing hints + * - execute a number of 'select @@server_id' with different hints and check that + * query goes to backend according hint + */ + + +#include +#include "testconnections.h" + +#define SERVER1 0 +#define SERVER2 1 +#define SERVER3 2 +#define SERVER4 3 + +static struct result +{ + const char* query; + int reply; +} queries[] = +{ + {"select @@server_id; -- maxscale begin route to master", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; -- maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; -- maxscale end", SERVER2}, + {"select @@server_id; -- maxscale named1 prepare route to master", SERVER2}, + {"select @@server_id; -- maxscale named1 begin", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; -- maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; -- maxscale end", SERVER2}, + {"select @@server_id; -- maxscale shorthand1 begin route to server server2", SERVER2}, + {"select @@server_id;", SERVER2}, + {"select @@server_id; -- maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER2}, + {"select @@server_id; -- maxscale end", SERVER2}, + {"select @@server_id; # maxscale begin route to master", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; # maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; # maxscale end", SERVER2}, + {"select @@server_id; # maxscale named2 prepare route to master", SERVER2}, + {"select @@server_id; # maxscale named2 begin", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; # maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id; # maxscale end", SERVER2}, + {"select @@server_id; # maxscale shorthand2 begin route to server server2", SERVER2}, + {"select @@server_id;", SERVER2}, + {"select @@server_id; # maxscale route to server server3", SERVER3}, + {"select @@server_id;", SERVER2}, + {"select @@server_id; # maxscale end", SERVER2}, + {"select @@server_id/* maxscale begin route to master */;", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id/* maxscale route to server server3 */;", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id/* maxscale end */;", SERVER2}, + {"select @@server_id/* maxscale named3 prepare route to master */;", SERVER2}, + {"select @@server_id/* maxscale named3 begin */;", SERVER1}, + {"select @@server_id;", SERVER1}, + {"select @@server_id/* maxscale route to server server3 */;", SERVER3}, + {"select @@server_id;", SERVER1}, + {"select @@server_id/* maxscale end */;", SERVER2}, + {"select @@server_id/* maxscale shorthand3 begin route to server server2 */; ", SERVER2}, + {"select @@server_id;", SERVER2}, + {"select @@server_id/* maxscale route to server server3 */;", SERVER3}, + {"select @@server_id;", SERVER2}, + {"select @@server_id/* maxscale end */;", SERVER2}, + {NULL, SERVER1} +}; + +int main(int argc, char** argv) +{ + TestConnections* test = new TestConnections(argc, argv); + test->repl->connect(); + test->connect_maxscale(); + + char server_id[test->repl->N][1024]; + + /** Get server_id for each node */ + for (int i = 0; i < test->repl->N; i++) + { + sprintf(server_id[i], "%d", test->repl->get_server_id(i)); + } + + for (int i = 0; queries[i].query; i++) + { + char str[1024]; + find_field(test->conn_rwsplit, queries[i].query, "@@server_id", str); + if (strcmp(server_id[queries[i].reply], str) != 0) + { + test->add_result(1, "%s: Expected %s but got %s.\n", + queries[i].query, server_id[queries[i].reply], str); + } + } + int rval = test->global_result; + delete test; + return rval; +} + diff --git a/maxscale-system-test/testconnections.cpp b/maxscale-system-test/testconnections.cpp new file mode 100644 index 000000000..a151c1c41 --- /dev/null +++ b/maxscale-system-test/testconnections.cpp @@ -0,0 +1,2102 @@ +#include +#include +#include +#include +#include +#include + +#include "mariadb_func.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" +#include "testconnections.h" + +namespace maxscale +{ +static bool start = true; +static bool check_nodes = true; +static std::string required_repl_version; +static std::string required_galera_version; +} + +void TestConnections::check_nodes(bool value) +{ + maxscale::check_nodes = value; +} + +void TestConnections::skip_maxscale_start(bool value) +{ + maxscale::start = !value; +} + +void TestConnections::require_repl_version(const char *version) +{ + maxscale::required_repl_version = version; +} + +void TestConnections::require_galera_version(const char *version) +{ + maxscale::required_galera_version = version; +} + +TestConnections::TestConnections(int argc, char *argv[]): + no_backend_log_copy(false), use_snapshots(false), verbose(false), rwsplit_port(4006), + readconn_master_port(4008), readconn_slave_port(4009), binlog_port(5306), + global_result(0), binlog_cmd_option(0), enable_timeouts(true), use_ipv6(false), + no_galera(false) +{ + chdir(test_dir); + gettimeofday(&start_time, NULL); + ports[0] = rwsplit_port; + ports[1] = readconn_master_port; + ports[2] = readconn_slave_port; + + read_env(); + + char * gal_env = getenv("galera_000_network"); + if ((gal_env == NULL) || (strcmp(gal_env, "") == 0 )) + { + no_galera = true; + tprintf("Galera backend variables are not defined, Galera won't be used\n"); + } + + bool maxscale_init = true; + + static struct option long_options[] = + { + + {"verbose", no_argument, 0, 'v'}, + {"silent", no_argument, 0, 'n'}, + {"help", no_argument, 0, 'h'}, + {"no-maxscale-start", no_argument, 0, 's'}, + {"no-nodes-check", no_argument, 0, 'r'}, + {"quiet", no_argument, 0, 'q'}, + {"restart-galera", no_argument, 0, 'g'}, + {"no-timeouts", no_argument, 0, 'z'}, + {"no-galera", no_argument, 0, 'y'}, + {0, 0, 0, 0} + }; + + int c; + int option_index = 0; + bool restart_galera = false; + + while ((c = getopt_long(argc, argv, "vnqhsirgzy", long_options, &option_index)) != -1) + { + switch (c) + { + case 'v': + verbose = true; + break; + + case 'n': + verbose = false; + break; + + case 'q': + freopen("/dev/null", "w", stdout); + break; + + case 'h': + printf("Options:\n" + "-h, --help\n" + "-v, --verbose\n" + "-q, --silent\n" + "-s, --no-maxscale-start\n" + "-i, --no-maxscale-init\n" + "-g, --restart-galera\n" + "-y, --no-galera\n" + "-z, --no-timeouts\n"); + exit(0); + break; + + case 's': + printf("Maxscale won't be started\n"); + maxscale::start = false; + break; + case 'i': + printf("Maxscale won't be started and Maxscale.cnf won't be uploaded\n"); + maxscale_init = false; + break; + + case 'r': + printf("Nodes are not checked before test and are not restarted\n"); + maxscale::check_nodes = false; + break; + + case 'g': + printf("Restarting Galera setup\n"); + restart_galera = true; + break; + + case 'z': + enable_timeouts = false; + break; + + case 'y': + printf("Do not use Galera setup\n"); + no_galera = true; + break; + + default: + printf("UNKNOWN OPTION: %c\n", c); + break; + } + } + + if (optind < argc) + { + test_name = argv[optind]; + } + else + { + test_name = basename(argv[0]); + } + + sprintf(get_logs_command, "%s/get_logs.sh", test_dir); + + sprintf(ssl_options, "--ssl-cert=%s/ssl-cert/client-cert.pem --ssl-key=%s/ssl-cert/client-key.pem", + test_dir, test_dir); + setenv("ssl_options", ssl_options, 1); + + repl = new Mariadb_nodes("node", test_dir, verbose); + if (!no_galera) + { + galera = new Galera_nodes("galera", test_dir, verbose); + //galera->use_ipv6 = use_ipv6; + galera->use_ipv6 = false; + } + else + { + galera = repl; + } + + repl->use_ipv6 = use_ipv6; + + + if (maxscale::required_repl_version.length()) + { + int ver_repl_required = get_int_version(maxscale::required_repl_version); + std::string ver_repl = repl->get_lowest_version(); + int int_ver_repl = get_int_version(ver_repl); + + if (int_ver_repl < ver_repl_required) + { + tprintf("Test requires a higher version of backend servers, skipping test."); + tprintf("Required version: %s", maxscale::required_repl_version.c_str()); + tprintf("Master-slave version: %s", ver_repl.c_str()); + exit(0); + } + } + + if (maxscale::required_galera_version.length()) + { + int ver_galera_required = get_int_version(maxscale::required_galera_version); + std::string ver_galera = galera->get_lowest_version(); + int int_ver_galera = get_int_version(ver_galera); + + if (int_ver_galera < ver_galera_required) + { + tprintf("Test requires a higher version of backend servers, skipping test."); + tprintf("Required version: %s", maxscale::required_galera_version.c_str()); + tprintf("Galera version: %s", ver_galera.c_str()); + exit(0); + } + } + + if ((restart_galera) && (!no_galera)) + { + galera->stop_nodes(); + galera->start_replication(); + } + + bool snapshot_reverted = false; + + if (use_snapshots) + { + snapshot_reverted = revert_snapshot((char *) "clean"); + } + + if (!snapshot_reverted && maxscale::check_nodes) + { + if (!repl->fix_replication() ) + { + exit(200); + } + if (!no_galera) + { + if (!galera->fix_replication()) + { + exit(200); + } + } + } + + if (maxscale_init) + { + init_maxscale(); + } + + if (backend_ssl) + { + tprintf("Configuring backends for ssl \n"); + repl->configure_ssl(true); + if (!no_galera) + { + galera->configure_ssl(false); + galera->start_replication(); + } + } + + char str[1024]; + sprintf(str, "mkdir -p LOGS/%s", test_name); + system(str); + + timeout = 999999999; + set_log_copy_interval(999999999); + pthread_create( &timeout_thread_p, NULL, timeout_thread, this); + pthread_create( &log_copy_thread_p, NULL, log_copy_thread, this); + tprintf("Starting test"); + gettimeofday(&start_time, NULL); +} + +TestConnections::~TestConnections() +{ + if (backend_ssl) + { + repl->disable_ssl(); + //galera->disable_ssl(); + } + + copy_all_logs(); + + if (global_result != 0 ) + { + tprintf("Reverting snapshot\n"); + revert_snapshot((char*) "clean"); + } + + delete repl; + if (!no_galera) + { + delete galera; + } +} + +void TestConnections::add_result(int result, const char *format, ...) +{ + timeval t2; + gettimeofday(&t2, NULL); + double elapsedTime = (t2.tv_sec - start_time.tv_sec); + elapsedTime += (double) (t2.tv_usec - start_time.tv_usec) / 1000000.0; + + if (result != 0) + { + global_result += result; + + printf("%04f: TEST_FAILED! ", elapsedTime); + + va_list argp; + va_start(argp, format); + vprintf(format, argp); + va_end(argp); + + if (format[strlen(format) - 1] != '\n') + { + printf("\n"); + } + } +} + +int TestConnections::read_env() +{ + + char *env; + + if (verbose) + { + printf("Reading test setup configuration from environmental variables\n"); + } + + env = getenv("maxscale_IP"); + if (env != NULL) + { + sprintf(maxscale_IP, "%s", env); + } + env = getenv("maxscale_network6"); + if (env != NULL) + { + sprintf(maxscale_IP6, "%s", env); + } + env = getenv("maxscale_user"); + if (env != NULL) + { + sprintf(maxscale_user, "%s", env); + } + else + { + sprintf(maxscale_user, "skysql"); + } + env = getenv("maxscale_password"); + if (env != NULL) + { + sprintf(maxscale_password, "%s", env); + } + else + { + sprintf(maxscale_password, "skysql"); + } + env = getenv("maxadmin_password"); + if (env != NULL) + { + sprintf(maxadmin_password, "%s", env); + } + else + { + sprintf(maxadmin_password, "mariadb"); + } + env = getenv("maxscale_keyfile"); + if (env != NULL) + { + sprintf(maxscale_keyfile, "%s", env); + } + else + { + sprintf(maxscale_keyfile, "skysql"); + } + + //env = getenv("get_logs_command"); if (env != NULL) {sprintf(get_logs_command, "%s", env);} + + env = getenv("sysbench_dir"); + if (env != NULL) + { + sprintf(sysbench_dir, "%s", env); + } + + env = getenv("maxscale_cnf"); + if (env != NULL) + { + sprintf(maxscale_cnf, "%s", env); + } + else + { + sprintf(maxscale_cnf, "/etc/maxscale.cnf"); + } + env = getenv("maxscale_log_dir"); + if (env != NULL) + { + sprintf(maxscale_log_dir, "%s", env); + } + else + { + sprintf(maxscale_log_dir, "/var/log/maxscale/"); + } + env = getenv("maxscale_binlog_dir"); + if (env != NULL) + { + sprintf(maxscale_binlog_dir, "%s", env); + } + else + { + sprintf(maxscale_binlog_dir, "/var/lib/maxscale/Binlog_Service/"); + } + //env = getenv("test_dir"); if (env != NULL) {sprintf(test_dir, "%s", env);} + env = getenv("maxscale_whoami"); + if (env != NULL) + { + sprintf(maxscale_access_user, "%s", env); + } + env = getenv("maxscale_access_sudo"); + if (env != NULL) + { + sprintf(maxscale_access_sudo, "%s", env); + } + ssl = false; + env = getenv("ssl"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + ssl = true; + } + env = getenv("mysql51_only"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + maxscale::check_nodes = false; + } + + env = getenv("no_nodes_check"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + maxscale::check_nodes = false; + } + env = getenv("no_backend_log_copy"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + no_backend_log_copy = true; + } + env = getenv("use_ipv6"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + use_ipv6 = true; + } + + env = getenv("maxscale_hostname"); + if (env != NULL) + { + sprintf(maxscale_hostname, "%s", env); + } + else + { + sprintf(maxscale_hostname, "%s", maxscale_IP); + } + + env = getenv("backend_ssl"); + if (env != NULL && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + backend_ssl = true; + } + else + { + backend_ssl = false; + } + + if (strcmp(maxscale_access_user, "root") == 0) + { + sprintf(maxscale_access_homedir, "/%s/", maxscale_access_user); + } + else + { + sprintf(maxscale_access_homedir, "/home/%s/", maxscale_access_user); + } + + env = getenv("smoke"); + if ((env != NULL) && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + smoke = true; + } + else + { + smoke = false; + } + env = getenv("threads"); + if ((env != NULL)) + { + sscanf(env, "%d", &threads); + } + else + { + threads = 4; + } + + env = getenv("use_snapshots"); + if (env != NULL && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + use_snapshots = true; + } + else + { + use_snapshots = false; + } + env = getenv("take_snapshot_command"); + if (env != NULL) + { + sprintf(take_snapshot_command, "%s", env); + } + else + { + sprintf(take_snapshot_command, "exit 1"); + } + env = getenv("revert_snapshot_command"); + if (env != NULL) + { + sprintf(revert_snapshot_command, "%s", env); + } + else + { + sprintf(revert_snapshot_command, "exit 1"); + } + + env = getenv("no_maxscale_start"); + if (env != NULL && ((strcasecmp(env, "yes") == 0) || (strcasecmp(env, "true") == 0) )) + { + maxscale::start = false; + } +} + +int TestConnections::print_env() +{ + int i; + printf("Maxscale IP\t%s\n", maxscale_IP); + printf("Maxscale User name\t%s\n", maxscale_user); + printf("Maxscale Password\t%s\n", maxscale_password); + printf("Maxscale SSH key\t%s\n", maxscale_keyfile); + printf("Maxadmin password\t%s\n", maxadmin_password); + printf("Access user\t%s\n", maxscale_access_user); + repl->print_env(); + galera->print_env(); +} + +const char * get_template_name(char * test_name) +{ + int i = 0; + while (cnf_templates[i].test_name && strcmp(cnf_templates[i].test_name, test_name) != 0) + { + i++; + } + + if (cnf_templates[i].test_name) + { + return cnf_templates[i].test_template; + } + + printf("Failed to find configuration template for test '%s', using default template '%s'.\n", test_name, + default_template); + return default_template; +} + +void TestConnections::process_template(const char *template_name, const char *dest) +{ + char str[4096]; + char template_file[1024]; + + sprintf(template_file, "%s/cnf/maxscale.cnf.template.%s", test_dir, template_name); + sprintf(str, "cp %s maxscale.cnf", template_file); + if (system(str) != 0) + { + tprintf("Error copying maxscale.cnf template\n"); + return; + } + + if (backend_ssl) + { + tprintf("Adding ssl settings\n"); + system("sed -i \"s|type=server|type=server\\nssl=required\\nssl_cert=/###access_homedir###/certs/client-cert.pem\\nssl_key=/###access_homedir###/certs/client-key.pem\\nssl_ca_cert=/###access_homedir###/certs/ca.pem|g\" maxscale.cnf"); + } + + sprintf(str, "sed -i \"s/###threads###/%d/\" maxscale.cnf", threads); + system(str); + + Mariadb_nodes * mdn[2]; + char * IPcnf; + mdn[0] = repl; + mdn[1] = galera; + int i, j; + + for (j = 0; j < 2; j++) + { + for (i = 0; i < mdn[j]->N; i++) + { + if (mdn[j]->use_ipv6) + { + IPcnf = mdn[j]->IP6[i]; + } + else + { + IPcnf = mdn[j]->IP[i]; + } + sprintf(str, "sed -i \"s/###%s_server_IP_%0d###/%s/\" maxscale.cnf", + mdn[j]->prefix, i + 1, IPcnf); + system(str); + + sprintf(str, "sed -i \"s/###%s_server_port_%0d###/%d/\" maxscale.cnf", + mdn[j]->prefix, i + 1, mdn[j]->port[i]); + system(str); + } + + mdn[j]->connect(); + execute_query(mdn[j]->nodes[0], (char *) "CREATE DATABASE IF NOT EXISTS test"); + mdn[j]->close_connections(); + } + + sprintf(str, "sed -i \"s/###access_user###/%s/g\" maxscale.cnf", maxscale_access_user); + system(str); + + sprintf(str, "sed -i \"s|###access_homedir###|%s|g\" maxscale.cnf", maxscale_access_homedir); + system(str); + + if (repl->v51) + { + system("sed -i \"s/###repl51###/mysql51_replication=true/g\" maxscale.cnf"); + } + copy_to_maxscale((char *) "maxscale.cnf", (char *) dest); +} + +int TestConnections::init_maxscale() +{ + const char * template_name = get_template_name(test_name); + tprintf("Template is %s\n", template_name); + + process_template(template_name, maxscale_access_homedir); + + ssh_maxscale(true, "cp maxscale.cnf %s;rm -rf %s/certs;mkdir -m a+wrx %s/certs;", maxscale_cnf, + maxscale_access_homedir, maxscale_access_homedir); + + char str[4096]; + char dtr[4096]; + sprintf(str, "%s/ssl-cert/*", test_dir); + sprintf(dtr, "%s/certs/", maxscale_access_homedir); + copy_to_maxscale(str, dtr); + sprintf(str, "cp %s/ssl-cert/* .", test_dir); + system(str); + + ssh_maxscale(true, "chown maxscale:maxscale -R %s/certs;" + "chmod 664 %s/certs/*.pem;" + " chmod a+x %s;" + "%s" + "iptables -I INPUT -p tcp --dport 4001 -j ACCEPT;" + "rm -f %s/maxscale.log %s/maxscale1.log;" + "rm -rf /tmp/core* /dev/shm/* /var/lib/maxscale/maxscale.cnf.d/ /var/lib/maxscale/*;" + "%s", + maxscale_access_homedir, maxscale_access_homedir, maxscale_access_homedir, + maxscale::start ? "killall -9 maxscale;" : "", + maxscale_log_dir, maxscale_log_dir, maxscale::start ? "service maxscale restart" : ""); + + fflush(stdout); + + if (maxscale::start) + { + int waits; + + for (waits = 0; waits < 15; waits++) + { + if (ssh_maxscale(true, "/bin/sh -c \"maxadmin help > /dev/null || exit 1\"") == 0) + { + break; + } + sleep(1); + } + + if (waits > 0) + { + tprintf("Waited %d seconds for MaxScale to start", waits); + } + } +} + +int TestConnections::connect_maxscale() +{ + return connect_rwsplit() + + connect_readconn_master() + + connect_readconn_slave(); +} + +int TestConnections::close_maxscale_connections() +{ + mysql_close(conn_master); + mysql_close(conn_slave); + mysql_close(conn_rwsplit); +} + +int TestConnections::restart_maxscale() +{ + sleep(15); + int res = ssh_maxscale(true, "service maxscale restart"); + fflush(stdout); + sleep(10); + return res; +} + +int TestConnections::start_maxscale() +{ + sleep(15); + int res = ssh_maxscale(true, "service maxscale start"); + fflush(stdout); + sleep(10); + return res; +} + +int TestConnections::stop_maxscale() +{ + int res = ssh_maxscale(true, "service maxscale stop"); + check_maxscale_processes(0); + fflush(stdout); + return res; +} + +int TestConnections::copy_mariadb_logs(Mariadb_nodes * repl, char * prefix) +{ + int local_result = 0; + char * mariadb_log; + FILE * f; + int i; + int exit_code; + char str[4096]; + + sprintf(str, "mkdir -p LOGS/%s", test_name); + system(str); + for (i = 0; i < repl->N; i++) + { + if (strcmp(repl->IP[i], "127.0.0.1") != 0) // Do not copy MariaDB logs in case of local backend + { + mariadb_log = repl->ssh_node_output(i, (char *) "cat /var/lib/mysql/*.err", true, &exit_code); + sprintf(str, "LOGS/%s/%s%d_mariadb_log", test_name, prefix, i); + f = fopen(str, "w"); + if (f != NULL) + { + fwrite(mariadb_log, sizeof(char), strlen(mariadb_log), f); + fclose(f); + } + else + { + printf("Error writing MariaDB log"); + local_result = 1; + } + free(mariadb_log); + } + } + return local_result; +} + +int TestConnections::copy_all_logs() +{ + char str[4096]; + set_timeout(300); + + if (!no_backend_log_copy) + { + copy_mariadb_logs(repl, (char *) "node"); + copy_mariadb_logs(galera, (char *) "galera"); + } + + sprintf(str, "%s/copy_logs.sh %s", test_dir, test_name); + tprintf("Executing %s\n", str); + if (system(str) != 0) + { + tprintf("copy_logs.sh executing FAILED!\n"); + return 1; + } + else + { + tprintf("copy_logs.sh OK!\n"); + return 0; + } +} + +int TestConnections::copy_all_logs_periodic() +{ + char str[4096]; + //set_timeout(300); + + timeval t2; + gettimeofday(&t2, NULL); + double elapsedTime = (t2.tv_sec - start_time.tv_sec); + elapsedTime += (double) (t2.tv_usec - start_time.tv_usec) / 1000000.0; + + sprintf(str, "%s/copy_logs.sh %s %04f", test_dir, test_name, elapsedTime); + tprintf("Executing %s\n", str); + if (system(str) != 0) + { + tprintf("copy_logs.sh executing FAILED!\n"); + return 1; + } + else + { + tprintf("copy_logs.sh OK!\n"); + return 0; + } +} + +int TestConnections::prepare_binlog() +{ + char version_str[1024]; + find_field(repl->nodes[0], "SELECT @@VERSION", "@@version", version_str); + tprintf("Master server version %s\n", version_str); + + if ((strstr(version_str, "10.0") != NULL) || + (strstr(version_str, "10.1") != NULL) || + (strstr(version_str, "10.2") != NULL)) + { + tprintf("10.0!\n"); + } + else + { + add_result(ssh_maxscale(true, + "sed -i \"s/,mariadb10-compatibility=1//\" %s", + maxscale_cnf), "Error editing maxscale.cnf"); + } + + tprintf("Removing all binlog data from Maxscale node\n"); + add_result(ssh_maxscale(true, "rm -rf %s", maxscale_binlog_dir), + "Removing binlog data failed\n"); + + tprintf("Creating binlog dir\n"); + add_result(ssh_maxscale(true, "mkdir -p %s", maxscale_binlog_dir), + "Creating binlog data dir failed\n"); + tprintf("Set 'maxscale' as a owner of binlog dir\n"); + add_result(ssh_maxscale(false, + "%s mkdir -p %s; %s chown maxscale:maxscale -R %s", + maxscale_access_sudo, maxscale_binlog_dir, + maxscale_access_sudo, maxscale_binlog_dir), + "directory ownership change failed\n"); + return 0; +} + +int TestConnections::start_binlog() +{ + char sys1[4096]; + MYSQL * binlog; + char log_file[256]; + char log_pos[256]; + char cmd_opt[256]; + + int i; + int global_result = 0; + bool no_pos; + + no_pos = repl->no_set_pos; + + switch (binlog_cmd_option) + { + case 1: + sprintf(cmd_opt, "--binlog-checksum=CRC32"); + break; + case 2: + sprintf(cmd_opt, "--binlog-checksum=NONE"); + break; + default: + sprintf(cmd_opt, " "); + } + + repl->stop_nodes(); + + binlog = open_conn_no_db(binlog_port, maxscale_IP, repl->user_name, repl->password, ssl); + execute_query(binlog, (char *) "stop slave"); + execute_query(binlog, (char *) "reset slave all"); + execute_query(binlog, (char *) "reset master"); + mysql_close(binlog); + + tprintf("Stopping maxscale\n"); + add_result(stop_maxscale(), "Maxscale stopping failed\n"); + + for (i = 0; i < repl->N; i++) + { + repl->start_node(i, cmd_opt); + } + sleep(5); + + tprintf("Connecting to all backend nodes\n"); + repl->connect(); + + for (i = 0; i < repl->N; i++) + { + execute_query(repl->nodes[i], "stop slave"); + execute_query(repl->nodes[i], "reset slave all"); + execute_query(repl->nodes[i], "reset master"); + } + + prepare_binlog(); + + tprintf("Testing binlog when MariaDB is started with '%s' option\n", cmd_opt); + + tprintf("ls binlog data dir on Maxscale node\n"); + add_result(ssh_maxscale(true, "ls -la %s/", maxscale_binlog_dir), "ls failed\n"); + + tprintf("show master status\n"); + find_field(repl->nodes[0], (char *) "show master status", (char *) "File", &log_file[0]); + find_field(repl->nodes[0], (char *) "show master status", (char *) "Position", &log_pos[0]); + tprintf("Real master file: %s\n", log_file); + tprintf("Real master pos : %s\n", log_pos); + + tprintf("Stopping first slave (node 1)\n"); + try_query(repl->nodes[1], (char *) "stop slave;"); + //repl->no_set_pos = true; + repl->no_set_pos = false; + tprintf("Configure first backend slave node to be slave of real master\n"); + repl->set_slave(repl->nodes[1], repl->IP[0], repl->port[0], log_file, log_pos); + + tprintf("Starting back Maxscale\n"); + add_result(start_maxscale(), "Maxscale start failed\n"); + + tprintf("Connecting to MaxScale binlog router (with any DB)\n"); + binlog = open_conn_no_db(binlog_port, maxscale_IP, repl->user_name, repl->password, ssl); + + add_result(mysql_errno(binlog), "Error connection to binlog router %s\n", mysql_error(binlog)); + + repl->no_set_pos = true; + tprintf("configuring Maxscale binlog router\n"); + repl->set_slave(binlog, repl->IP[0], repl->port[0], log_file, log_pos); + + // ssl between binlog router and Master + if (backend_ssl) + { + sprintf(sys1, + "CHANGE MASTER TO master_ssl_cert='%s/certs/client-cert.pem', master_ssl_ca='%s/certs/ca.pem', master_ssl=1, master_ssl_key='%s/certs/client-key.pem'", + maxscale_access_homedir, maxscale_access_homedir, maxscale_access_homedir); + tprintf("Configuring Master ssl: %s\n", sys1); + try_query(binlog, sys1); + } + + try_query(binlog, "start slave"); + try_query(binlog, "show slave status"); + + repl->no_set_pos = false; + + // get Master status from Maxscale binlog + tprintf("show master status\n"); + fflush(stdout); + find_field(binlog, (char *) "show master status", (char *) "File", &log_file[0]); + find_field(binlog, (char *) "show master status", (char *) "Position", &log_pos[0]); + + tprintf("Maxscale binlog master file: %s\n", log_file); + fflush(stdout); + tprintf("Maxscale binlog master pos : %s\n", log_pos); + fflush(stdout); + + tprintf("Setup all backend nodes except first one to be slaves of binlog Maxscale node\n"); + fflush(stdout); + for (i = 2; i < repl->N; i++) + { + try_query(repl->nodes[i], (char *) "stop slave;"); + repl->set_slave(repl->nodes[i], maxscale_IP, binlog_port, log_file, log_pos); + } + repl->close_connections(); + try_query(binlog, "show slave status"); + mysql_close(binlog); + repl->no_set_pos = no_pos; + return global_result; +} + +int TestConnections::start_mm() +{ + int i; + char log_file1[256]; + char log_pos1[256]; + char log_file2[256]; + char log_pos2[256]; + + tprintf("Stopping maxscale\n"); + fflush(stdout); + int global_result = stop_maxscale(); + + tprintf("Stopping all backend nodes\n"); + fflush(stdout); + global_result += repl->stop_nodes(); + + for (i = 0; i < 2; i++) + { + tprintf("Starting back node %d\n", i); + global_result += repl->start_node(i, (char *) ""); + } + + repl->connect(); + for (i = 0; i < 2; i++) + { + execute_query(repl->nodes[i], (char *) "stop slave"); + execute_query(repl->nodes[i], (char *) "reset master"); + } + + execute_query(repl->nodes[0], (char *) "SET GLOBAL READ_ONLY=ON"); + + find_field(repl->nodes[0], (char *) "show master status", (char *) "File", log_file1); + find_field(repl->nodes[0], (char *) "show master status", (char *) "Position", log_pos1); + + find_field(repl->nodes[1], (char *) "show master status", (char *) "File", log_file2); + find_field(repl->nodes[1], (char *) "show master status", (char *) "Position", log_pos2); + + repl->set_slave(repl->nodes[0], repl->IP[1], repl->port[1], log_file2, log_pos2); + repl->set_slave(repl->nodes[1], repl->IP[0], repl->port[0], log_file1, log_pos1); + + repl->close_connections(); + + tprintf("Starting back Maxscale\n"); + fflush(stdout); + global_result += start_maxscale(); + + return global_result; +} + +void TestConnections::check_log_err(const char * err_msg, bool expected) +{ + + char * err_log_content; + + tprintf("Getting logs\n"); + char sys1[4096]; + set_timeout(100); + sprintf(&sys1[0], "rm -f *.log; %s %s", get_logs_command, maxscale_IP); + //tprintf("Executing: %s\n", sys1); + system(sys1); + set_timeout(50); + + tprintf("Reading maxscale.log\n"); + if ( ( read_log((char *) "maxscale.log", &err_log_content) != 0) || (strlen(err_log_content) < 2) ) + { + tprintf("Reading maxscale1.log\n"); + free(err_log_content); + if (read_log((char *) "maxscale1.log", &err_log_content) != 0) + { + add_result(1, "Error reading log\n"); + } + } + //printf("\n\n%s\n\n", err_log_content); + if (err_log_content != NULL) + { + if (expected) + { + if (strstr(err_log_content, err_msg) == NULL) + { + add_result(1, "There is NO \"%s\" error in the log\n", err_msg); + } + else + { + tprintf("There is proper \"%s \" error in the log\n", err_msg); + } + } + else + { + if (strstr(err_log_content, err_msg) != NULL) + { + add_result(1, "There is UNEXPECTED error \"%s\" error in the log\n", err_msg); + } + else + { + tprintf("There are no unxpected errors \"%s \" error in the log\n", err_msg); + } + } + + free(err_log_content); + } +} + +int TestConnections::find_connected_slave(int * global_result) +{ + int conn_num; + int all_conn = 0; + int current_slave = -1; + repl->connect(); + for (int i = 0; i < repl->N; i++) + { + conn_num = get_conn_num(repl->nodes[i], maxscale_ip(), maxscale_hostname, (char *) "test"); + tprintf("connections to %d: %u\n", i, conn_num); + if ((i == 0) && (conn_num != 1)) + { + tprintf("There is no connection to master\n"); + *global_result = 1; + } + all_conn += conn_num; + if ((i != 0) && (conn_num != 0)) + { + current_slave = i; + } + } + if (all_conn != 2) + { + tprintf("total number of connections is not 2, it is %d\n", all_conn); + *global_result = 1; + } + tprintf("Now connected slave node is %d (%s)\n", current_slave, repl->IP[current_slave]); + repl->close_connections(); + return current_slave; +} + +int TestConnections::find_connected_slave1() +{ + int conn_num; + int all_conn = 0; + int current_slave = -1; + repl->connect(); + for (int i = 0; i < repl->N; i++) + { + conn_num = get_conn_num(repl->nodes[i], maxscale_ip(), maxscale_hostname, (char *) "test"); + tprintf("connections to %d: %u\n", i, conn_num); + all_conn += conn_num; + if ((i != 0) && (conn_num != 0)) + { + current_slave = i; + } + } + tprintf("Now connected slave node is %d (%s)\n", current_slave, repl->IP[current_slave]); + repl->close_connections(); + return current_slave; +} + +int TestConnections::check_maxscale_processes(int expected) +{ + char* maxscale_num = ssh_maxscale_output(false, "ps -C maxscale | grep maxscale | wc -l"); + if (maxscale_num == NULL) + { + return -1; + } + char* nl = strchr(maxscale_num, '\n'); + if (nl) + { + *nl = '\0'; + } + + if (atoi(maxscale_num) != expected) + { + tprintf("%s maxscale processes detected, trying agin in 5 seconds\n", maxscale_num); + sleep(5); + maxscale_num = ssh_maxscale_output(false, "ps -C maxscale | grep maxscale | wc -l"); + if (atoi(maxscale_num) != expected) + { + add_result(1, "Number of MaxScale processes is not %d, it is %s\n", expected, maxscale_num); + } + } + + return 0; +} + +int TestConnections::check_maxscale_alive() +{ + int gr = global_result; + set_timeout(10); + tprintf("Connecting to Maxscale\n"); + add_result(connect_maxscale(), "Can not connect to Maxscale\n"); + tprintf("Trying simple query against all sevices\n"); + tprintf("RWSplit \n"); + set_timeout(10); + try_query(conn_rwsplit, (char *) "show databases;"); + tprintf("ReadConn Master \n"); + set_timeout(10); + try_query(conn_master, (char *) "show databases;"); + tprintf("ReadConn Slave \n"); + set_timeout(10); + try_query(conn_slave, (char *) "show databases;"); + set_timeout(10); + close_maxscale_connections() ; + add_result(global_result - gr, "Maxscale is not alive\n"); + stop_timeout(); + + check_maxscale_processes(1); + + return global_result - gr; +} + +int TestConnections::test_maxscale_connections(bool rw_split, bool rc_master, bool rc_slave) +{ + int rval = 0; + int rc; + + tprintf("Testing RWSplit, expecting %s\n", (rw_split ? "success" : "failure")); + rc = execute_query(conn_rwsplit, "select 1"); + if ((rc == 0) != rw_split) + { + tprintf("Error: Query %s\n", (rw_split ? "failed" : "succeeded")); + rval++; + } + + tprintf("Testing ReadConnRoute Master, expecting %s\n", (rc_master ? "success" : "failure")); + rc = execute_query(conn_master, "select 1"); + if ((rc == 0) != rc_master) + { + tprintf("Error: Query %s", (rc_master ? "failed" : "succeeded")); + rval++; + } + + tprintf("Testing ReadConnRoute Slave, expecting %s\n", (rc_slave ? "success" : "failure")); + rc = execute_query(conn_slave, "select 1"); + if ((rc == 0) != rc_slave) + { + tprintf("Error: Query %s", (rc_slave ? "failed" : "succeeded")); + rval++; + } + return rval; +} + +void TestConnections::generate_ssh_cmd(char * cmd, char * ssh, bool sudo) +{ + if (strcmp(maxscale_IP, "127.0.0.1") == 0) + { + if (sudo) + { + sprintf(cmd, + "%s %s", + maxscale_access_sudo, ssh); + } + else + { + sprintf(cmd, + "%s", + ssh); + } + + } + else + { + if (sudo) + { + sprintf(cmd, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s '%s %s'", + maxscale_keyfile, maxscale_access_user, maxscale_IP, maxscale_access_sudo, ssh); + } + else + { + sprintf(cmd, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s '%s\'", + maxscale_keyfile, maxscale_access_user, maxscale_IP, ssh); + } + } +} + + +char* TestConnections::ssh_maxscale_output(bool sudo, const char* format, ...) +{ + va_list valist; + + va_start(valist, format); + int message_len = vsnprintf(NULL, 0, format, valist); + va_end(valist); + + if (message_len < 0) + { + return NULL; + } + + char *sys = (char*)malloc(message_len + 1); + + va_start(valist, format); + vsnprintf(sys, message_len + 1, format, valist); + va_end(valist); + + char *cmd = (char*)malloc(message_len + 1024); + generate_ssh_cmd(cmd, sys, sudo); +//tprintf("############ssh smd %s\n:", cmd); + FILE *output = popen(cmd, "r"); + if (output == NULL) + { + printf("Error opening ssh %s\n", strerror(errno)); + return NULL; + } + char buffer[1024]; + size_t rsize = sizeof(buffer); + char* result = (char*)calloc(rsize, sizeof(char)); + + while (fgets(buffer, sizeof(buffer), output)) + { + result = (char*)realloc(result, sizeof(buffer) + rsize); + rsize += sizeof(buffer); + strcat(result, buffer); + } + + free(sys); + free(cmd); + pclose(output); + + return result; +} + +int TestConnections::ssh_maxscale(bool sudo, const char* format, ...) +{ + va_list valist; + + va_start(valist, format); + int message_len = vsnprintf(NULL, 0, format, valist); + va_end(valist); + + if (message_len < 0) + { + return -1; + } + + char *sys = (char*)malloc(message_len + 1); + + va_start(valist, format); + vsnprintf(sys, message_len + 1, format, valist); + va_end(valist); + + char *cmd = (char*)malloc(message_len + 1024); + + if (strcmp(maxscale_IP, "127.0.0.1") == 0) + { + tprintf("starting bash\n"); + sprintf(cmd, "bash"); + } + else + { + sprintf(cmd, + "ssh -i %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s%s", + maxscale_keyfile, maxscale_access_user, maxscale_IP, verbose ? "" : " > /dev/null"); + } + int rc = 1; + FILE *in = popen(cmd, "w"); + + if (in) + { + if (sudo) + { + fprintf(in, "sudo su -\n"); + fprintf(in, "cd /home/%s\n", maxscale_access_user); + } + + fprintf(in, "%s\n", sys); + rc = pclose(in); + } + + free(sys); + free(cmd); + return rc; +} + +int TestConnections::copy_to_maxscale(const char* src, const char* dest) +{ + char sys[strlen(src) + strlen(dest) + 1024]; + if (strcmp(maxscale_IP, "127.0.0.1") == 0) + { + sprintf(sys, "cp %s %s", + src, dest); + } + else + { + + sprintf(sys, "scp -q -i %s -o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no -o LogLevel=quiet %s %s@%s:%s", + maxscale_keyfile, src, maxscale_access_user, maxscale_IP, dest); + } + return system(sys); +} + + +int TestConnections::copy_from_maxscale(char* src, char* dest) +{ + char sys[strlen(src) + strlen(dest) + 1024]; + if (strcmp(maxscale_IP, "127.0.0.1") == 0) + { + sprintf(sys, "cp %s %s", + src, dest); + } + else + { + sprintf(sys, "scp -i %s -o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no -o LogLevel=quiet %s@%s:%s %s", + maxscale_keyfile, maxscale_access_user, maxscale_IP, src, dest); + } + return system(sys); +} + +int TestConnections::create_connections(int conn_N, bool rwsplit_flag, bool master_flag, bool slave_flag, + bool galera_flag) +{ + int i; + int local_result = 0; + MYSQL * rwsplit_conn[conn_N]; + MYSQL * master_conn[conn_N]; + MYSQL * slave_conn[conn_N]; + MYSQL * galera_conn[conn_N]; + + + tprintf("Opening %d connections to each router\n", conn_N); + for (i = 0; i < conn_N; i++) + { + set_timeout(20); + + if (verbose) + { + tprintf("opening %d-connection: ", i + 1); + } + + if (rwsplit_flag) + { + if (verbose) + { + printf("RWSplit \t"); + } + + rwsplit_conn[i] = open_rwsplit_connection(); + if (!rwsplit_conn[i]) + { + local_result++; + tprintf("RWSplit connection failed\n"); + } + } + if (master_flag) + { + if (verbose) + { + printf("ReadConn master \t"); + } + + master_conn[i] = open_readconn_master_connection(); + if ( mysql_errno(master_conn[i]) != 0 ) + { + local_result++; + tprintf("ReadConn master connection failed, error: %s\n", mysql_error(master_conn[i]) ); + } + } + if (slave_flag) + { + if (verbose) + { + printf("ReadConn slave \t"); + } + + slave_conn[i] = open_readconn_slave_connection(); + if ( mysql_errno(slave_conn[i]) != 0 ) + { + local_result++; + tprintf("ReadConn slave connection failed, error: %s\n", mysql_error(slave_conn[i]) ); + } + } + if (galera_flag) + { + if (verbose) + { + printf("Galera \n"); + } + + galera_conn[i] = open_conn(4016, maxscale_IP, maxscale_user, maxscale_password, ssl); + if ( mysql_errno(galera_conn[i]) != 0) + { + local_result++; + tprintf("Galera connection failed, error: %s\n", mysql_error(galera_conn[i])); + } + } + } + for (i = 0; i < conn_N; i++) + { + set_timeout(20); + + if (verbose) + { + tprintf("Trying query against %d-connection: ", i + 1); + } + + if (rwsplit_flag) + { + if (verbose) + { + tprintf("RWSplit \t"); + } + local_result += execute_query(rwsplit_conn[i], "select 1;"); + } + if (master_flag) + { + if (verbose) + { + tprintf("ReadConn master \t"); + } + local_result += execute_query(master_conn[i], "select 1;"); + } + if (slave_flag) + { + if (verbose) + { + tprintf("ReadConn slave \t"); + } + local_result += execute_query(slave_conn[i], "select 1;"); + } + if (galera_flag) + { + if (verbose) + { + tprintf("Galera \n"); + } + local_result += execute_query(galera_conn[i], "select 1;"); + } + } + + //global_result += check_pers_conn(Test, pers_conn_expected); + tprintf("Closing all connections\n"); + for (i = 0; i < conn_N; i++) + { + set_timeout(20); + if (rwsplit_flag) + { + mysql_close(rwsplit_conn[i]); + } + if (master_flag) + { + mysql_close(master_conn[i]); + } + if (slave_flag) + { + mysql_close(slave_conn[i]); + } + if (galera_flag) + { + mysql_close(galera_conn[i]); + } + } + stop_timeout(); + + return local_result; +} + +int TestConnections::get_client_ip(char * ip) +{ + MYSQL * conn; + MYSQL_RES *res; + MYSQL_ROW row; + int ret = 1; + unsigned long long int num_fields; + //unsigned long long int row_i=0; + unsigned long long int rows; + unsigned long long int i; + unsigned int conn_num = 0; + + connect_rwsplit(); + if (execute_query(conn_rwsplit, (char *) "CREATE DATABASE IF NOT EXISTS db_to_check_clent_ip") != 0 ) + { + return ret; + } + close_rwsplit(); + conn = open_conn_db(rwsplit_port, maxscale_IP, (char *) "db_to_check_clent_ip", maxscale_user, + maxscale_password, ssl); + + if (conn != NULL) + { + if (mysql_query(conn, "show processlist;") != 0) + { + printf("Error: can't execute SQL-query: show processlist\n"); + printf("%s\n\n", mysql_error(conn)); + conn_num = 0; + } + else + { + res = mysql_store_result(conn); + if (res == NULL) + { + printf("Error: can't get the result description\n"); + conn_num = -1; + } + else + { + num_fields = mysql_num_fields(res); + rows = mysql_num_rows(res); + for (i = 0; i < rows; i++) + { + row = mysql_fetch_row(res); + if ( (row[2] != NULL ) && (row[3] != NULL) ) + { + if (strstr(row[3], "db_to_check_clent_ip") != NULL) + { + ret = 0; + strcpy(ip, row[2]); + } + } + } + } + mysql_free_result(res); + } + } + + mysql_close(conn); + return ret; +} + +int TestConnections::set_timeout(long int timeout_seconds) +{ + if (enable_timeouts) + { + timeout = timeout_seconds; + } + return 0; +} + +int TestConnections::set_log_copy_interval(long int interval_seconds) +{ + log_copy_to_go = interval_seconds; + log_copy_interval = interval_seconds; + return 0; +} + +int TestConnections::stop_timeout() +{ + timeout = 999999999; + return 0; +} + +int TestConnections::tprintf(const char *format, ...) +{ + timeval t2; + gettimeofday(&t2, NULL); + double elapsedTime = (t2.tv_sec - start_time.tv_sec); + elapsedTime += (double) (t2.tv_usec - start_time.tv_usec) / 1000000.0; + + struct tm tm_now; + localtime_r(&t2.tv_sec, &tm_now); + unsigned int msec = t2.tv_usec / 1000; + + printf("%02u:%02u:%02u.%03u %04f: ", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, msec, elapsedTime); + + va_list argp; + va_start(argp, format); + vprintf(format, argp); + va_end(argp); + + /** Add a newline if the message doesn't have one */ + if (format[strlen(format) - 1] != '\n') + { + printf("\n"); + } + + fflush(stdout); + fflush(stderr); +} + +void *timeout_thread( void *ptr ) +{ + TestConnections * Test = (TestConnections *) ptr; + struct timespec tim; + while (Test->timeout > 0) + { + tim.tv_sec = 1; + tim.tv_nsec = 0; + nanosleep(&tim, NULL); + Test->timeout--; + } + Test->tprintf("\n **** Timeout! *** \n"); + delete Test; + exit(250); +} + +void *log_copy_thread( void *ptr ) +{ + TestConnections * Test = (TestConnections *) ptr; + struct timespec tim; + while (true) + { + while (Test->log_copy_to_go > 0) + { + tim.tv_sec = 1; + tim.tv_nsec = 0; + nanosleep(&tim, NULL); + Test->log_copy_to_go--; + } + Test->log_copy_to_go = Test->log_copy_interval; + Test->tprintf("\n **** Copying all logs *** \n"); + Test->copy_all_logs_periodic(); + } +} + +int TestConnections::insert_select(int N) +{ + int result = 0; + + tprintf("Create t1\n"); + set_timeout(30); + create_t1(conn_rwsplit); + + tprintf("Insert data into t1\n"); + set_timeout(N * 16 + 30); + insert_into_t1(conn_rwsplit, N); + stop_timeout(); + repl->sync_slaves(); + + tprintf("SELECT: rwsplitter\n"); + set_timeout(30); + result += select_from_t1(conn_rwsplit, N); + + tprintf("SELECT: master\n"); + set_timeout(30); + result += select_from_t1(conn_master, N); + + tprintf("SELECT: slave\n"); + set_timeout(30); + result += select_from_t1(conn_slave, N); + + return result; +} + +int TestConnections::use_db(char * db) +{ + int local_result = 0; + char sql[100]; + + sprintf(sql, "USE %s;", db); + set_timeout(20); + tprintf("selecting DB '%s' for rwsplit\n", db); + local_result += execute_query(conn_rwsplit, sql); + tprintf("selecting DB '%s' for readconn master\n", db); + local_result += execute_query(conn_slave, sql); + tprintf("selecting DB '%s' for readconn slave\n", db); + local_result += execute_query(conn_master, sql); + for (int i = 0; i < repl->N; i++) + { + tprintf("selecting DB '%s' for direct connection to node %d\n", db, i); + local_result += execute_query(repl->nodes[i], sql); + } + return local_result; +} + +int TestConnections::check_t1_table(bool presence, char * db) +{ + const char *expected = presence ? "" : "NOT"; + const char *actual = presence ? "NOT" : ""; + int start_result = global_result; + + add_result(use_db(db), "use db failed\n"); + stop_timeout(); + repl->sync_slaves(); + + tprintf("Checking: table 't1' should %s be found in '%s' database\n", expected, db); + set_timeout(30); + int exists = check_if_t1_exists(conn_rwsplit); + + if (exists == presence) + { + tprintf("RWSplit: ok\n"); + } + else + { + add_result(1, "Table t1 is %s found in '%s' database using RWSplit\n", actual, db); + } + + set_timeout(30); + exists = check_if_t1_exists(conn_master); + + if (exists == presence) + { + tprintf("ReadConn master: ok\n"); + } + else + { + add_result(1, "Table t1 is %s found in '%s' database using Readconnrouter with router option master\n", + actual, db); + } + + set_timeout(30); + exists = check_if_t1_exists(conn_slave); + + if (exists == presence) + { + tprintf("ReadConn slave: ok\n"); + } + else + { + add_result(1, "Table t1 is %s found in '%s' database using Readconnrouter with router option slave\n", actual, + db); + } + + + for (int i = 0; i < repl->N; i++) + { + set_timeout(30); + exists = check_if_t1_exists(repl->nodes[i]); + if (exists == presence) + { + tprintf("Node %d: ok\n", i); + } + else + { + add_result(1, "Table t1 is %s found in '%s' database using direct connect to node %d\n", actual, db, i); + } + } + + stop_timeout(); + + return global_result - start_result; +} + +int TestConnections::try_query(MYSQL *conn, const char *format, ...) +{ + va_list valist; + + va_start(valist, format); + int message_len = vsnprintf(NULL, 0, format, valist); + va_end(valist); + + char sql[message_len + 1]; + + va_start(valist, format); + vsnprintf(sql, sizeof(sql), format, valist); + va_end(valist); + + int res = execute_query1(conn, sql, false); + add_result(res, "Query '%.*s%s' failed!\n", message_len < 100 ? message_len : 100, sql, message_len < 100 ? "" : "..."); + return res; +} + +int TestConnections::try_query_all(const char *sql) +{ + return try_query(conn_rwsplit, sql) + + try_query(conn_master, sql) + + try_query(conn_slave, sql); +} + +int TestConnections::find_master_maxadmin(Mariadb_nodes * nodes) +{ + bool found = false; + int master = -1; + + for (int i = 0; i < nodes->N; i++) + { + char show_server[256]; + char res[256]; + sprintf(show_server, "show server server%d", i + 1); + get_maxadmin_param(show_server, (char *) "Status", res); + + if (strstr(res, "Master")) + { + if (found) + { + master = -1; + } + else + { + master = i; + found = true; + } + } + } + + return master; +} + +int TestConnections::find_slave_maxadmin(Mariadb_nodes * nodes) +{ + int slave = -1; + + for (int i = 0; i < nodes->N; i++) + { + char show_server[256]; + char res[256]; + sprintf(show_server, "show server server%d", i + 1); + get_maxadmin_param(show_server, (char *) "Status", res); + + if (strstr(res, "Slave")) + { + slave = i; + } + } + + return slave; +} + +int TestConnections::execute_maxadmin_command(char * cmd) +{ + return ssh_maxscale(true, "maxadmin %s", cmd); +} +int TestConnections::execute_maxadmin_command_print(char * cmd) +{ + printf("%s\n", ssh_maxscale_output(true, "maxadmin %s", cmd)); + return 0; +} + +int TestConnections::check_maxadmin_param(const char *command, const char *param, const char *value) +{ + char result[1024]; + int rval = 1; + + if (get_maxadmin_param((char*)command, (char*)param, (char*)result) == 0) + { + char *end = strchr(result, '\0') - 1; + + while (isspace(*end)) + { + *end-- = '\0'; + } + + char *start = result; + + while (isspace(*start)) + { + start++; + } + + if (strcmp(start, value) == 0) + { + rval = 0; + } + else + { + printf("Expected %s, got %s\n", value, start); + } + } + + return rval; +} + +int TestConnections::get_maxadmin_param(char *command, char *param, char *result) +{ + char * buf; + + buf = ssh_maxscale_output(true, "maxadmin %s", command); + + //printf("%s\n", buf); + + char *x = strstr(buf, param); + + if (x == NULL) + { + return 1; + } + + x += strlen(param); + + // Skip any trailing parts of the parameter name + while (!isspace(*x)) + { + x++; + } + + // Trim leading whitespace + while (!isspace(*x)) + { + x++; + } + + char *end = strchr(x, '\n'); + + // Trim trailing whitespace + while (isspace(*end)) + { + *end-- = '\0'; + } + + strcpy(result, x); + + return 0; +} + +int TestConnections::list_dirs() +{ + for (int i = 0; i < repl->N; i++) + { + tprintf("ls on node %d\n", i); + repl->ssh_node(i, (char *) "ls -la /var/lib/mysql", true); + fflush(stdout); + } + tprintf("ls maxscale \n"); + ssh_maxscale(true, "ls -la /var/lib/maxscale/"); + fflush(stdout); + return 0; +} + +long unsigned TestConnections::get_maxscale_memsize() +{ + char * ps_out = ssh_maxscale_output(false, "ps -e -o pid,vsz,comm= | grep maxscale"); + long unsigned mem = 0; + pid_t pid; + sscanf(ps_out, "%d %lu", &pid, &mem); + return mem; +} + +void TestConnections::check_current_operations(int value) +{ + char value_str[512]; + sprintf(value_str, "%d", value); + + for (int i = 0; i < repl->N; i++) + { + char command[512]; + sprintf(command, "show server server%d", i + 1); + add_result(check_maxadmin_param(command, "Current no. of operations:", value_str), + "Current no. of operations is not %s", value_str); + } +} + +void TestConnections::check_current_connections(int value) +{ + char value_str[512]; + sprintf(value_str, "%d", value); + + for (int i = 0; i < repl->N; i++) + { + char command[512]; + sprintf(command, "show server server%d", i + 1); + add_result(check_maxadmin_param(command, "Current no. of conns:", value_str), + "Current no. of conns is not %s", value_str); + } +} + +int TestConnections::take_snapshot(char * snapshot_name) +{ + char str[4096]; + sprintf(str, "%s %s", take_snapshot_command, snapshot_name); + return system(str); +} + +int TestConnections::revert_snapshot(char * snapshot_name) +{ + char str[4096]; + sprintf(str, "%s %s", revert_snapshot_command, snapshot_name); + return system(str); +} + +bool TestConnections::test_bad_config(const char *config) +{ + char src[PATH_MAX]; + + process_template(config, "./"); + + // Set the timeout to prevent hangs with configurations that work + set_timeout(20); + + return ssh_maxscale(true, "cp maxscale.cnf /etc/maxscale.cnf; service maxscale stop; " + "maxscale -U maxscale -lstdout &> /dev/null && sleep 1 && pkill -9 maxscale") == 0; +} + +int TestConnections::connect_rwsplit() +{ + if (use_ipv6) + { + conn_rwsplit = open_conn(rwsplit_port, maxscale_IP6, maxscale_user, maxscale_password, ssl); + } + else + { + conn_rwsplit = open_conn(rwsplit_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + routers[0] = conn_rwsplit; + + int rc = 0; + int my_errno = mysql_errno(conn_rwsplit); + + if (my_errno) + { + if (verbose) + { + tprintf("Failed to connect to readwritesplit: %d, %s", my_errno, mysql_error(conn_rwsplit)); + } + rc = my_errno; + } + + return rc; +} + +int TestConnections::connect_readconn_master() +{ + if (use_ipv6) + { + conn_master = open_conn(readconn_master_port, maxscale_IP6, maxscale_user, maxscale_password, ssl); + } + else + { + conn_master = open_conn(readconn_master_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + routers[1] = conn_master; + + int rc = 0; + int my_errno = mysql_errno(conn_master); + + if (my_errno) + { + if (verbose) + { + tprintf("Failed to connect to readwritesplit: %d, %s", my_errno, mysql_error(conn_master)); + } + rc = my_errno; + } + + return rc; +} + +int TestConnections::connect_readconn_slave() +{ + if (use_ipv6) + { + conn_slave = open_conn(readconn_slave_port, maxscale_IP6, maxscale_user, maxscale_password, ssl); + } + else + { + conn_slave = open_conn(readconn_slave_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + routers[2] = conn_slave; + + int rc = 0; + int my_errno = mysql_errno(conn_slave); + + if (my_errno) + { + if (verbose) + { + tprintf("Failed to connect to readwritesplit: %d, %s", my_errno, mysql_error(conn_slave)); + } + rc = my_errno; + } + + return rc; +} + +char* TestConnections::maxscale_ip() const +{ + return use_ipv6 ? (char*)maxscale_IP6 : (char*)maxscale_IP; +} diff --git a/maxscale-system-test/testconnections.h b/maxscale-system-test/testconnections.h new file mode 100644 index 000000000..b5e171859 --- /dev/null +++ b/maxscale-system-test/testconnections.h @@ -0,0 +1,711 @@ +#ifndef TESTCONNECTIONS_H +#define TESTCONNECTIONS_H + +#include "mariadb_nodes.h" +#include "templates.h" +#include +#include +#include + +/** + * @brief Class contains references to Master/Slave and Galera test setups + * Test setup should consist of two setups: one Master/Slave and one Galera. + * + * Maxscale should be configured separatelly for every test. + * + * Test setup should be described by enviromental variables: + * - Maxscale_IP - IP adress of Maxscale machine + * - Maxscale_User - User name to access Maxscale services + * - Maxscale_Password - Password to access Maxscale services + * - Maxscale_sshkey - ssh key for Maxscale machine + * - maxscale_cnf - name of maxscale .cnf file (full) + * - KillVMCommand - Command to kill a node (should handle one parameter: IP address of virtual machine to kill) + * - StartVMCommand - Command to restart virtual machine (should handle one parameter: IP address of virtual machine to kill) + * - GetLogsCommand - Command to copy log files from node virtual machines (should handle one parameter: IP address of virtual machine to kill) + * - SysbenchDir - path to SysBench directory (sysbanch should be >= 0.5) + * - node_N - Number of Master/Slave setup nodes + * - node_NNN - IP address of node NNN (NNN - 3 digits node index starting from 000) + * - node_port_NNN - MariaDB port for node NNN + * - node_sshkey_NNN - ssh key to access node NNN (should be sutable for 'root' and 'ec2-user') + * - node_User - User name to access Master/Slav setup + * - node_Password - Password to access Master/Slave setup + * - galera_N, galera_NNN, galera_port_NNN, galera_sshkey_NNN, galera_User, galera_Password - same for Galera setup + * + */ +class TestConnections +{ +private: + /** Whether timeouts are enabled or not */ + bool enable_timeouts; +public: + /** + * @brief TestConnections constructor: reads environmental variables, copies MaxScale.cnf for MaxScale machine + * @param test_exec_name Path to currect executable + */ + TestConnections(int argc, char *argv[]); + + ~TestConnections(); + + /** + * @brief global_result Result of test, 0 if PASSED + */ + int global_result; + + /** + * @brief test_name Neme of the test + */ + char * test_name; + + /** + * @brief rwsplit_port RWSplit service port + */ + int rwsplit_port; + + /** + * @brief readconn_master_port ReadConnection in master mode service port + */ + int readconn_master_port; + + /** + * @brief readconn_slave_port ReadConnection in slave mode service port + */ + int readconn_slave_port; + + /** + * @brief binlog_port binlog router service port + */ + int binlog_port; + + /** + * @brief conn_rwsplit MYSQL connection struct to RWSplit service + */ + MYSQL *conn_rwsplit; + + /** + * @brief conn_master MYSQL connection struct to ReadConnection in master mode service + */ + MYSQL *conn_master; + + /** + * @brief conn_slave MYSQL connection struct to ReadConnection in slave mode service + */ + MYSQL *conn_slave; + + /** + * @brief routers Array of 3 MYSQL handlers which contains copies of conn_rwsplit, conn_master, conn_slave + */ + MYSQL *routers[3]; + + /** + * @brief ports of 3 int which contains copies of rwsplit_port, readconn_master_port, readconn_slave_port + */ + int ports[3]; + + /** + * @brief galera Mariadb_nodes object containing references to Galera setuo + */ + Mariadb_nodes * galera; + + /** + * @brief repl Mariadb_nodes object containing references to Master/Slave setuo + */ + Mariadb_nodes * repl; + + /** + * @brief Get MaxScale IP address + * + * @return The current IP address of MaxScale + */ + char* maxscale_ip() const; + + /** + * @brief Maxscale_IP Maxscale machine IP address + */ + char maxscale_IP[1024]; + + /** + * @brief Maxscale_IP6 Maxscale machine IP address (IPv6) + */ + char maxscale_IP6[1024]; + + /** + * @brief use_ipv6 If true IPv6 addresses will be used to connect Maxscale and backed + * Also IPv6 addresses go to maxscale.cnf + */ + bool use_ipv6; + + /** + * @brief maxscale_hostname Maxscale machine 'hostname' value + */ + char maxscale_hostname[1024]; + + /** + * @brief Maxscale_User User name to access Maxscale services + */ + char maxscale_user[256]; + + /** + * @brief Maxscale_Password Password to access Maxscale services + */ + char maxscale_password[256]; + + /** + * @brief maxadmin_Password Password to access Maxadmin tool + */ + char maxadmin_password[256]; + + /** + * @brief Maxscale_sshkey ssh key for Maxscale machine + */ + char maxscale_keyfile[4096]; + + /** + * @brief GetLogsCommand Command to copy log files from node virtual machines (should handle one parameter: IP address of virtual machine to kill) + */ + char get_logs_command[4096]; + + /** + * @brief make_snapshot_command Command line to create a snapshot of all VMs + */ + char take_snapshot_command[4096]; + + /** + * @brief revert_snapshot_command Command line to revert a snapshot of all VMs + */ + char revert_snapshot_command[4096]; + + /** + * @brief use_snapshots if TRUE every test is trying to revert snapshot before running the test + */ + bool use_snapshots; + + /** + * @brief SysbenchDir path to SysBench directory (sysbanch should be >= 0.5) + */ + char sysbench_dir[4096]; + + /** + * @brief maxscale_cnf full name of Maxscale configuration file + */ + char maxscale_cnf[4096]; + + /** + * @brief maxscale_log_dir name of log files directory + */ + char maxscale_log_dir[4096]; + + /** + * @brief maxscale_lbinog_dir name of binlog files (for binlog router) directory + */ + char maxscale_binlog_dir[4096]; + + /** + * @brief maxscale_access_user username to access test machines + */ + char maxscale_access_user[256]; + + /** + * @brief maxscale_access_homedir home directory of access_user + */ + char maxscale_access_homedir[256]; + + /** + * @brief maxscale_access_sudo empty if sudo is not needed or "sudo " if sudo is needed. + */ + char maxscale_access_sudo[64]; + + /** + * @brief copy_mariadb_logs copies MariaDB logs from backend + * @param repl Mariadb_nodes object + * @param prefix file name prefix + * @return 0 if success + */ + int copy_mariadb_logs(Mariadb_nodes *repl, char * prefix); + + /** + * @brief no_backend_log_copy if true logs from backends are not copied (needed if case of Aurora RDS backend or similar) + */ + bool no_backend_log_copy; + + /** + * @brief verbose if true more printing activated + */ + bool verbose; + + /** + * @brief smoke if true all tests are executed in quick mode + */ + bool smoke; + + /** + * @brief binlog_cmd_option index of mariadb start option + */ + int binlog_cmd_option; + + /** + * @brief ssl if true ssl will be used + */ + int ssl; + + /** + * @brief backend_ssl if true ssl configuratio for all servers will be added + */ + bool backend_ssl; + + /** + * @brief no_galera Do not check, restart and use Galera setup; all Galera tests will fail + */ + bool no_galera; + + /** + * @brief ssl_options string with ssl configuration for command line client + */ + char ssl_options[1024]; + + /** + * @brief threads Number of Maxscale threads + */ + int threads; + + /** + * @brief timeout seconds until test termination + */ + long int timeout; + + /** + * @brief log_copy_interval seconds between log copying + */ + long int log_copy_interval; + + /** + * @brief log_copy_interval seconds until next log copying + */ + long int log_copy_to_go; + + /** + * @brief timeout_thread_p pointer to timeout thread + */ + pthread_t timeout_thread_p; + + /** + * @brief log_copy_thread_p pointer to log copying thread + */ + pthread_t log_copy_thread_p; + + /** + * @brief start_time time when test was started (used by printf to print Timestamp) + */ + timeval start_time; + + /** Check whether all nodes are in a valid state */ + static void check_nodes(bool value); + + /** Skip initial start of MaxScale */ + static void skip_maxscale_start(bool value); + + /** Test requires a certain backend version */ + static void require_repl_version(const char *version); + static void require_galera_version(const char *version); + + /** + * @brief add_result adds result to global_result and prints error message if result is not 0 + * @param result 0 if step PASSED + * @param format ... message to pring if result is not 0 + */ + void add_result(int result, const char *format, ...); + + /** + * @brief ReadEnv Reads all Maxscale and Master/Slave and Galera setups info from environmental variables + * @return 0 in case of success + */ + int read_env(); + + /** + * @brief PrintIP Prints all Maxscale and Master/Slave and Galera setups info + * @return 0 + */ + int print_env(); + + /** + * @brief InitMaxscale Copies MaxSclae.cnf and start MaxScale + * @return 0 if case of success + */ + int init_maxscale(); + + /** + * @brief ConnectMaxscale Opens connections to RWSplit, ReadConn master and ReadConn slave Maxscale services + * Opens connections to RWSplit, ReadConn master and ReadConn slave Maxscale services + * Connections stored in conn_rwsplit, conn_master and conn_slave MYSQL structs + * @return 0 in case of success + */ + int connect_maxscale(); + + /** + * @brief CloseMaxscaleConn Closes connection that were opened by ConnectMaxscale() + * @return 0 + */ + int close_maxscale_connections(); + + /** + * @brief ConnectRWSplit Opens connections to RWSplit and store MYSQL struct in conn_rwsplit + * @return 0 in case of success + */ + int connect_rwsplit(); + + /** + * @brief ConnectReadMaster Opens connections to ReadConn master and store MYSQL struct in conn_master + * @return 0 in case of success + */ + int connect_readconn_master(); + + /** + * @brief ConnectReadSlave Opens connections to ReadConn slave and store MYSQL struct in conn_slave + * @return 0 in case of success + */ + int connect_readconn_slave(); + + /** + * @brief OpenRWSplitConn Opens new connections to RWSplit and returns MYSQL struct + * To close connection mysql_close() have to be called + * @return MYSQL struct + */ + MYSQL * open_rwsplit_connection() + { + return open_conn(rwsplit_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + + /** + * @brief OpenReadMasterConn Opens new connections to ReadConn master and returns MYSQL struct + * To close connection mysql_close() have to be called + * @return MYSQL struct + */ + MYSQL * open_readconn_master_connection() + { + return open_conn(readconn_master_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + + /** + * @brief OpenReadSlaveConn Opens new connections to ReadConn slave and returns MYSQL struct + * To close connection mysql_close() have to be called + * @return MYSQL struct + */ + MYSQL * open_readconn_slave_connection() + { + return open_conn(readconn_slave_port, maxscale_IP, maxscale_user, maxscale_password, ssl); + } + + /** + * @brief CloseRWSplit Closes RWplit connections stored in conn_rwsplit + */ + void close_rwsplit() + { + mysql_close(conn_rwsplit); + conn_rwsplit = NULL; + } + + /** + * @brief CloseReadMaster Closes ReadConn master connections stored in conn_master + */ + void close_readconn_master() + { + mysql_close(conn_master); + conn_master = NULL; + } + + /** + * @brief CloseReadSlave Closes ReadConn slave connections stored in conn_slave + */ + void close_readconn_slave() + { + mysql_close(conn_slave); + conn_slave = NULL; + } + + /** + * @brief restart_maxscale Issues 'service maxscale restart' command + */ + int restart_maxscale(); + + /** + * @brief start_maxscale Issues 'service maxscale start' command + */ + int start_maxscale(); + + /** + * @brief stop_maxscale Issues 'service maxscale stop' command + */ + int stop_maxscale(); + + /** + * @brief start_binlog configure first node as Master, Second as slave connected to Master and others as slave connected to MaxScale binlog router + * @return 0 in case of success + */ + int start_binlog(); + + /** + * @brief prepare_binlog clean up binlog directory, set proper access rights to it + * @return 0 + */ + int prepare_binlog(); + + /** + * @brief start_mm configure first node as Master for second, Second as Master for first + * @return 0 in case of success + */ + int start_mm(); + + /** + * @brief copy_all_logs Copies all MaxScale logs and (if happens) core to current workspace + */ + int copy_all_logs(); + + /** + * @brief copy_all_logs_periodic Copies all MaxScale logs and (if happens) core to current workspace and sends time stemp to log copying script + */ + int copy_all_logs_periodic(); + + /** + * @brief Generate command line to execute command on the Maxscale ode via ssh + * @param cmd result + * @param ssh command to execute + * @param sudo if true the command is executed with root privelegues + */ + void generate_ssh_cmd(char * cmd, char * ssh, bool sudo); + + /** + * @brief Execute a command via ssh on the MaxScale machine + * @param ssh ssh command to execute on the MaxScale machine + * @return Output of the command or NULL if the command failed to execute + */ + char* ssh_maxscale_output(bool sudo, const char* format, ...); + + /** + * @brief Execute a shell command on Maxscale + * @param sudo Use root + * @param format printf style format string + * @return 0 on success + */ + int ssh_maxscale(bool sudo, const char* format, ...); + + /** + * @brief Copy a local file to the MaxScale machine + * @param src Source file on the local filesystem + * @param dest Destination file on the MaxScale machine's file system + * @return exit code of the system command + */ + int copy_to_maxscale(const char* src, const char* dest); + + /** + * @brief Copy a remote file from the MaxScale machine + * @param src Source file on the remote filesystem + * @param dest Destination file on the local file system + * @return exit code of the system command + */ + int copy_from_maxscale(char* src, char* dest); + + /** + * @brief Test that connections to MaxScale are in the expected state + * @param rw_split State of the MaxScale connection to Readwritesplit. True for working connection, false for no connection. + * @param rc_master State of the MaxScale connection to Readconnroute Master. True for working connection, false for no connection. + * @param rc_slave State of the MaxScale connection to Readconnroute Slave. True for working connection, false for no connection. + * @return 0 if connections are in the expected state + */ + int test_maxscale_connections(bool rw_split, + bool rc_master, + bool rc_slave); + + /** + * @brief Create a number of connections to all services, run simple query, close all connections + * @param conn_N number of connections + * @param rwsplit_flag if true connections to RWSplit router will be created, if false - no connections to RWSplit + * @param master_flag if true connections to ReadConn master router will be created, if false - no connections to ReadConn master + * @param slave_flag if true connections to ReadConn slave router will be created, if false - no connections to ReadConn slave + * @param galera_flag if true connections to RWSplit router with Galera backend will be created, if false - no connections to RWSplit with Galera backend + * @return 0 in case of success + */ + int create_connections(int conn_N, bool rwsplit_flag, bool master_flag, bool slave_flag, bool galera_flag); + + /** + * Trying to get client IP address by connection to DB via RWSplit and execution 'show processlist' + * + * @param ip client IP address as it visible by Maxscale + * @return 0 in case of success + */ + int get_client_ip(char * ip); + + /** + * @brief set_timeout startes timeout thread which terminates test application after timeout_seconds + * @param timeout_seconds timeout time + * @return 0 if success + */ + int set_timeout(long int timeout_seconds); + + /** + * @brief set_log_copy_interval sets interval for periodic log copying + * @param interval_seconds interval in seconds + * @return 0 if success + */ + int set_log_copy_interval(long int interval_seconds); + + /** + * @brief stop_timeout stops timeout thread + * @return 0 + */ + int stop_timeout(); + + /** + * @brief printf adds timestam to printf + * @param __format + * @return + */ + int tprintf(const char *format, ...); + + /** + * @brief Creats t1 table, insert data into it and checks if data can be correctly read from all Maxscale services + * @param Test Pointer to TestConnections object that contains references to test setup + * @param N number of INSERTs; every next INSERT is longer 16 times in compare with previous one: for N=4 last INSERT is about 700kb long + * @return 0 in case of no error and all checks are ok + */ + int insert_select(int N); + + /** + * @brief Executes USE command for all Maxscale service and all Master/Slave backend nodes + * @param Test Pointer to TestConnections object that contains references to test setup + * @param db Name of DB in 'USE' command + * @return 0 in case of success + */ + int use_db(char * db); + + /** + * @brief Checks if table t1 exists in DB + * @param presence expected result + * @param db DB name + * @return 0 if (t1 table exists AND presence=TRUE) OR (t1 table does not exist AND presence=false) + */ + + int check_t1_table(bool presence, char * db); + + /** + * @brief CheckLogErr Reads error log and tried to search for given string + * @param err_msg Error message to search in the log + * @param expected TRUE if err_msg is expedted in the log, false if err_msg should NOT be in the log + * @return 0 if (err_msg is found AND expected is TRUE) OR (err_msg is NOT found in the log AND expected is false) + */ + void check_log_err(const char * err_msg, bool expected); + + /** + * @brief FindConnectedSlave Finds slave node which has connections from MaxScale + * @param Test TestConnections object which contains info about test setup + * @param global_result pointer to variable which is increased in case of error + * @return index of found slave node + */ + int find_connected_slave(int * global_result); + + /** + * @brief FindConnectedSlave1 same as FindConnectedSlave() but does not increase global_result + * @param Test TestConnections object which contains info about test setup + * @return index of found slave node + */ + int find_connected_slave1(); + + /** + * @brief CheckMaxscaleAlive Checks if MaxScale is alive + * Reads test setup info from enviromental variables and tries to connect to all Maxscale services to check if i is alive. + * Also 'show processlist' query is executed using all services + * @return 0 in case if success + */ + int check_maxscale_alive(); + + /** + * @brief try_query Executes SQL query and repors error + * @param conn MYSQL struct + * @param sql SQL string + * @return 0 if ok + */ + int try_query(MYSQL *conn, const char *sql, ...); + + /** + * @brief try_query_all Executes SQL query on all MaxScale connections + * @param sql SQL string + * @return 0 if ok + */ + int try_query_all(const char *sql); + + /** + * @brief find_master_maxadmin Tries to find node with 'Master' status using Maxadmin connand 'show server' + * @param nodes Mariadb_nodes object + * @return node index if one master found, -1 if no master found or several masters found + */ + int find_master_maxadmin(Mariadb_nodes * nodes); + int find_slave_maxadmin(Mariadb_nodes * nodes); + + int execute_maxadmin_command(char * cmd); + int execute_maxadmin_command_print(char * cmd); + int check_maxadmin_param(const char *command, const char *param, const char *value); + int get_maxadmin_param(char *command, char *param, char *result); + void check_current_operations(int value); + void check_current_connections(int value); + + /** + * @brief check_maxscale_processes Check if number of running Maxscale processes is equal to 'expected' + * @param expected expected number of Maxscale processes + * @return 0 if check is done + */ + int check_maxscale_processes(int expected); + + /** + * @brief list_dirs Execute 'ls' on binlog directory on all repl nodes and on Maxscale node + * @return 0 + */ + int list_dirs(); + + /** + * @brief get_maxscale_memsize Gets size of the memory consumed by Maxscale process + * @return memory size in kilobytes + */ + long unsigned get_maxscale_memsize(); + + /** + * @brief make_snapshot Makes a snapshot for all running VMs + * @param snapshot_name name of created snapshot + * @return 0 in case of success or mdbci error code in case of error + */ + int take_snapshot(char * snapshot_name); + + /** + * @brief revert_snapshot Revert snapshot for all running VMs + * @param snapshot_name name of snapshot to revert + * @return 0 in case of success or mdbci error code in case of error + */ + int revert_snapshot(char * snapshot_name); + + /** + * @brief Test a bad configuration + * @param config Name of the config template + * @return Always false, the test will time out if the loading is successful + */ + bool test_bad_config(const char *config); + + /** + * @brief Process a template configuration file + * + * @param dest Destination file name for actual configuration file + */ + void process_template(const char *src, const char *dest = "/etc/maxscale.cnf"); +}; + +/** + * @brief timeout_thread Thread which terminates test application after 'timeout' milliseconds + * @param ptr pointer to TestConnections object + * @return void + */ +void * timeout_thread(void *ptr ); + +/** + * @brief log_copy_thread Thread which peridically copies logs from Maxscale machine + * @param ptr pointer to TestConnections object + * @return void + */ +void * log_copy_thread(void *ptr ); + +#endif // TESTCONNECTIONS_H diff --git a/maxscale-system-test/transaction_test_wo_maxscale.cpp b/maxscale-system-test/transaction_test_wo_maxscale.cpp new file mode 100644 index 000000000..5fda8642c --- /dev/null +++ b/maxscale-system-test/transaction_test_wo_maxscale.cpp @@ -0,0 +1,129 @@ +/** + * @file transaction_test_wo_maxscale.cpp + */ + + +#include +#include "testconnections.h" +#include "maxadmin_operations.h" +#include "sql_t1.h" + +int check_sha1(TestConnections* Test) +{ + Test->tprintf("ls before FLUSH LOGS\n"); + + Test->tprintf("Master"); + Test->repl->ssh_node(0, (char *) "ls -la /var/lib/mysql/mar-bin.0000*", false); + + Test->tprintf("FLUSH LOGS\n"); + Test->try_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + Test->tprintf("Logs flushed\n"); + sleep(20); + Test->tprintf("ls after first FLUSH LOGS\n"); + + Test->tprintf("Master\n"); + Test->repl->ssh_node(0, (char *) "ls -la /var/lib/mysql/mar-bin.0000*", false); + + Test->tprintf("FLUSH LOGS\n"); + Test->try_query(Test->repl->nodes[0], (char *) "FLUSH LOGS"); + Test->tprintf("Logs flushed\n"); + fflush(stdout); + + sleep(19); + printf("ls before FLUSH LOGS\n"); + + printf("Master"); + Test->repl->ssh_node(0, (char *) "ls -la /var/lib/mysql/mar-bin.0000*", false); + + return Test->global_result; +} + +int start_transaction(TestConnections* Test) +{ + int global_result = 0; + Test->tprintf("Transaction test\n"); + Test->tprintf("Start transaction\n"); + global_result += execute_query(Test->repl->nodes[0], (char *) "START TRANSACTION"); + //global_result += execute_query(Test->repl->nodes[0], (char *) "SET autocommit = 0"); + Test->tprintf("INSERT data\n"); + global_result += execute_query(Test->repl->nodes[0], (char *) "INSERT INTO t1 VALUES(111, 10)"); + sleep(20); + return global_result; +} + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + + int i; + + for (int option = 0; option < 3; option++) + { + + Test->repl->connect(); + + create_t1(Test->repl->nodes[0]); + Test->add_result( insert_into_t1(Test->repl->nodes[0], 4), "INSER into t1 failed\n"); + Test->tprintf("Sleeping to let replication happen\n"); + sleep(30); + + for (i = 0; i < Test->repl->N; i++) + { + Test->tprintf("Checking data from node %d (%s)\n", i, Test->repl->IP[i]); + Test->add_result( select_from_t1(Test->repl->nodes[i], 4), "select form t1 wrong\n"); + } + + Test->tprintf("First transaction test (with ROLLBACK)\n"); + start_transaction(Test); + + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values\n"); + Test->add_result( execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "failed\n"); + + //printf("SELECT, checking inserted values from slave\n"); + //global_result += execute_query_check_one(Test->repl->nodes[2], (char *) "SELECT * FROM t1 WHERE fl=10", "111"); + + Test->add_result( check_sha1(Test), "sha1 wrong\n"); + + Test->tprintf("ROLLBACK\n"); + Test->try_query(Test->repl->nodes[0], (char *) "ROLLBACK"); + Test->tprintf("INSERT INTO t1 VALUES(112, 10)\n"); + Test->try_query(Test->repl->nodes[0], (char *) "INSERT INTO t1 VALUES(112, 10)"); + sleep(20); + + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values\n"); + Test->add_result( execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "112"), "failed\n"); + + Test->tprintf("SELECT * FROM t1 WHERE fl=10, checking inserted values from slave\n"); + Test->add_result( execute_query_check_one(Test->repl->nodes[2], (char *) "SELECT * FROM t1 WHERE fl=10", + "112"), "failed\n"); + Test->tprintf("DELETE FROM t1 WHERE fl=10\n"); + Test->try_query(Test->repl->nodes[0], (char *) "DELETE FROM t1 WHERE fl=10"); + Test->tprintf("Checking t1\n"); + Test->add_result( select_from_t1(Test->repl->nodes[0], 4), "failed\n"); + + Test->tprintf("Second transaction test (with COMMIT)\n"); + start_transaction(Test); + + Test->tprintf("COMMIT\n"); + Test->try_query(Test->repl->nodes[0], (char *) "COMMIT"); + + printf("SELECT, checking inserted values\n"); + Test->add_result( execute_query_check_one(Test->repl->nodes[0], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "failed\n"); + + Test->tprintf("SELECT, checking inserted values from slave\n"); + Test->add_result( execute_query_check_one(Test->repl->nodes[2], (char *) "SELECT * FROM t1 WHERE fl=10", + "111"), "failed\n"); + Test->tprintf("DELETE FROM t1 WHERE fl=10\n"); + Test->try_query(Test->repl->nodes[0], (char *) "DELETE FROM t1 WHERE fl=10"); + + Test->add_result( check_sha1(Test), "sha1 wrong\n"); + Test->repl->close_connections(); + } + + int rval = Test->global_result; + delete Test; + return rval; +} diff --git a/maxscale-system-test/user_cache.cpp b/maxscale-system-test/user_cache.cpp new file mode 100644 index 000000000..f35e775a7 --- /dev/null +++ b/maxscale-system-test/user_cache.cpp @@ -0,0 +1,72 @@ +/** + * @file user_cache.cpp Test user caching mechanism of MaxScale + * + * - Create 'testuser'@'%' user + * - Start up MaxScale with 'testuser' as the service user + * - Delete 'testuser'@'%' + * - Restart MaxScale + * - Check that queries through MaxScale are OK + */ + + +#include +#include "testconnections.h" + +int main(int argc, char *argv[]) +{ + TestConnections * Test = new TestConnections(argc, argv); + Test->stop_timeout(); + Test->stop_maxscale(); + + /** Create the test user and give required grants */ + Test->tprintf("Creating 'testuser'@'%'\n"); + Test->repl->connect(); + execute_query_silent(Test->repl->nodes[0], "CREATE USER 'testuser'@'%' IDENTIFIED BY 'testpasswd'"); + execute_query_silent(Test->repl->nodes[0], "GRANT SELECT ON mysql.user TO 'testuser'@'%'"); + execute_query_silent(Test->repl->nodes[0], "GRANT SELECT ON mysql.db TO 'testuser'@'%'"); + execute_query_silent(Test->repl->nodes[0], "GRANT SELECT ON mysql.tables_priv TO 'testuser'@'%'"); + execute_query_silent(Test->repl->nodes[0], "GRANT SHOW DATABASES ON *.* TO 'testuser'@'%'"); + + /** Wait for the user to replicate */ + Test->tprintf("Waiting for users to replicate\n"); + sleep(10); + + /** Test that MaxScale works and initialize the cache */ + Test->tprintf("Test that MaxScale works and initialize the cache\n"); + Test->start_maxscale(); + Test->connect_maxscale(); + Test->set_timeout(30); + Test->add_result(Test->try_query_all("SHOW DATABASES"), "Initial query without user cache should work\n"); + Test->stop_timeout(); + + /** Block all nodes */ + Test->tprintf("Blocking all nodes\n"); + for (int i = 0; i < Test->repl->N; i++) + { + Test->repl->block_node(i); + } + + /** Restart MaxScale and check that the user cache works */ + Test->tprintf("Restarting MaxScale\n"); + Test->restart_maxscale(); + sleep(5); + + Test->tprintf("Unblocking all nodes\n"); + Test->repl->unblock_all_nodes(); + sleep(5); + + Test->tprintf("Dropping 'testuser'@'%'\n"); + execute_query_silent(Test->repl->nodes[0], "DROP USER 'testuser'@'%'"); + sleep(5); + + Test->tprintf("Checking that the user cache works and queries are accepted\n"); + Test->set_timeout(30); + Test->connect_maxscale(); + Test->add_result(Test->try_query_all("SHOW DATABASES"), "Second query with user cache should work\n"); + Test->stop_timeout(); + + int rval = Test->global_result; + delete Test; + + return rval; +} diff --git a/maxscale-system-test/utf64.cnf b/maxscale-system-test/utf64.cnf new file mode 100644 index 000000000..7a1afab99 --- /dev/null +++ b/maxscale-system-test/utf64.cnf @@ -0,0 +1,3 @@ +[mysqld] +character_set_server=utf8mb4 +collation_server=utf8mb4_unicode_520_ci diff --git a/maxscale-system-test/utilities.cmake b/maxscale-system-test/utilities.cmake new file mode 100644 index 000000000..27deb9f16 --- /dev/null +++ b/maxscale-system-test/utilities.cmake @@ -0,0 +1,104 @@ + +# Helper function to add a configuration template +function(add_template name template) + set(CNF_TEMPLATES "${CNF_TEMPLATES}{\"${name}\",\"${template}\"}," CACHE INTERNAL "") +endfunction() + +# This functions adds a source file as an executable, links that file against +# the common test core and creates a test from it. The first parameter is the +# source file, the second is the name of the executable and the test and the +# last parameter is the template suffix of the test. The template should follow +# the following naming policy: `maxscale.cnf.template.