manageiq-postgres_ha_admin 2.0.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2ea7e89193add948ecd827491eae6c7e055c20a6
4
- data.tar.gz: d416a3b5fd696ca582e13e360b6ea00af762aa58
3
+ metadata.gz: 00a1ceacb4bfb538a4cdeb7b7b1bc9d60e7ca986
4
+ data.tar.gz: 7ace66668500095929cb06389dd98ed77ec9c2ce
5
5
  SHA512:
6
- metadata.gz: a76ffe81cb4c0f218a5374a333f2458da83c59ff14cbc285ba9787c58e4362568c7c0303d794f587098ae3aa4b6fe144fc44c6ef1270e990e1581e5c83521394
7
- data.tar.gz: 2aaeb1824891fad95440f473ccd864a048538ddcdf37832a9d3b8c492d29d81e1a1d353ee070aca4861136a5cea8a2152bd26bf90c94c989df29e2f1bd97e088
6
+ metadata.gz: ee0f5eb9fb4d357e9d413166c9251cb67191bc7110d2b275eb6d22055a923a1e85f9313d023f13ff156690a82d76aa05fca79dc60d257b79c246db80d0338fa8
7
+ data.tar.gz: 6444e2e66980ee5c3f498ee292e47f9cf2a88f4169106690fa41a0706f14b1a8e802a2020d06180577920025cd7212dc3801e2a37bb577f0eee35ab165352a85
@@ -5,6 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [3.0.0] - 2018-09-05
9
+
10
+ ### Added
11
+ - Allow users of the gem to specify a logger object [#7](https://github.com/ManageIQ/manageiq-postgres_ha_admin/pull/7)
12
+
13
+ ### Changed
14
+ - Make sources of database connection info pluggable [#8](https://github.com/ManageIQ/manageiq-postgres_ha_admin/pull/8)
15
+ - Improve FailoverDatabases/ServerStore class [#9](https://github.com/ManageIQ/manageiq-postgres_ha_admin/pull/9)
16
+ - Make failover monitor generic [#10](https://github.com/ManageIQ/manageiq-postgres_ha_admin/pull/10)
17
+
8
18
  ## [2.0.0] - 2018-08-01
9
19
 
10
20
  ### Added
@@ -14,5 +24,6 @@ This project adheres to [Semantic Versioning](http://semver.org/).
14
24
  ### Changed
15
25
  - Make changes for upgrading repmgr to version 4 [#4](https://github.com/ManageIQ/manageiq-postgres_ha_admin/pull/4)
16
26
 
17
- [Unreleased]: https://github.com/ManageIQ/manageiq-postgres_ha_admin/compare/v2.0.0...master
27
+ [Unreleased]: https://github.com/ManageIQ/manageiq-postgres_ha_admin/compare/v3.0.0...master
28
+ [3.0.0]: https://github.com/ManageIQ/manageiq-postgres_ha_admin/compare/v2.0.0...v3.0.0
18
29
  [2.0.0]: https://github.com/ManageIQ/manageiq-postgres_ha_admin/compare/v1.0.0...v2.0.0
@@ -1,4 +1,23 @@
1
1
  require 'manageiq/postgres_ha_admin/version'
2
- require 'manageiq/postgres_ha_admin/database_yml'
3
- require 'manageiq/postgres_ha_admin/failover_databases'
2
+
3
+ require 'manageiq/postgres_ha_admin/logging'
4
+ require 'manageiq/postgres_ha_admin/null_logger'
5
+
6
+ require 'manageiq/postgres_ha_admin/server_store'
4
7
  require 'manageiq/postgres_ha_admin/failover_monitor'
8
+
9
+ require 'manageiq/postgres_ha_admin/config_handler'
10
+ require 'manageiq/postgres_ha_admin/config_handler/rails_config_handler'
11
+ require 'manageiq/postgres_ha_admin/config_handler/pglogical_config_handler'
12
+
13
+ module ManageIQ
14
+ module PostgresHaAdmin
15
+ class << self
16
+ attr_writer :logger
17
+ end
18
+
19
+ def self.logger
20
+ @logger ||= NullLogger.new
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module ManageIQ
2
+ module PostgresHaAdmin
3
+ class ConfigHandler
4
+ def name
5
+ "Config Handler"
6
+ end
7
+
8
+ def read
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def write(_conninfo)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def before_failover(&block)
17
+ raise ArgumentError, "A block is required to set the before failover callback" unless block_given?
18
+ @before_failover_cb = block
19
+ end
20
+
21
+ def after_failover(&block)
22
+ raise ArgumentError, "A block is required to set the after failover callback" unless block_given?
23
+ @after_failover_cb = block
24
+ end
25
+
26
+ def do_before_failover
27
+ @before_failover_cb&.call
28
+ end
29
+
30
+ def do_after_failover(new_primary_conn_info)
31
+ @after_failover_cb&.call(new_primary_conn_info)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,36 @@
1
+ require 'pg'
2
+ require 'pg/dsn_parser'
3
+
4
+ module ManageIQ
5
+ module PostgresHaAdmin
6
+ class PglogicalConfigHandler < ConfigHandler
7
+ attr_reader :subscription, :conn_info
8
+
9
+ def initialize(options = {})
10
+ @subscription = options[:subscription]
11
+ @conn_info = options[:conn_info]
12
+ end
13
+
14
+ def name
15
+ "pglogical subscription #{subscription} Config Handler"
16
+ end
17
+
18
+ def read
19
+ conn = PG::Connection.open(@conn_info)
20
+ dsn = conn.exec_params(<<~SQL, [@subscription]).first["if_dsn"]
21
+ SELECT if_dsn
22
+ FROM pglogical.subscription s
23
+ JOIN pglogical.node_interface i
24
+ ON s.sub_origin_if = i.if_id
25
+ WHERE s.sub_name = $1
26
+ SQL
27
+ PG::DSNParser.new.parse(dsn)
28
+ end
29
+
30
+ def write(_params)
31
+ # Nothing to do here as the expectation is that the user will
32
+ # remove and re-add the subscription in the after failover callback
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,29 +4,33 @@ require 'fileutils'
4
4
 
5
5
  module ManageIQ
6
6
  module PostgresHaAdmin
7
- class DatabaseYml
8
- attr_reader :db_yml_file, :environment
7
+ class RailsConfigHandler < ConfigHandler
8
+ attr_reader :environment, :file_path
9
9
 
10
- def initialize(db_yml_file, environment)
11
- @db_yml_file = db_yml_file
12
- @environment = environment
10
+ def initialize(options = {})
11
+ @file_path = options[:file_path]
12
+ @environment = options[:environment]
13
13
  end
14
14
 
15
- def pg_params_from_database_yml
16
- rails_params_to_pg(YAML.load_file(db_yml_file)[environment])
15
+ def name
16
+ "Rails #{environment} Config Handler"
17
17
  end
18
18
 
19
- def update_database_yml(params)
20
- db_yml = YAML.load_file(db_yml_file)
19
+ def read
20
+ rails_params_to_pg(YAML.load_file(file_path)[environment])
21
+ end
22
+
23
+ def write(params)
24
+ db_yml = YAML.load_file(file_path)
21
25
  db_yml[environment].merge!(pg_parameters_to_rails(params))
22
26
  remove_empty(db_yml[environment])
23
27
 
24
- new_name = "#{db_yml_file}_#{Time.current.strftime("%d-%B-%Y_%H.%M.%S")}"
25
- FileUtils.copy(db_yml_file, new_name)
28
+ new_name = "#{file_path}_#{Time.current.strftime("%d-%B-%Y_%H.%M.%S")}"
29
+ FileUtils.copy(file_path, new_name)
26
30
  begin
27
- File.write(db_yml_file, db_yml.to_yaml)
31
+ File.write(file_path, db_yml.to_yaml)
28
32
  rescue
29
- FileUtils.mv(new_name, db_yml_file)
33
+ FileUtils.mv(new_name, file_path)
30
34
  raise
31
35
  end
32
36
  new_name
@@ -5,48 +5,46 @@ require 'linux_admin'
5
5
  module ManageIQ
6
6
  module PostgresHaAdmin
7
7
  class FailoverMonitor
8
- RAILS_ROOT = [
9
- Pathname.new("/var/www/miq/vmdb"),
10
- Pathname.new(File.expand_path(File.join(__dir__, "../..")))
11
- ].detect { |f| File.exist?(f) }
8
+ include Logging
9
+
12
10
  FAILOVER_ATTEMPTS = 10
13
11
  DB_CHECK_FREQUENCY = 300
14
12
  FAILOVER_CHECK_FREQUENCY = 60
15
- LOG_FILE = '/var/www/miq/vmdb/log/ha_admin.log'.freeze
16
13
  attr_accessor :failover_attempts, :db_check_frequency, :failover_check_frequency
14
+ attr_reader :config_handlers
17
15
 
18
- def initialize(db_yml_file: '/var/www/miq/vmdb/config/database.yml',
19
- failover_yml_file: '/var/www/miq/vmdb/config/failover_databases.yml',
20
- ha_admin_yml_file: '/var/www/miq/vmdb/config/ha_admin.yml',
21
- logger: nil,
22
- environment: 'production')
23
- if logger.respond_to?(:error, :info)
24
- @logger = logger
25
- else
26
- @logger = Logger.new(LOG_FILE)
27
- @logger.level = Logger::INFO
28
- end
29
- @database_yml = DatabaseYml.new(db_yml_file, environment)
30
- @failover_db = FailoverDatabases.new(failover_yml_file, @logger)
31
- initialize_settings(ha_admin_yml_file)
16
+ def initialize(config_path = "")
17
+ initialize_settings(config_path)
18
+ @config_handlers = []
32
19
  end
33
20
 
34
- def monitor
35
- connection = pg_connection(@database_yml.pg_params_from_database_yml)
36
- if connection
37
- @failover_db.update_failover_yml(connection)
38
- connection.finish
39
- return
40
- end
41
-
42
- @logger.error("Primary Database is not available. EVM server stop initiated. Starting to execute failover...")
43
- stop_evmserverd
21
+ def add_handler(handler)
22
+ @config_handlers << [handler, ServerStore.new]
23
+ end
44
24
 
45
- if execute_failover
46
- start_evmserverd
47
- raise_failover_event
48
- else
49
- @logger.error("Failover failed")
25
+ def monitor
26
+ config_handlers.each do |handler, server_store|
27
+ begin
28
+ connection = pg_connection(handler.read)
29
+ if connection
30
+ server_store.update_servers(connection)
31
+ connection.finish
32
+ next
33
+ end
34
+
35
+ logger.error("Primary Database is not available for #{handler.name}. Starting to execute failover...")
36
+ handler.do_before_failover
37
+
38
+ new_conn_info = execute_failover(handler, server_store)
39
+ if new_conn_info
40
+ handler.do_after_failover(new_conn_info)
41
+ else
42
+ logger.error("Failover failed")
43
+ end
44
+ rescue => e
45
+ logger.error("Received #{e.class} error while monitoring #{handler.name}: #{e.message}")
46
+ logger.error(e.backtrace)
47
+ end
50
48
  end
51
49
  end
52
50
 
@@ -55,59 +53,52 @@ module PostgresHaAdmin
55
53
  begin
56
54
  monitor
57
55
  rescue => err
58
- @logger.error("#{err.class}: #{err}")
59
- @logger.error(err.backtrace.join("\n"))
56
+ logger.error("#{err.class}: #{err}")
57
+ logger.error(err.backtrace.join("\n"))
60
58
  end
61
59
  sleep(db_check_frequency)
62
60
  end
63
61
  end
64
62
 
65
- def active_servers_conninfo
66
- servers = @failover_db.active_databases_conninfo_hash
67
- db_yml_params = @database_yml.pg_params_from_database_yml
68
- servers.map! { |info| db_yml_params.merge(info) }
69
- end
70
-
71
- def raise_failover_event
72
- require "awesome_spawn"
73
- AwesomeSpawn.run("rake evm:raise_server_event",
74
- :chdir => RAILS_ROOT,
75
- :params => ["--", {:event => "db_failover_executed"}])
63
+ def active_servers_conninfo(handler, server_store)
64
+ servers = server_store.connection_info_list
65
+ current_params = handler.read
66
+ servers.map! { |info| current_params.merge(info) }
76
67
  end
77
68
 
78
69
  private
79
70
 
80
71
  def initialize_settings(ha_admin_yml_file)
72
+ ha_admin_yml = {}
81
73
  begin
82
- ha_admin_yml = YAML.load_file(ha_admin_yml_file)
74
+ ha_admin_yml = YAML.load_file(ha_admin_yml_file) if File.exist?(ha_admin_yml_file)
83
75
  rescue SystemCallError, IOError => err
84
- ha_admin_yml = {}
85
- @logger.error("#{err.class}: #{err}")
86
- @logger.info("File not loaded: #{ha_admin_yml_file}. Default settings for failover will be used.")
76
+ logger.error("#{err.class}: #{err}")
77
+ logger.info("File not loaded: #{ha_admin_yml_file}. Default settings for failover will be used.")
87
78
  end
88
79
  @failover_attempts = ha_admin_yml['failover_attempts'] || FAILOVER_ATTEMPTS
89
80
  @db_check_frequency = ha_admin_yml['db_check_frequency'] || DB_CHECK_FREQUENCY
90
81
  @failover_check_frequency = ha_admin_yml['failover_check_frequency'] || FAILOVER_CHECK_FREQUENCY
91
- @logger.info("FAILOVER_ATTEMPTS=#{@failover_attempts} DB_CHECK_FREQUENCY=#{@db_check_frequency} FAILOVER_CHECK_FREQUENCY=#{@failover_check_frequency}")
82
+ logger.info("FAILOVER_ATTEMPTS=#{@failover_attempts} DB_CHECK_FREQUENCY=#{@db_check_frequency} FAILOVER_CHECK_FREQUENCY=#{@failover_check_frequency}")
92
83
  end
93
84
 
94
- def execute_failover
85
+ def execute_failover(handler, server_store)
95
86
  failover_attempts.times do
96
- with_each_standby_connection do |connection, params|
87
+ with_each_standby_connection(handler, server_store) do |connection, params|
97
88
  next if database_in_recovery?(connection)
98
- next unless @failover_db.host_is_repmgr_primary?(params[:host], connection)
99
- @logger.info("Failing over to server using conninfo: #{params.reject { |k, _v| k == :password }}")
100
- @failover_db.update_failover_yml(connection)
101
- @database_yml.update_database_yml(params)
102
- return true
89
+ next unless server_store.host_is_primary?(params[:host], connection)
90
+ logger.info("Failing over to server using conninfo: #{params.reject { |k, _v| k == :password }}")
91
+ server_store.update_servers(connection)
92
+ handler.write(params)
93
+ return params
103
94
  end
104
95
  sleep(failover_check_frequency)
105
96
  end
106
97
  false
107
98
  end
108
99
 
109
- def with_each_standby_connection
110
- active_servers_conninfo.each do |params|
100
+ def with_each_standby_connection(handler, server_store)
101
+ active_servers_conninfo(handler, server_store).each do |params|
111
102
  connection = pg_connection(params)
112
103
  next if connection.nil?
113
104
  begin
@@ -121,19 +112,10 @@ module PostgresHaAdmin
121
112
  def pg_connection(params)
122
113
  PG::Connection.open(params)
123
114
  rescue PG::Error => e
124
- @logger.error("Failed to establish PG connection: #{e.message}")
115
+ logger.error("Failed to establish PG connection: #{e.message}")
125
116
  nil
126
117
  end
127
118
 
128
- def start_evmserverd
129
- LinuxAdmin::Service.new("evmserverd").restart
130
- @logger.info("Starting EVM server from failover monitor")
131
- end
132
-
133
- def stop_evmserverd
134
- LinuxAdmin::Service.new("evmserverd").stop
135
- end
136
-
137
119
  # Checks if postgres database is in recovery mode
138
120
  #
139
121
  # @param pg_connection [PG::Connection] established pg connection
@@ -0,0 +1,9 @@
1
+ module ManageIQ
2
+ module PostgresHaAdmin
3
+ module Logging
4
+ def logger
5
+ ManageIQ::PostgresHaAdmin.logger
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ module ManageIQ
4
+ module PostgresHaAdmin
5
+ class NullLogger < Logger
6
+ def initialize(*_args)
7
+ end
8
+
9
+ def add(*_args, &_block)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -4,38 +4,38 @@ require 'pg/dsn_parser'
4
4
 
5
5
  module ManageIQ
6
6
  module PostgresHaAdmin
7
- class FailoverDatabases
7
+ class ServerStore
8
+ include Logging
9
+
8
10
  TABLE_NAME = "repmgr.nodes".freeze
9
11
 
10
- attr_reader :yml_file
12
+ attr_reader :servers
11
13
 
12
- def initialize(yml_file, logger)
13
- @yml_file = yml_file
14
- @logger = logger
14
+ def initialize
15
+ @servers = []
15
16
  end
16
17
 
17
- def active_databases_conninfo_hash
18
+ def connection_info_list
18
19
  valid_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
19
- active_databases.map! do |db_info|
20
+ servers.map! do |db_info|
20
21
  db_info.keep_if { |k, _v| valid_keys.include?(k) }
21
22
  end
22
23
  end
23
24
 
24
- def active_databases
25
- all_databases.select { |record| record[:active] }
26
- end
27
-
28
- def update_failover_yml(connection)
29
- arr_yml = query_repmgr(connection)
30
- File.write(yml_file, arr_yml.to_yaml) unless arr_yml.empty?
25
+ def update_servers(connection)
26
+ new_servers = query_repmgr(connection)
27
+ if servers_changed?(new_servers)
28
+ logger.info("Updating servers cache to #{new_servers}")
29
+ @servers = new_servers
30
+ end
31
31
  rescue IOError => err
32
- @logger.error("#{err.class}: #{err}")
33
- @logger.error(err.backtrace.join("\n"))
32
+ logger.error("#{err.class}: #{err}")
33
+ logger.error(err.backtrace.join("\n"))
34
34
  end
35
35
 
36
- def host_is_repmgr_primary?(host, connection)
36
+ def host_is_primary?(host, connection)
37
37
  query_repmgr(connection).each do |record|
38
- if record[:host] == host && entry_is_active_master?(record)
38
+ if record[:host] == host && record[:type] == 'primary'
39
39
  return true
40
40
  end
41
41
  end
@@ -44,10 +44,14 @@ module PostgresHaAdmin
44
44
 
45
45
  private
46
46
 
47
+ def servers_changed?(new_servers)
48
+ ((servers - new_servers) + (new_servers - servers)).any?
49
+ end
50
+
47
51
  def query_repmgr(connection)
48
52
  return [] unless table_exists?(connection, TABLE_NAME)
49
53
  result = []
50
- db_result = connection.exec("SELECT type, conninfo, active FROM #{TABLE_NAME}")
54
+ db_result = connection.exec("SELECT type, conninfo, active FROM #{TABLE_NAME} WHERE active")
51
55
  db_result.map_types!(PG::BasicTypeMapForResults.new(connection)).each do |record|
52
56
  dsn = PG::DSNParser.parse(record.delete("conninfo"))
53
57
  result << record.symbolize_keys.merge(dsn)
@@ -55,20 +59,11 @@ module PostgresHaAdmin
55
59
  db_result.clear
56
60
  result
57
61
  rescue PG::Error => err
58
- @logger.error("#{err.class}: #{err}")
59
- @logger.error(err.backtrace.join("\n"))
62
+ logger.error("#{err.class}: #{err}")
63
+ logger.error(err.backtrace.join("\n"))
60
64
  result
61
65
  end
62
66
 
63
- def entry_is_active_master?(record)
64
- record[:type] == 'primary' && record[:active]
65
- end
66
-
67
- def all_databases
68
- return [] unless File.exist?(yml_file)
69
- YAML.load_file(yml_file)
70
- end
71
-
72
67
  def table_exists?(connection, table_name)
73
68
  result = connection.exec("SELECT to_regclass('#{table_name}')").first
74
69
  !result['to_regclass'].nil?
@@ -1,5 +1,5 @@
1
1
  module ManageIQ
2
2
  module PostgresHaAdmin
3
- VERSION = '2.0.0'.freeze
3
+ VERSION = '3.0.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manageiq-postgres_ha_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ManageIQ Developers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-01 00:00:00.000000000 Z
11
+ date: 2018-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_spawn
@@ -152,8 +152,7 @@ dependencies:
152
152
  version: '0'
153
153
  description: ManageIQ Postgres H.A. Admin
154
154
  email:
155
- executables:
156
- - postgres_ha_admin
155
+ executables: []
157
156
  extensions: []
158
157
  extra_rdoc_files: []
159
158
  files:
@@ -170,11 +169,14 @@ files:
170
169
  - LICENSE.txt
171
170
  - README.md
172
171
  - Rakefile
173
- - bin/postgres_ha_admin
174
172
  - lib/manageiq-postgres_ha_admin.rb
175
- - lib/manageiq/postgres_ha_admin/database_yml.rb
176
- - lib/manageiq/postgres_ha_admin/failover_databases.rb
173
+ - lib/manageiq/postgres_ha_admin/config_handler.rb
174
+ - lib/manageiq/postgres_ha_admin/config_handler/pglogical_config_handler.rb
175
+ - lib/manageiq/postgres_ha_admin/config_handler/rails_config_handler.rb
177
176
  - lib/manageiq/postgres_ha_admin/failover_monitor.rb
177
+ - lib/manageiq/postgres_ha_admin/logging.rb
178
+ - lib/manageiq/postgres_ha_admin/null_logger.rb
179
+ - lib/manageiq/postgres_ha_admin/server_store.rb
178
180
  - lib/manageiq/postgres_ha_admin/version.rb
179
181
  - manageiq-postgres_ha_admin.gemspec
180
182
  homepage: https://github.com/ManageIQ/manageiq-postgres_ha_admin
@@ -1,7 +0,0 @@
1
- #!/bin/env ruby
2
-
3
- require 'bundler/setup'
4
- require 'manageiq-postgres_ha_admin'
5
-
6
- monitor = ManageIQ::PostgresHaAdmin::FailoverMonitor.new
7
- monitor.monitor_loop