PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/mysql/lib/mysql_service/node.rb

https://github.com/Abadasoft/vcap-services
Ruby | 665 lines | 573 code | 56 blank | 36 comment | 38 complexity | 590210bdf461aa2c5c28a99b4a938284 MD5 | raw file
  1. # Copyright (c) 2009-2011 VMware, Inc.
  2. require "erb"
  3. require "fileutils"
  4. require "logger"
  5. require "pp"
  6. require "uuidtools"
  7. require "mysql2"
  8. require "open3"
  9. require "thread"
  10. module VCAP
  11. module Services
  12. module Mysql
  13. class Node < VCAP::Services::Base::Node
  14. end
  15. end
  16. end
  17. end
  18. require "mysql_service/common"
  19. require "mysql_service/util"
  20. require "mysql_service/storage_quota"
  21. require "mysql_service/mysql_error"
  22. class VCAP::Services::Mysql::Node
  23. KEEP_ALIVE_INTERVAL = 15
  24. STORAGE_QUOTA_INTERVAL = 1
  25. include VCAP::Services::Mysql::Util
  26. include VCAP::Services::Mysql::Common
  27. include VCAP::Services::Mysql
  28. class ProvisionedService
  29. include DataMapper::Resource
  30. property :name, String, :key => true
  31. property :user, String, :required => true
  32. property :password, String, :required => true
  33. # property plan is deprecated. The instances in one node have same plan.
  34. property :plan, Integer, :required => true
  35. property :quota_exceeded, Boolean, :default => false
  36. end
  37. def initialize(options)
  38. super(options)
  39. @mysql_config = options[:mysql]
  40. @max_db_size = options[:max_db_size] * 1024 * 1024
  41. @max_long_query = options[:max_long_query]
  42. @max_long_tx = options[:max_long_tx]
  43. @max_user_conns = options[:max_user_conns] || 0
  44. @mysqldump_bin = options[:mysqldump_bin]
  45. @gzip_bin = options[:gzip_bin]
  46. @mysql_bin = options[:mysql_bin]
  47. @delete_user_lock = Mutex.new
  48. @base_dir = options[:base_dir]
  49. @local_db = options[:local_db]
  50. @long_queries_killed = 0
  51. @long_tx_killed = 0
  52. @statistics_lock = Mutex.new
  53. @provision_served = 0
  54. @binding_served = 0
  55. end
  56. def pre_send_announcement
  57. @pool = mysql_connect
  58. EM.add_periodic_timer(KEEP_ALIVE_INTERVAL) {mysql_keep_alive}
  59. EM.add_periodic_timer(@max_long_query.to_f/2) {kill_long_queries} if @max_long_query > 0
  60. if (@max_long_tx > 0) and (check_innodb_plugin)
  61. EM.add_periodic_timer(@max_long_tx.to_f/2) {kill_long_transaction}
  62. else
  63. @logger.info("long transaction killer is disabled.")
  64. end
  65. EM.add_periodic_timer(STORAGE_QUOTA_INTERVAL) {enforce_storage_quota}
  66. FileUtils.mkdir_p(@base_dir) if @base_dir
  67. DataMapper.setup(:default, @local_db)
  68. DataMapper::auto_upgrade!
  69. @queries_served = 0
  70. @qps_last_updated = 0
  71. # initialize qps counter
  72. get_qps
  73. @capacity_lock.synchronize do
  74. ProvisionedService.all.each do |provisionedservice|
  75. @capacity -= capacity_unit
  76. end
  77. end
  78. check_db_consistency
  79. end
  80. def all_instances_list
  81. ProvisionedService.all.map{|s| s.name}
  82. end
  83. def all_bindings_list
  84. res = []
  85. all_ins_users = ProvisionedService.all.map{|s| s.user}
  86. @pool.with_connection do |connection|
  87. connection.query('select DISTINCT user.user,db,password from user, db where user.user = db.user and length(user.user) > 0').each do |entry|
  88. # Filter out the instances handles
  89. res << gen_credential(entry["name"], entry["user"], entry["password"]) unless all_ins_users.include?(entry["user"])
  90. end
  91. end
  92. res
  93. end
  94. def announcement
  95. @capacity_lock.synchronize do
  96. { :available_capacity => @capacity }
  97. end
  98. end
  99. def check_db_consistency()
  100. db_list = []
  101. missing_accounts =[]
  102. @pool.with_connection do |connection|
  103. connection.query('select db, user from db').each(:as => :array){|row| db_list.push(row)}
  104. end
  105. ProvisionedService.all.each do |service|
  106. account = service.name, service.user
  107. missing_accounts << account unless db_list.include?(account)
  108. end
  109. missing_accounts.each do |account|
  110. db, user = account
  111. @logger.warn("Node database inconsistent!!! db:user <#{db}:#{user}> not in mysql.")
  112. end
  113. missing_accounts
  114. end
  115. # check whether mysql has required innodb plugin installed.
  116. def check_innodb_plugin()
  117. @pool.with_connection do |connection|
  118. res = connection.query("show tables from information_schema like 'INNODB_TRX'")
  119. return true if res.count > 0
  120. end
  121. end
  122. def mysql_connect
  123. host, user, password, port, socket = %w{host user pass port socket}.map { |opt| @mysql_config[opt] }
  124. 5.times do
  125. begin
  126. return ConnectionPool.new(:host => host, :username => user, :password => password, :database => "mysql", :port => port.to_i, :socket => socket, :logger => @logger)
  127. rescue Mysql2::Error => e
  128. @logger.error("MySQL connection attempt failed: [#{e.errno}] #{e.error}")
  129. sleep(5)
  130. end
  131. end
  132. @logger.fatal("MySQL connection unrecoverable")
  133. shutdown
  134. exit
  135. end
  136. def node_ready?()
  137. @pool && @pool.connected?
  138. end
  139. #keep connection alive, and check db liveness
  140. def mysql_keep_alive
  141. 5.times do
  142. begin
  143. @pool.keep_alive
  144. return
  145. rescue Mysql2::Error => e
  146. @logger.error("MySQL connection attempt failed: [#{e.errno}] #{e.error}")
  147. sleep(5)
  148. end
  149. end
  150. @logger.fatal("MySQL connection unrecoverable")
  151. shutdown
  152. exit
  153. end
  154. def kill_long_queries
  155. @pool.with_connection do |connection|
  156. process_list = connection.query("show processlist")
  157. process_list.each do |proc|
  158. thread_id, user, db, command, time, info, state = %w(Id User Db Command Time Info State).map{|o| proc[o]}
  159. if (time.to_i >= @max_long_query) and (command == 'Query') and (user != 'root') then
  160. connection.query("KILL QUERY #{thread_id}")
  161. @logger.warn("Killed long query: user:#{user} db:#{db} time:#{time} state: #{state} info:#{info}")
  162. @long_queries_killed += 1
  163. end
  164. end
  165. end
  166. rescue Mysql2::Error => e
  167. @logger.error("MySQL error: [#{e.errno}] #{e.error}")
  168. end
  169. def kill_long_transaction
  170. query_str = "SELECT * from ("+
  171. " SELECT trx_started, id, user, db, info, TIME_TO_SEC(TIMEDIFF(NOW() , trx_started )) as active_time" +
  172. " FROM information_schema.INNODB_TRX t inner join information_schema.PROCESSLIST p " +
  173. " ON t.trx_mysql_thread_id = p.ID " +
  174. " WHERE trx_state='RUNNING' and user!='root' " +
  175. ") as inner_table " +
  176. "WHERE inner_table.active_time > #{@max_long_tx}"
  177. @pool.with_connection do |connection|
  178. result = connection.query(query_str)
  179. result.each do |trx|
  180. trx_started, id, user, db, info, active_time = %w(trx_started id user db info active_time).map{|o| trx[o]}
  181. connection.query("KILL QUERY #{id}")
  182. @logger.warn("Kill long transaction: user:#{user} db:#{db} thread:#{id} info:#{info} active_time:#{active_time}")
  183. @long_tx_killed += 1
  184. end
  185. end
  186. rescue => e
  187. @logger.error("Error during kill long transaction: #{e}.")
  188. end
  189. def provision(plan, credential=nil)
  190. raise MysqlError.new(MysqlError::MYSQL_INVALID_PLAN, plan) unless plan == @plan
  191. provisioned_service = ProvisionedService.new
  192. provisioned_service.plan = 1
  193. begin
  194. if credential
  195. name, user, password = %w(name user password).map{|key| credential[key]}
  196. provisioned_service.name = name
  197. provisioned_service.user = user
  198. provisioned_service.password = password
  199. else
  200. # mysql database name should start with alphabet character
  201. provisioned_service.name = 'd' + UUIDTools::UUID.random_create.to_s.gsub(/-/, '')
  202. provisioned_service.user = 'u' + generate_credential
  203. provisioned_service.password = 'p' + generate_credential
  204. end
  205. raise "Could not create database" unless create_database(provisioned_service)
  206. if not provisioned_service.save
  207. @logger.error("Could not save entry: #{provisioned_service.errors.inspect}")
  208. raise MysqlError.new(MysqlError::MYSQL_LOCAL_DB_ERROR)
  209. end
  210. response = gen_credential(provisioned_service.name, provisioned_service.user, provisioned_service.password)
  211. @statistics_lock.synchronize do
  212. @provision_served += 1
  213. end
  214. return response
  215. rescue => e
  216. delete_database(provisioned_service)
  217. raise e
  218. end
  219. end
  220. def unprovision(name, credentials)
  221. return if name.nil?
  222. @logger.debug("Unprovision database:#{name} and its #{credentials.size} bindings")
  223. provisioned_service = ProvisionedService.get(name)
  224. raise MysqlError.new(MysqlError::MYSQL_CONFIG_NOT_FOUND, name) if provisioned_service.nil?
  225. # TODO: validate that database files are not lingering
  226. # Delete all bindings, ignore not_found error since we are unprovision
  227. begin
  228. credentials.each{ |credential| unbind(credential)} if credentials
  229. rescue =>e
  230. # ignore error, only log it
  231. @logger.warn("Error found in unbind operation:#{e}")
  232. end
  233. delete_database(provisioned_service)
  234. if not provisioned_service.destroy
  235. @logger.error("Could not delete service: #{provisioned_service.errors.inspect}")
  236. raise MysqlError.new(MysqError::MYSQL_LOCAL_DB_ERROR)
  237. end
  238. # the order is important, restore quota only when record is deleted from local db.
  239. @logger.debug("Successfully fulfilled unprovision request: #{name}")
  240. true
  241. end
  242. def bind(name, bind_opts, credential=nil)
  243. @logger.debug("Bind service for db:#{name}, bind_opts = #{bind_opts}")
  244. binding = nil
  245. begin
  246. service = ProvisionedService.get(name)
  247. raise MysqlError.new(MysqlError::MYSQL_CONFIG_NOT_FOUND, name) unless service
  248. # create new credential for binding
  249. binding = Hash.new
  250. if credential
  251. binding[:user] = credential["user"]
  252. binding[:password] = credential["password"]
  253. else
  254. binding[:user] = 'u' + generate_credential
  255. binding[:password] = 'p' + generate_credential
  256. end
  257. binding[:bind_opts] = bind_opts
  258. begin
  259. create_database_user(name, binding[:user], binding[:password])
  260. rescue Mysql2::Error => e
  261. raise "Could not create database user: [#{e.errno}] #{e.error}"
  262. end
  263. response = gen_credential(name, binding[:user], binding[:password])
  264. @logger.debug("Bind response: #{response.inspect}")
  265. @statistics_lock.synchronize do
  266. @binding_served += 1
  267. end
  268. return response
  269. rescue => e
  270. delete_database_user(binding[:user]) if binding
  271. raise e
  272. end
  273. end
  274. def unbind(credential)
  275. return if credential.nil?
  276. @logger.debug("Unbind service: #{credential.inspect}")
  277. name, user, bind_opts,passwd = %w(name user bind_opts password).map{|k| credential[k]}
  278. # Special case for 'ancient' instances that don't have new credentials for each Bind operation.
  279. # Never delete a user that was created as part of the initial provisioning process.
  280. @logger.debug("Begin check ancient credentials.")
  281. ProvisionedService.all(:name => name, :user => user).each {|record| @logger.info("Find unbind credential in local database: #{record.inspect}. Skip delete account."); return true}
  282. @logger.debug("Ancient credential not found.")
  283. # validate the existence of credential, in case we delete a normal account because of a malformed credential
  284. @pool.with_connection do |connection|
  285. res = connection.query("SELECT * from mysql.user WHERE user='#{user}'")
  286. raise MysqlError.new(MysqlError::MYSQL_CRED_NOT_FOUND, credential.inspect) if res.count() <= 0
  287. end
  288. delete_database_user(user)
  289. true
  290. end
  291. def create_database(provisioned_service)
  292. name, password, user = [:name, :password, :user].map { |field| provisioned_service.send(field) }
  293. begin
  294. start = Time.now
  295. @logger.debug("Creating: #{provisioned_service.inspect}")
  296. @pool.with_connection do |connection|
  297. connection.query("CREATE DATABASE #{name}")
  298. end
  299. create_database_user(name, user, password)
  300. @logger.debug("Done creating #{provisioned_service.inspect}. Took #{Time.now - start}.")
  301. return true
  302. rescue Mysql2::Error => e
  303. @logger.warn("Could not create database: [#{e.errno}] #{e.error}")
  304. return false
  305. end
  306. end
  307. def create_database_user(name, user, password)
  308. @logger.info("Creating credentials: #{user}/#{password} for database #{name}")
  309. @pool.with_connection do |connection|
  310. connection.query("GRANT ALL ON #{name}.* to #{user}@'%' IDENTIFIED BY '#{password}' WITH MAX_USER_CONNECTIONS #{@max_user_conns}")
  311. connection.query("GRANT ALL ON #{name}.* to #{user}@'localhost' IDENTIFIED BY '#{password}' WITH MAX_USER_CONNECTIONS #{@max_user_conns}")
  312. connection.query("FLUSH PRIVILEGES")
  313. end
  314. end
  315. def delete_database(provisioned_service)
  316. name, user = [:name, :user].map { |field| provisioned_service.send(field) }
  317. begin
  318. delete_database_user(user)
  319. @logger.info("Deleting database: #{name}")
  320. @pool.with_connection do |connection|
  321. connection.query("DROP DATABASE #{name}")
  322. end
  323. rescue Mysql2::Error => e
  324. @logger.error("Could not delete database: [#{e.errno}] #{e.error}")
  325. end
  326. end
  327. def delete_database_user(user)
  328. @logger.info("Delete user #{user}")
  329. @delete_user_lock.synchronize do
  330. ["%", "localhost"].each do |host|
  331. @pool.with_connection do |connection|
  332. res = connection.query("SELECT user from mysql.user where user='#{user}' and host='#{host}'")
  333. if res.count == 1
  334. connection.query("DROP USER #{user}@'#{host}'")
  335. else
  336. @logger.warn("Failure to delete non-existent user #{user}@'#{host}'")
  337. end
  338. end
  339. end
  340. kill_user_session(user)
  341. end
  342. rescue Mysql2::Error => e
  343. @logger.error("Could not delete user '#{user}': [#{e.errno}] #{e.error}")
  344. end
  345. def kill_user_session(user)
  346. @logger.info("Kill sessions of user: #{user}")
  347. begin
  348. @pool.with_connection do |connection|
  349. process_list = connection.query("show processlist")
  350. process_list.each do |proc|
  351. thread_id, user_, db, command, time, info = proc["Id"], proc["User"], proc["db"], proc["Command"], proc["Time"], proc["Info"]
  352. if user_ == user then
  353. connection.query("KILL #{thread_id}")
  354. @logger.info("Kill session: user:#{user} db:#{db}")
  355. end
  356. end
  357. end
  358. rescue Mysql2::Error => e
  359. # kill session failed error, only log it.
  360. @logger.error("Could not kill user session.:[#{e.errno}] #{e.error}")
  361. end
  362. end
  363. # restore a given instance using backup file.
  364. def restore(name, backup_path)
  365. @logger.debug("Restore db #{name} using backup at #{backup_path}")
  366. service = ProvisionedService.get(name)
  367. raise MysqlError.new(MysqlError::MYSQL_CONFIG_NOT_FOUND, name) unless service
  368. @pool.with_connection do |connection|
  369. # revoke write and lock privileges to prevent race with drop database.
  370. connection.query("UPDATE db SET insert_priv='N', create_priv='N',
  371. update_priv='N', lock_tables_priv='N' WHERE Db='#{name}'")
  372. connection.query("FLUSH PRIVILEGES")
  373. kill_database_session(connection, name)
  374. # mysql can't delete tables that not in dump file.
  375. # recreate the database to prevent leave unclean tables after restore.
  376. connection.query("DROP DATABASE #{name}")
  377. connection.query("CREATE DATABASE #{name}")
  378. # restore privileges.
  379. connection.query("UPDATE db SET insert_priv='Y', create_priv='Y',
  380. update_priv='Y', lock_tables_priv='Y' WHERE Db='#{name}'")
  381. connection.query("FLUSH PRIVILEGES")
  382. end
  383. host, user, pass, port, socket = %w{host user pass port socket}.map { |opt| @mysql_config[opt] }
  384. path = File.join(backup_path, "#{name}.sql.gz")
  385. cmd = "#{@gzip_bin} -dc #{path}|" +
  386. "#{@mysql_bin} -h #{host} -P #{port} -u #{user} --password=#{pass}"
  387. cmd += " -S #{socket}" unless socket.nil?
  388. cmd += " #{name}"
  389. o, e, s = exe_cmd(cmd)
  390. if s.exitstatus == 0
  391. return true
  392. else
  393. return nil
  394. end
  395. rescue => e
  396. @logger.error("Error during restore #{e}")
  397. nil
  398. end
  399. # Disable all credentials and kill user sessions
  400. def disable_instance(prov_cred, binding_creds)
  401. @logger.debug("Disable instance #{prov_cred["name"]} request.")
  402. binding_creds << prov_cred
  403. binding_creds.each do |cred|
  404. unbind(cred)
  405. end
  406. true
  407. rescue => e
  408. @logger.warn(e)
  409. nil
  410. end
  411. # Dump db content into given path
  412. def dump_instance(prov_cred, binding_creds, dump_file_path)
  413. @logger.debug("Dump instance #{prov_cred["name"]} request.")
  414. name = prov_cred["name"]
  415. host, user, password, port, socket = %w{host user pass port socket}.map { |opt| @mysql_config[opt] }
  416. dump_file = File.join(dump_file_path, "#{name}.sql")
  417. @logger.info("Dump instance #{name} content to #{dump_file}")
  418. cmd = "#{@mysqldump_bin} -h #{host} -u #{user} --password=#{password} --single-transaction #{'-S '+socket if socket} #{name} > #{dump_file}"
  419. o, e, s = exe_cmd(cmd)
  420. if s.exitstatus == 0
  421. return true
  422. else
  423. return nil
  424. end
  425. rescue => e
  426. @logger.warn(e)
  427. nil
  428. end
  429. # Provision and import dump files
  430. # Refer to #dump_instance
  431. def import_instance(prov_cred, binding_creds_hash, dump_file_path, plan)
  432. @logger.debug("Import instance #{prov_cred["name"]} request.")
  433. @logger.info("Provision an instance with plan: #{plan} using data from #{prov_cred.inspect}")
  434. provision(plan, prov_cred)
  435. name = prov_cred["name"]
  436. import_file = File.join(dump_file_path, "#{name}.sql")
  437. host, user, password, port, socket = %w{host user pass port socket}.map { |opt| @mysql_config[opt] }
  438. @logger.info("Import data from #{import_file} to database #{name}")
  439. cmd = "#{@mysql_bin} --host=#{host} --user=#{user} --password=#{password} #{'-S '+socket if socket} #{name} < #{import_file}"
  440. o, e, s = exe_cmd(cmd)
  441. if s.exitstatus == 0
  442. return true
  443. else
  444. return nil
  445. end
  446. rescue => e
  447. @logger.warn(e)
  448. nil
  449. end
  450. # Re-bind credentials
  451. # Refer to #disable_instance
  452. def enable_instance(prov_cred, binding_creds_hash)
  453. @logger.debug("Enable instance #{prov_cred["name"]} request.")
  454. name = prov_cred["name"]
  455. prov_cred = bind(name, nil, prov_cred)
  456. binding_creds_hash.each_value do |v|
  457. cred = v["credentials"]
  458. binding_opts = v["binding_options"]
  459. v["credentials"] = bind(name, binding_opts, cred)
  460. end
  461. return [prov_cred, binding_creds_hash]
  462. rescue => e
  463. @logger.warn(e)
  464. []
  465. end
  466. # shell CMD wrapper and logger
  467. def exe_cmd(cmd, stdin=nil)
  468. @logger.debug("Execute shell cmd:[#{cmd}]")
  469. o, e, s = Open3.capture3(cmd, :stdin_data => stdin)
  470. if s.exitstatus == 0
  471. @logger.info("Execute cmd:[#{cmd}] successd.")
  472. else
  473. @logger.error("Execute cmd:[#{cmd}] failed. Stdin:[#{stdin}], stdout: [#{o}], stderr:[#{e}]")
  474. end
  475. return [o, e, s]
  476. end
  477. def varz_details()
  478. varz = {}
  479. # how many queries served since startup
  480. varz[:queries_since_startup] = get_queries_status
  481. # queries per second
  482. varz[:queries_per_second] = get_qps
  483. # disk usage per instance
  484. status = get_instance_status
  485. varz[:database_status] = status
  486. varz[:max_capacity] = @max_capacity
  487. varz[:available_capacity] = @capacity
  488. # how many long queries and long txs are killed.
  489. varz[:long_queries_killed] = @long_queries_killed
  490. varz[:long_transactions_killed] = @long_tx_killed
  491. # how many provision/binding operations since startup.
  492. @statistics_lock.synchronize do
  493. varz[:provision_served] = @provision_served
  494. varz[:binding_served] = @binding_served
  495. end
  496. varz
  497. rescue => e
  498. @logger.error("Error during generate varz: #{e}")
  499. {}
  500. end
  501. def healthz_details()
  502. healthz = {:self => "ok"}
  503. begin
  504. @pool.with_connection do |connection|
  505. connection.query("SHOW DATABASES")
  506. end
  507. rescue => e
  508. @logger.error("Error get database list: #{e}")
  509. healthz[:self] = "fail"
  510. return healthz
  511. end
  512. begin
  513. ProvisionedService.all.each do |instance|
  514. healthz[instance.name.to_sym] = get_instance_healthz(instance)
  515. end
  516. rescue => e
  517. @logger.error("Error get instance list: #{e}")
  518. healthz[:self] = "fail"
  519. end
  520. healthz
  521. end
  522. def get_instance_healthz(instance)
  523. res = "ok"
  524. host, port, socket, root_user, root_pass = %w{host port socket user pass}.map { |opt| @mysql_config[opt] }
  525. begin
  526. begin
  527. conn = Mysql2::Client.new(:host => host, :username => instance.user, :password => instance.password, :database =>instance.name, :port => port.to_i, :socket => socket)
  528. rescue Mysql2::Error => e
  529. # user had modified instance password, fallback to root account
  530. conn = Mysql2::Client.new(:host => host, :username => root_user, :password => root_pass, :database =>instance.name, :port => port.to_i, :socket => socket)
  531. res = "password-modified"
  532. end
  533. conn.query("SHOW TABLES")
  534. rescue => e
  535. @logger.warn("Error get tables of #{instance.name}: #{e}")
  536. res = "fail"
  537. ensure
  538. begin
  539. conn.close if conn
  540. rescue => e1
  541. #ignore
  542. end
  543. end
  544. res
  545. end
  546. def get_queries_status()
  547. @pool.with_connection do |connection|
  548. result = connection.query("SHOW STATUS WHERE Variable_name ='QUERIES'")
  549. return 0 if result.count == 0
  550. return result.to_a[0]["Value"].to_i
  551. end
  552. end
  553. def get_qps()
  554. queries = get_queries_status
  555. ts = Time.now.to_i
  556. delta_t = (ts - @qps_last_updated).to_f
  557. qps = (queries - @queries_served)/delta_t
  558. @queries_served = queries
  559. @qps_last_updated = ts
  560. qps
  561. end
  562. def get_instance_status()
  563. all_dbs = []
  564. @pool.with_connection do |connection|
  565. result = connection.query('show databases')
  566. result.each {|db| all_dbs << db["Database"]}
  567. system_dbs = ['mysql', 'information_schema']
  568. sizes = connection.query(
  569. 'SELECT table_schema "name",
  570. sum( data_length + index_length ) "size"
  571. FROM information_schema.TABLES
  572. GROUP BY table_schema')
  573. result = []
  574. db_with_tables = []
  575. sizes.each do |i|
  576. db = {}
  577. name, size = i["name"], i["size"]
  578. next if system_dbs.include?(name)
  579. db_with_tables << name
  580. db[:name] = name
  581. db[:size] = size.to_i
  582. db[:max_size] = @max_db_size
  583. result << db
  584. end
  585. # handle empty db without table
  586. (all_dbs - db_with_tables - system_dbs ).each do |db|
  587. result << {:name => db, :size => 0, :max_size => @max_db_size}
  588. end
  589. return result
  590. end
  591. end
  592. def gen_credential(name, user, passwd)
  593. response = {
  594. "name" => name,
  595. "hostname" => @local_ip,
  596. "host" => @local_ip,
  597. "port" => @mysql_config['port'],
  598. "user" => user,
  599. "username" => user,
  600. "password" => passwd,
  601. }
  602. end
  603. def is_percona_server?()
  604. @pool.with_connection do |connection|
  605. res = connection.query("show variables where variable_name like 'version_comment'")
  606. return res.count > 0 && res.to_a[0]["Value"] =~ /percona/i
  607. end
  608. end
  609. end