require 'spec_helper' require 'flydata/source_mysql/mysql_compatibility_check' module Flydata module SourceMysql describe MysqlCompatibilityCheck do let(:subject_object) { described_class.new(dp_hash, de_hash, options) } let(:dp_hash) { default_data_port } let(:de_hash) { default_mysql_cred } let(:options) { default_options } let(:default_data_port) do { "servers"=>["sample-test-site.com"] } end let(:default_mysql_cred) do { "host" => "test", "port" => 1234, "username" => "test", "password" => "password", "database" => "test_db" } end let(:default_options) { {} } let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).with("call mysql.rds_show_configuration;") allow(client).to receive(:close) end describe "#check" do subject { subject_object.check } it "calls all check methods" do %w(compatibility56_variable mysql_user_compat mysql_protocol_tcp_compat mysql_parameters_compat rds_master_status mysql_binlog_retention writing_permissions).each do |method_name| expect(subject_object).to receive("check_#{method_name}".to_sym) end subject end end describe "#check_mysql_user_compat" do subject { subject_object.check_mysql_user_compat } context "with all privileges in all databases" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT ALL PRIVILEGES ON *.* TO 'test'@'host"}]) end it do expect{subject}.to_not raise_error end end context "with missing privileges in one database" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT ALL PRIVILEGES ON 'mysql'.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT, RELOAD, REPLICATION CLIENT ON `test_db`.* TO 'test'@'host"}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /test_db': LOCK TABLES, REPLICATION SLAVE/) end end context "with all required privileges in between all and specific databases" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `test_db`.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `mysql`.* TO 'test'@'host"}]) end it do expect{subject}.to_not raise_error end end context "with missing privileges in each database" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `test_db`.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT, LOCK TABLES ON `mysql`.* TO 'test'@'host"}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': RELOAD\n.*test_db': RELOAD/) end end context "with all required privileges in all databases" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT SELECT, LOCK TABLES, RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}]) end it do expect{subject}.to_not raise_error end end context "with missing privileges in all databases" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT RELOAD, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'test'@'host"}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': SELECT, LOCK TABLES\n.*test_db': SELECT, LOCK TABLES/) end end context "with privileges for other databases only" do before do allow(client).to receive(:query).and_return([{"Grants for test"=>"GRANT REPLICATION CLIENT ON `test_db_01`.* TO 'test'@'host"}, {"Grants for test"=>"GRANT LOCK TABLES ON `test_db_02`.* TO 'test'@'host"}, {"Grants for test"=>"GRANT SELECT ON `text_db_03`.* TO 'test'@'host"}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /mysql': SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT\n.*test_db': SELECT, RELOAD, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT/) end end end describe "#check_mysql_binlog_retention" do subject { subject_object.check_mysql_binlog_retention } context "on on-premise mysql server" do before do allow(subject_object).to receive(:is_rds?).and_return(false) end context "where retention is below limit" do before do allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 1}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /expire_logs_days/) end end context "where retention is 0" do before do allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 0}]) end it do expect{subject}.to_not raise_error end end context "where retention is above limit" do before do allow(client).to receive(:query).and_return([{"expire_logs_days"=>11}]) allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 11}]) end it do expect{subject}.to_not raise_error end end end context "on RDS" do before do allow(subject_object).to receive(:is_rds?).and_return(true) end before do allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}]) end context "where retention period is nil" do before do allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>nil}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/) end end context "where retention period is too low" do before do allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>95}]) end it do expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/) end end context "where retention period is equal to the recommended limit" do before do allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>96}]) end it do expect{subject}.to_not raise_error end end context "where retention period is over recommended limit" do before do allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>97}]) end it do expect{subject}.to_not raise_error end end context "where user has no access to rds configuration" do before do allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_raise(Mysql2::Error, "execute command denied to user") end it do expect{subject}.to_not raise_error end it "logs the correct message" do message = "[WARNING]Cannot verify RDS retention period on current MySQL user account.\nTo see retention period, please run this on your RDS:\n $> call mysql.rds_show_configuration;\nPlease verify that the hours is not nil and is at least 96 hours\nTo set binlog retention hours, you can run this on your RDS:\n $> call mysql.rds_set_configuration('binlog retention hours', 96);\n" expect(subject_object).to receive(:log_warn_stderr).with(message) subject end end end end describe "#check_rds_master_status" do subject { subject_object.check_rds_master_status } let(:master_status) { [] } before do allow(client).to receive(:query).and_return(master_status) end context 'when host is rds' do before do de_hash['host'] = 'rdrss.xxyyzz.rds.amazonaws.com' end context "where backup retention period is not set" do let(:master_status) { [] } it { expect{subject}.to raise_error(FlydataCore::MysqlCompatibilityError, /Backup Retention Period/) } end context "where backup retention period is set" do let(:master_status) do [{ 'File' => 'mysql-bin-changelog.026292', 'Position' => '31300', 'Binlog_Do_DB' => '', 'Binlog_Ignore_DB' => '', 'Executed_Gtid_Set' => '', }] end it { expect{subject}.not_to raise_error } end end end end end end