# # Copyright 2012-2018, Seth Vargo # Copyright 2017-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "chef/resource" class Chef class Resource class SwapFile < Chef::Resource resource_name :swap_file provides(:swap_file) { true } description "Use the swap_file resource to create or delete swap files on Linux systems, and optionally to manage the swappiness configuration for a host." introduced "14.0" property :path, String, description: "The path where the swap file will be created on the system, if it differs from the resource block name.", name_property: true property :size, Integer, description: "The size (in MBs) of the swap file." property :persist, [TrueClass, FalseClass], description: "Persist the swapon.", default: false property :timeout, Integer, description: "Timeout for 'dd' / 'fallocate' commands.", default: 600 property :swappiness, Integer, description: "The swappiness value to set on the system." action :create do description "Create a swapfile." if swap_enabled? Chef::Log.debug("#{new_resource} already created - nothing to do") else begin Chef::Log.info "starting first create: #{node['virtualization']['system']}" do_create(swap_creation_command) rescue Mixlib::ShellOut::ShellCommandFailed => e Chef::Log.warn("#{new_resource} Rescuing failed swapfile creation for #{new_resource.path}") Chef::Log.debug("#{new_resource} Exception when creating swapfile #{new_resource.path}: #{e}") do_create(dd_command) end end if new_resource.swappiness declare_resource(:sysctl, "vm.swappiness") do value new_resource.swappiness end end end action :remove do description "Remove a swapfile and disable swap." swapoff if swap_enabled? remove_swapfile if ::File.exist?(new_resource.path) end action_class do def do_create(command) create_swapfile(command) set_permissions mkswap swapon persist if persist? end def create_swapfile(command) converge_by "create empty swapfile at #{new_resource.path}" do # ~FC054 shell_out!(command, timeout: new_resource.timeout) end end def set_permissions permissions = "600" converge_by "set permissions on #{new_resource.path} to #{permissions}" do shell_out!("chmod #{permissions} #{new_resource.path}") end end def mkswap converge_by "make #{new_resource.path} swappable" do shell_out!("mkswap -f #{new_resource.path}") end end def swapon converge_by "enable swap for #{new_resource.path}" do shell_out!("swapon #{new_resource.path}") end end def swapoff converge_by "turn off swap for #{new_resource.path}" do shell_out!("swapoff #{new_resource.path}") end end def remove_swapfile converge_by "remove swap file #{new_resource.path}" do ::FileUtils.rm(new_resource.path) end end def swap_enabled? enabled_swapfiles = shell_out("swapon --summary").stdout # Regex for our resource path and only our resource path # It will terminate on whitespace after the path it match # /testswapfile would match # /testswapfiledir/someotherfile will not swapfile_regex = Regexp.new("^#{new_resource.path}[\\s\\t\\n\\f]+") !swapfile_regex.match(enabled_swapfiles).nil? end def swap_creation_command command = if compatible_filesystem? && compatible_kernel && !docker? fallocate_command else dd_command end Chef::Log.debug("#{new_resource} swap creation command is '#{command}'") command end def fallback_swap_creation_command command = dd_command Chef::Log.debug("#{new_resource} fallback swap creation command is '#{command}'") command end # The block size (1MB) def block_size 1_048_576 end def fallocate_size size = block_size * new_resource.size Chef::Log.debug("#{new_resource} fallocate size is #{size}") size end def fallocate_command size = fallocate_size command = "fallocate -l #{size} #{new_resource.path}" Chef::Log.debug("#{new_resource} fallocate command is '#{command}'") command end def dd_command command = "dd if=/dev/zero of=#{new_resource.path} bs=#{block_size} count=#{new_resource.size}" Chef::Log.debug("#{new_resource} dd command is '#{command}'") command end def compatible_kernel fallocate_location = shell_out("which fallocate").stdout Chef::Log.debug("#{new_resource} fallocate location is '#{fallocate_location}'") ::File.exist?(fallocate_location.chomp) end def compatible_filesystem? compatible_filesystems = %w{xfs ext4} parent_directory = ::File.dirname(new_resource.path) # Get FS info, get second line as first is column headings command = "df -PT #{parent_directory} | awk 'NR==2 {print $2}'" result = shell_out(command).stdout Chef::Log.debug("#{new_resource} filesystem listing is '#{result}'") compatible_filesystems.any? { |fs| result.include? fs } end def persist? !!new_resource.persist end def persist fstab = "/etc/fstab" contents = ::File.readlines(fstab) addition = "#{new_resource.path} swap swap defaults 0 0" if contents.any? { |line| line.strip == addition } Chef::Log.debug("#{new_resource} already added to /etc/fstab - skipping") else Chef::Log.info("#{new_resource} adding entry to #{fstab} for #{new_resource.path}") contents << "#{addition}\n" ::File.open(fstab, "w") { |f| f.write(contents.join("")) } end end end end end end