require 'spec_helper' require 'flydata/compatibility_check' module Flydata describe AgentCompatibilityCheck do let(:default_data_port) do { "servers"=>["sample-test-site.com"] } end describe "#check" do subject { AgentCompatibilityCheck.new("servers" => ['localhost']) } context "runs all check methods" do context "when all ports are accessible" do let(:sock) { double('sock') } before do allow(TCPSocket).to receive(:new).and_return(sock) allow(sock).to receive(:close) end it "does nothing" do subject.check end end context "when a port access fails" do before do allow(TCPSocket).to receive(:new).and_raise(Errno::ETIMEDOUT) end it do expect{subject.check_outgoing_ports}.to raise_error(FlydataCore::AgentCompatibilityError, /ports/) end end end end end describe MysqlCompatibilityCheck do 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 describe "#check_mysql_user_compat" do let(:client) { double('client') } subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:close) end 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.check_mysql_user_compat}.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.check_mysql_user_compat}.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.check_mysql_user_compat}.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.check_mysql_user_compat}.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.check_mysql_user_compat}.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.check_mysql_user_compat}.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.check_mysql_user_compat}.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 context "on on-premise mysql server" do subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) } context "where retention is below limit" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 1}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(false) end it do expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /expire_logs_days/) end end context "where retention is 0" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).and_return([{"Variable_name" => "expire_logs_days", "Value" => 0}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(false) end it do expect{subject.check_mysql_binlog_retention}.to_not raise_error end end context "where retention is above limit" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) 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}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(false) end it do expect{subject.check_mysql_binlog_retention}.to_not raise_error end end end context "on RDS" do subject { MysqlCompatibilityCheck.new(default_data_port, default_mysql_cred) } context "where retention period is nil" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}]) allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>nil}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(true) end it do expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/) end end context "where retention period is too low" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}]) allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>4}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(true) end it do expect{subject.check_mysql_binlog_retention}.to raise_error(FlydataCore::MysqlCompatibilityError, /rds_set_config/) end end context "where retention period is over recommended limit" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}]) allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_return([{"name"=>"binlog retention hours", "value"=>120}]) allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(true) end it do expect{subject.check_mysql_binlog_retention}.to_not raise_error end end context "where user has no access to rds configuration" do let(:client) { double('client') } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).with("SELECT @@expire_logs_days").and_return([{"@@expire_logs_days"=>0}]) allow(client).to receive(:query).with("call mysql.rds_show_configuration;").and_raise(Mysql2::Error, "execute command denied to user") allow(client).to receive(:close) allow(subject).to receive(:is_rds?).and_return(true) end it do expect{subject.check_mysql_binlog_retention}.to_not raise_error end end end end describe "#check_mysql_table_types" do let(:test_data_entry) do { "host" => "test", "port" => 1234, "username" => "test", "password" => "password", "database" => "test_db", "tables"=>["normal_table", "engine_table", "view_table"] } end let(:normal_table) { {"table_name"=>"normal_table", "table_type"=>"BASE TABLE", "engine"=>"InnoDB"} } let(:engine_table) { {"table_name"=>"engine_table", "table_type"=>"BASE TABLE", "engine"=>"MEMORY"} } let(:blackhole_table) { {"table_name"=>"blackhole_table", "table_type"=>"BASE TABLE", "engine"=>"BLACKHOLE"} } let(:view) { {"table_name"=>"view_table", "table_type"=>"VIEW", "engine"=>nil} } let(:client) { double('client') } let(:subject_object) { MysqlCompatibilityCheck.new(default_data_port,test_data_entry, {}) } let(:error) { FlydataCore::MysqlCompatibilityError } let(:base_error_msg) { "FlyData does not support VIEW and MEMORY,BLACKHOLE STORAGE ENGINE table. Remove following tables from data entry: %s" } subject { subject_object.check_mysql_table_types } before do allow(Mysql2::Client).to receive(:new).and_return(client) allow(client).to receive(:query).and_return(table_list) allow(client).to receive(:escape).and_return("aaa") allow(client).to receive(:close) end context "where data entry has VIEW and MEMORY engine table" do let(:error_msg) { base_error_msg % engine_table['table_name'] + ', ' + view['table_name'] } let(:table_list) { [ engine_table, view ] } it { expect{subject}.to raise_error(error, /#{error_msg}/) } end context "where data entry has MEMORY engine table" do let(:error_msg) { base_error_msg % engine_table['table_name'] } let(:table_list) { [ engine_table ] } it { expect{subject}.to raise_error(error, /#{error_msg}/) } end context "where data entry has BLACKHOLE engine table" do let(:error_msg) { base_error_msg % blackhole_table['table_name'] } let(:table_list) { [ blackhole_table ] } it { expect{subject}.to raise_error(error, /#{error_msg}/) } end context "where data entry has the VIEW" do let(:error_msg) { base_error_msg % view['table_name'] } let(:table_list) { [ view ] } it { expect{subject}.to raise_error(error, /#{error_msg}/) } end context "where data entry does not have either VIEW and ENGINE table" do let(:table_list) { [ normal_table ] } it { expect{subject}.to_not raise_error } end end end end