Class: ManageIQ::ApplianceConsole::PostgresAdmin
- Inherits:
-
Object
- Object
- ManageIQ::ApplianceConsole::PostgresAdmin
- Defined in:
- lib/manageiq/appliance_console/postgres_admin.rb
Constant Summary collapse
- PG_DUMP_MAGIC =
"PGDMP".force_encoding(Encoding::BINARY).freeze
- BASE_BACKUP_MAGIC =
just the first 2 bits of gzip magic
"\037\213".force_encoding(Encoding::BINARY).freeze
- GC_DEFAULTS =
{ :analyze => false, :full => false, :verbose => false, :table => nil, :dbname => nil, :username => nil, :reindex => false }
- GC_AGGRESSIVE_DEFAULTS =
{ :analyze => true, :full => true, :verbose => false, :table => nil, :dbname => nil, :username => nil, :reindex => true }
- PG_DUMP_MULTI_VALUE_ARGS =
rubocop:disable Style/SymbolArray
[ :t, :table, :T, :exclude_table, :"exclude-table", :exclude_table_data, :"exclude-table-data", :n, :schema, :N, :exclude_schema, :"exclude-schema" ].freeze
Class Method Summary collapse
- .backup(opts) ⇒ Object
- .backup_pg_compress(opts) ⇒ Object
- .backup_pg_dump(opts) ⇒ Object
- .base_backup_file?(file) ⇒ Boolean
- .data_directory ⇒ Object
- .database_disk_filesystem ⇒ Object
- .database_size(opts) ⇒ Object
- .gc(options = {}) ⇒ Object
- .group ⇒ Object
- .initialized? ⇒ Boolean
- .local_server_received_standby_signal? ⇒ Boolean
- .local_server_status ⇒ Object
- .logical_volume_name ⇒ Object
- .logical_volume_path ⇒ Object
- .mount_point ⇒ Object
- .package_name ⇒ Object
- .pg_dump_file?(file) ⇒ Boolean
- .prep_data_directory ⇒ Object
- .recreate_db(opts) ⇒ Object
- .reindex(opts) ⇒ Object
- .restore(opts) ⇒ Object
- .restore_pg_basebackup(file) ⇒ Object
- .restore_pg_dump(opts) ⇒ Object
- .run_command(cmd_str, opts, args) ⇒ Object
- .run_command_with_logging(cmd_str, opts, params = {}) ⇒ Object (also: runcmd_with_logging)
- .service_name ⇒ Object
- .service_running? ⇒ Boolean
- .template_directory ⇒ Object
-
.user ⇒ Object
Unprivileged user to run postgresql.
- .vacuum(opts) ⇒ Object
- .volume_group_name ⇒ Object
- .with_pg_connection(db_opts = {:user => user, :dbname => user}) ⇒ Object
Class Method Details
.backup(opts) ⇒ Object
110 111 112 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 110 def self.backup(opts) backup_pg_compress(opts) end |
.backup_pg_compress(opts) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 163 def self.backup_pg_compress(opts) opts = opts.dup # discard dbname as pg_basebackup does not connect to a specific database opts.delete(:dbname) path = Pathname.new(opts.delete(:local_file)) FileUtils.mkdir_p(path.dirname) # Build commandline from AwesomeSpawn args = {:z => nil, :format => "t", :wal_method => "fetch", :pgdata => "-"} cmd = AwesomeSpawn.build_command_line("pg_basebackup", combine_command_args(opts, args)) logger.info("MIQ(#{name}.#{__method__}) Running command... #{cmd}") # Run command in a separate thread read, write = IO.pipe error_path = Dir::Tmpname.create("") { |tmpname| tmpname } process_thread = Process.detach(Kernel.spawn(pg_env(opts), cmd, :out => write, :err => error_path)) stream_reader = Thread.new { IO.copy_stream(read, path) } # Copy output to path write.close # Wait for them to finish process_status = process_thread.value stream_reader.join read.close handle_error(cmd, process_status.exitstatus, error_path) path.to_s end |
.backup_pg_dump(opts) ⇒ Object
151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 151 def self.backup_pg_dump(opts) opts = opts.dup dbname = opts.delete(:dbname) args = combine_command_args(opts, :format => "c", :file => opts[:local_file], nil => dbname) args = handle_multi_value_pg_dump_args!(opts, args) FileUtils.mkdir_p(File.dirname(opts.fetch(:local_file, ""))) run_command_with_logging("pg_dump", opts, args) opts[:local_file] end |
.base_backup_file?(file) ⇒ Boolean
106 107 108 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 106 def self.base_backup_file?(file) File.open(file, "rb") { |f| f.readpartial(2) } == BASE_BACKUP_MAGIC end |
.data_directory ⇒ Object
9 10 11 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 9 def self.data_directory Pathname.new(ENV.fetch("APPLIANCE_PG_DATA")) end |
.database_disk_filesystem ⇒ Object
46 47 48 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 46 def self.database_disk_filesystem "xfs".freeze end |
.database_size(opts) ⇒ Object
88 89 90 91 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 88 def self.database_size(opts) result = run_command("psql", opts, :command => "SELECT pg_database_size('#{opts[:dbname]}');") result.match(/^\s+([0-9]+)\n/)[1].to_i end |
.gc(options = {}) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 242 def self.gc( = {}) = ([:aggressive] ? GC_AGGRESSIVE_DEFAULTS : GC_DEFAULTS).merge() result = vacuum() logger.info("MIQ(#{name}.#{__method__}) Output... #{result}") if result.to_s.length > 0 if [:reindex] result = reindex() logger.info("MIQ(#{name}.#{__method__}) Output... #{result}") if result.to_s.length > 0 end end |
.group ⇒ Object
34 35 36 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 34 def self.group user end |
.initialized? ⇒ Boolean
57 58 59 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 57 def self.initialized? !Dir[data_directory.join("*")].empty? end |
.local_server_received_standby_signal? ⇒ Boolean
65 66 67 68 69 70 71 72 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 65 def self.local_server_received_standby_signal? # Beginning with PostgreSQL 12, replication configuration has been integrated into the main PostgreSQL configuraton system and the conventional recovery.conf file is no longer valid. # see: https://repmgr.org/docs/current/release-5.0.html # https://www.2ndquadrant.com/en/blog/replication-configuration-changes-in-postgresql-12/ # "standby.signal" – indicates the server should start up as a hot standby # If a standby is promoted, "standby.signal" is removed entirely (and not renamed as was the case with "recovery.conf", which became "recovery.done"). data_directory.join("standby.signal").exist? || data_directory.join("recovery.conf").exist? end |
.local_server_status ⇒ Object
74 75 76 77 78 79 80 81 82 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 74 def self.local_server_status if service_running? "running (#{local_server_received_standby_signal? ? "standby" : "primary"})" elsif initialized? "initialized and stopped" else "not initialized" end end |
.logical_volume_name ⇒ Object
38 39 40 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 38 def self.logical_volume_name "lv_pg".freeze end |
.logical_volume_path ⇒ Object
84 85 86 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 84 def self.logical_volume_path Pathname.new("/dev").join(volume_group_name, logical_volume_name) end |
.mount_point ⇒ Object
13 14 15 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 13 def self.mount_point Pathname.new(ENV.fetch("APPLIANCE_PG_MOUNT_POINT")) end |
.package_name ⇒ Object
25 26 27 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 25 def self.package_name ENV.fetch('APPLIANCE_PG_PACKAGE_NAME') end |
.pg_dump_file?(file) ⇒ Boolean
101 102 103 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 101 def self.pg_dump_file?(file) File.open(file, "rb") { |f| f.readpartial(5) } == PG_DUMP_MAGIC end |
.prep_data_directory ⇒ Object
93 94 95 96 97 98 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 93 def self.prep_data_directory # initdb will fail if the database directory is not empty or not owned by the PostgresAdmin.user FileUtils.mkdir(PostgresAdmin.data_directory) unless Dir.exist?(PostgresAdmin.data_directory) FileUtils.chown_R(PostgresAdmin.user, PostgresAdmin.group, PostgresAdmin.data_directory) FileUtils.rm_rf(PostgresAdmin.data_directory.children.map(&:to_s)) end |
.recreate_db(opts) ⇒ Object
193 194 195 196 197 198 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 193 def self.recreate_db(opts) dbname = opts[:dbname] opts = opts.merge(:dbname => 'postgres') run_command("psql", opts, :command => "DROP DATABASE IF EXISTS #{dbname}") run_command("psql", opts, :command => "CREATE DATABASE #{dbname} WITH OWNER = #{opts[:username] || 'root'} ENCODING = 'UTF8'") end |
.reindex(opts) ⇒ Object
266 267 268 269 270 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 266 def self.reindex(opts) args = {} args[:table] = opts[:table] if opts[:table] run_command("reindexdb", opts, args) end |
.restore(opts) ⇒ Object
114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 114 def self.restore(opts) file = opts[:local_file] backup_type = opts.delete(:backup_type) || validate_backup_file_type(file) prepare_restore(backup_type, opts[:dbname]) case backup_type when :pgdump then restore_pg_dump(opts) when :basebackup then restore_pg_basebackup(file) else raise "#{file} is not a database backup" end end |
.restore_pg_basebackup(file) ⇒ Object
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 128 def self.restore_pg_basebackup(file) pg_service = LinuxAdmin::Service.new(service_name) pg_service.stop prep_data_directory require 'rubygems/package' # Using a Gem::Package instance for the #extract_tar_gz method, so we don't # have to re-write all of that logic. Mostly making use of # `Gem::Package::TarReader` + `Zlib::GzipReader` that is already part of # rubygems/stdlib and integrated there. unpacker = Gem::Package.new("obviously_not_a_gem") File.open(file, IO::RDONLY | IO::NONBLOCK) do |backup_file| unpacker.extract_tar_gz(backup_file, data_directory.to_s) end FileUtils.chown_R(PostgresAdmin.user, PostgresAdmin.group, PostgresAdmin.data_directory) pg_service.start file end |
.restore_pg_dump(opts) ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 200 def self.restore_pg_dump(opts) recreate_db(opts) args = { :verbose => nil, :exit_on_error => nil } if File.pipe?(opts[:local_file]) cmd_args = combine_command_args(opts, args) cmd = AwesomeSpawn.build_command_line("pg_restore", cmd_args) error_path = Dir::Tmpname.create("") { |tmpname| tmpname } spawn_args = { :err => error_path, :in => [opts[:local_file].to_s, "rb"] } logger.info("MIQ(#{name}.#{__method__}) Running command... #{cmd}") process_thread = Process.detach(Kernel.spawn(pg_env(opts), cmd, spawn_args)) process_status = process_thread.value handle_error(cmd, process_status.exitstatus, error_path) else args[nil] = opts[:local_file] run_command("pg_restore", opts, args) end opts[:local_file] end |
.run_command(cmd_str, opts, args) ⇒ Object
272 273 274 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 272 def self.run_command(cmd_str, opts, args) run_command_with_logging(cmd_str, opts, combine_command_args(opts, args)) end |
.run_command_with_logging(cmd_str, opts, params = {}) ⇒ Object Also known as: runcmd_with_logging
276 277 278 279 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 276 def self.run_command_with_logging(cmd_str, opts, params = {}) logger.info("MIQ(#{name}.#{__method__}) Running command... #{AwesomeSpawn.build_command_line(cmd_str, params)}") AwesomeSpawn.run!(cmd_str, :params => params, :env => pg_env(opts)).output end |
.service_name ⇒ Object
21 22 23 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 21 def self.service_name ENV.fetch("APPLIANCE_PG_SERVICE") end |
.service_running? ⇒ Boolean
61 62 63 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 61 def self.service_running? LinuxAdmin::Service.new(service_name).running? end |
.template_directory ⇒ Object
17 18 19 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 17 def self.template_directory Pathname.new(ENV.fetch("APPLIANCE_TEMPLATE_DIRECTORY")) end |
.user ⇒ Object
Unprivileged user to run postgresql
30 31 32 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 30 def self.user "postgres".freeze end |
.vacuum(opts) ⇒ Object
254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 254 def self.vacuum(opts) # TODO: Add a real exception here raise "Vacuum requires database" unless opts[:dbname] args = {} args[:analyze] = nil if opts[:analyze] args[:full] = nil if opts[:full] args[:verbose] = nil if opts[:verbose] args[:table] = opts[:table] if opts[:table] run_command("vacuumdb", opts, args) end |
.volume_group_name ⇒ Object
42 43 44 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 42 def self.volume_group_name "vg_data".freeze end |
.with_pg_connection(db_opts = {:user => user, :dbname => user}) ⇒ Object
50 51 52 53 54 55 |
# File 'lib/manageiq/appliance_console/postgres_admin.rb', line 50 def self.with_pg_connection(db_opts = {:user => user, :dbname => user}) conn = PG.connect(db_opts) yield conn ensure conn.close if conn end |