summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2018-03-23 21:26:37 -0700
committerGitHub <noreply@github.com>2018-03-23 21:26:37 -0700
commitbec4db40902f3679e0b55ca27503d67a5ae92492 (patch)
tree38617b0459605333aa26736fef5c109c9841e5ce
parent48a36b3d34d1b8e0432f3def4a3cb4289be8cefa (diff)
parente579a172371f3da62f1216c8e31144c6d80eaa14 (diff)
downloadchef-bec4db40902f3679e0b55ca27503d67a5ae92492.tar.gz
Merge pull request #6990 from chef/swap
Add swap_file resource from the swap cookbook
-rw-r--r--lib/chef/resource/swap_file.rb209
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/unit/resource/swap_file_spec.rb40
3 files changed, 250 insertions, 0 deletions
diff --git a/lib/chef/resource/swap_file.rb b/lib/chef/resource/swap_file.rb
new file mode 100644
index 0000000000..6beb20c976
--- /dev/null
+++ b/lib/chef/resource/swap_file.rb
@@ -0,0 +1,209 @@
+#
+# 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
+
+ description "Use the swap_file resource to create or delete swap files on Linux systems."
+ introduced "14.0"
+
+ property :path, String,
+ description: "The path to put the swap file on the system.",
+ name_property: true
+
+ property :size, Integer,
+ description: "The size (in MBs) for the swap file."
+
+ property :persist, [TrueClass, FalseClass],
+ description: "Persist the swapon.",
+ default: false
+
+ property :timeout, Integer,
+ description: "Timeout for dd/fallocate.",
+ 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
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index e123cc5915..c232ce04e1 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -90,6 +90,7 @@ require "chef/resource/script"
require "chef/resource/service"
require "chef/resource/sudo"
require "chef/resource/sysctl"
+require "chef/resource/swap_file"
require "chef/resource/systemd_unit"
require "chef/resource/windows_service"
require "chef/resource/subversion"
diff --git a/spec/unit/resource/swap_file_spec.rb b/spec/unit/resource/swap_file_spec.rb
new file mode 100644
index 0000000000..905e12646a
--- /dev/null
+++ b/spec/unit/resource/swap_file_spec.rb
@@ -0,0 +1,40 @@
+#
+# Copyright:: Copyright 2018, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# 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 "spec_helper"
+
+describe Chef::Resource::SwapFile do
+ let(:resource) { Chef::Resource::SwapFile.new("swapfile") }
+
+ it "sets resource name as :swap_file" do
+ expect(resource.resource_name).to eql(:swap_file)
+ end
+
+ it "sets the path as its name" do
+ expect(resource.path).to eql("swapfile")
+ end
+
+ it "sets the default action as :create" do
+ expect(resource.action).to eql([:create])
+ end
+
+ it "supports :create and :remove actions" do
+ expect { resource.action :create }.not_to raise_error
+ expect { resource.action :remove }.not_to raise_error
+ expect { resource.action :delete }.to raise_error(ArgumentError)
+ end
+end