Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check for sufficient disk space before upgrade #1381

Merged
merged 1 commit into from
Aug 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions omnibus/files/private-chef-cookbooks/private-chef/libraries/du.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'mixlib/shellout'
module Du
# Calculate the disk space used by the given path. Requires that
# `du` is in our PATH.
#
# @param path [String] Path to a directory on disk
# @return [Integer] KB used by directory on disk
#
def self.du(path)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be hard to statfs for this, too? I've got the impression that this would let us stay clear of some potential compatibility issues...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind 😄

# TODO(ssd) 2017-08-18: Do we need to worry about sparse files
# here? If so, can we expect the --apparent-size flag to exist on
# all of our platforms.
command = Mixlib::ShellOut.new("du -sk #{path}")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about du -P?

this sounds compelling:

-P
Use a standard, portable, output format

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, you've already checked that. (Just now read the message)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-P is a flag to df rather than du. Unfortunately, as far as I can see, the -P flag doesn't solve the "mount point and file system with space with spaces"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah sorry for the noise, ignore me

command.run_command
if command.status.success?
command.stdout.split("\t").first.to_i
else
Chef::Log.error("du -sk #{path} failed with exit status: #{command.exitstatus}")
Chef::Log.error("du stderr: #{command.stderr}")
raise "du failed"
end
rescue Errno::ENOENT
raise "The du utility is not available. Unable to check disk usage"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this block upgrading? Can I override the disk usage check failure if I believe I know what I'm doing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently it does. We could use an environment variable to skip the check perhaps?

end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
require 'ffi'

class Statfs
#
# Statfs provides a simple interface to the statvfs system call.
# Since the statvfs struct varies a bit across platforms, this
# likely only works on Linux and OSX at the moment.
#
extend FFI::Library
ffi_lib FFI::Library::LIBC

attach_function(:statvfs, [:string, :pointer], :int)
attach_function(:strerror, [:int], :string)

FSBLKCNT_T = if RbConfig::CONFIG['host_os'] =~ /darwin|osx|mach/i
:uint
else
:ulong
end

# See http://man7.org/linux/man-pages/man2/statvfs.2.html
class Statvfs < FFI::Struct
spec = [
:f_bsize, :ulong, # Filesystem block size
:f_frsize, :ulong, # Fragement size
:f_blocks, FSBLKCNT_T, # Size of fs in f_frsize units
:f_bfree, FSBLKCNT_T, # Number of free blocks
:f_bavail, FSBLKCNT_T, # Number of free blocks for unpriviledged users
:f_files, FSBLKCNT_T, # Number of inodes
:f_ffree, FSBLKCNT_T, # Number of free inodes
:f_favail, FSBLKCNT_T, # Number of free inodes for unprivilged users
:f_fsid, :ulong, # Filesystem ID
:f_flag, :ulong, # Mount Flags
:f_namemax, :ulong # Max filename length
]

# Linux has this at the end of the struct and if we don't include
# it we end up getting a memory corruption error when th object
# gets GCd.
if RbConfig::CONFIG['host_os'] =~ /linux/i
spec << :f_spare
spec << [:int, 6]
end

layout(*spec)
end

def initialize(path)
@statvfs = stat(path)
end

#
# @returns [Integer] Free inodes on the given filesystem
#
def free_inodes
@statvfs[:f_favail]
end

#
# @returns [Integer] Free space in KB on the given filesystem
#
def free_space
# Since we are running as root we could report f_bfree but will
# stick with f_bavail since it will typically be more
# conservative.
(@statvfs[:f_frsize] * @statvfs[:f_bavail])/1024
end

private

def stat(path)
statvfs = Statvfs.new
if statvfs(path, statvfs.to_ptr) != 0
raise 'statvfs: ' + strerror(FFI.errno)
end
statvfs
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def whyrun_supported?
action :upgrade do
if upgrade_required?
converge_by("Upgrading database cluster") do
check_required_disk_space unless ENV['CS_SKIP_PG_DISK_CHECK'] == "1"
shutdown_postgres
initialize_new_cluster
update_to_latest_version
Expand Down Expand Up @@ -123,6 +124,37 @@ def upgrade_required?
end
end

#
# Since we don't use the --link flag, we need to ensure the disk has
# enough space for another copy of the postgresql data.
#
def check_required_disk_space
old_data_dir_size = Du.du(old_data_dir)
# new_data_dir might not exist at the point of making this check.
# In that case check the first existing directory above it.
new_dir = dir_or_existing_parent(new_data_dir)
free_disk_space = Statfs.new(new_dir).free_space

if old_data_dir_size < (free_disk_space * 0.90)
Chef::Log.debug("Old data dir size: #{old_data_dir_size}")
Chef::Log.debug(" Free disk space: #{free_disk_space}")
Chef::Log.debug("Free space is sufficient to start upgrade")
true
else
Chef::Log.fatal("Insufficient free space on disk to complete upgrade.")
Chef::Log.fatal("The current postgresql data directory contains #{old_data_dir_size} KB of data but only #{free_disk_space} KB is available on disk.")
Chef::Log.fatal("The upgrade process requires at least #{old_data_dir_size/0.90} KB.")
raise "Insufficient Disk Space to Upgrade"
end
end

def dir_or_existing_parent(dir)
return dir if ::File.exist?(dir)
return dir if ::File.expand_path(dir) == "/"

dir_or_existing_parent(::File.expand_path("#{dir}/.."))
end

# If a pre-existing postgres service exists it will need to be shut
# down prior to running the upgrade step.
def shutdown_postgres
Expand Down