cookbooks/fb_storage/recipes/default.rb (197 lines of code) (raw):

# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2 # # Cookbook Name:: fb_storage # Recipes:: default # # Copyright (c) 2016-present, Facebook, Inc. # All rights reserved. # # 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. cookbook_file '/sbin/mount.rtxfs' do only_if { node.centos? } owner 'root' group 'root' mode '0755' end # fsck for XFS with realtime devices (rtxfs filesystem type) cookbook_file '/sbin/fsck.rtxfs' do only_if { node.centos? } owner 'root' group 'root' mode '0755' end directory FB::Storage::REPLACED_DISKS_DIR do owner 'root' group 'root' mode '0755' end # we use these to disable things while setuping up storage so make # sure the directories exist %w{ /run/udev /run/udev/rules.d /run/systemd/system-generators }.each do |dir| directory dir do owner 'root' group 'root' mode '0755' end end whyrun_safe_ruby_block 'validate storage options' do not_if { node['fb_storage']['devices'].empty? } block do # Mac doesn't have node['block_device'] .... and probably other stuff if node.macos? fail 'fb_storage: The `storage` API does not support MacOSX' end # Same with FIO cards. if node.device_of_mount('/').include?('fio') fail 'fb_storage: The `storage` API does not support ' + 'machines with root devices on FIO cards.' end storage = node['fb_storage'].to_hash seen_mountpoints = [] if storage['devices'] && !storage['devices'].is_a?(Array) fail 'fb_storage: The `devices` key in the Storage API ' + 'is set but is not an array. It must be an array of devices.' end storage['devices'].each_with_index do |device, didx| next if device['_skip'] unless device['partitions'].is_a?(Array) fail "fb_storage: The #{didx} device in the Storage API " + 'has an `partitions` key that is not an array. It must be an array ' + 'of partitions' end num_partitions = device['partitions'].count if device['whole_device'] Chef::Log.warn( "fb_storage: 'whole_device' set on device #{didx}, " + 'this is not recommended', ) if num_partitions > 1 fail "fb_storage: Device #{didx} specified 'whole_device'" + " but #{num_partitions} partitions. Exactly 1 required for " + '\'whole_device\'' end if device['partitions'][0]['partition_start'] || device['partitions'][0]['partition_end'] fail "fb_storage: Device #{didx} specified 'whole_device'" + 'but also specified a partition size. These are incompatible' end end device['partitions'].each_with_index do |partition, pidx| # if you specify a partition sizing you must specify both sides if (partition['partition_start'] && !partition['partition_end']) || (!partition['partition_start'] && partition['partition_end']) fail 'fb_storage: You must specify both a partition ' + 'start and end' end # If we have sizes, validate them if partition['partition_start'] pmsg = 'fb_storage: Invalid partition start value ' + "'%s' specified on device #{didx} parition #{pidx}. " + 'It must be a number with an optional suffix of %%kmgt' %w{start end}.each do |disp| unless partition["partition_#{disp}"].match( /^\d+(\.\d+)?([KkMmGgTt%](iB)?)?$/, ) fail format(pmsg, partition["partition_#{disp}"]) end end elsif num_partitions > 1 fail 'fb_storage: If you want more than one partition, ' + 'you must specify size of each, but no size was specified for ' + " device #{didx} partition #{pidx}" end if partition['_swraid_array'] && partition['_swraid_array_journal'] fail "fb_storage: device #{didx} partition #{pidx} is " + "set as both a member of array #{partition['_swraid_array']} " + "and a journal of array #{partition['_swraid_array_journal']}. " + 'This is invalid, please remove one of these.' end unless partition['label'] || partition['_swraid_array'] || partition['_swraid_array_journal'] || partition['_xfs_rt_metadata'] || partition['_xfs_rt_data'] || partition['_xfs_rt_rescue'] Chef::Log.debug( 'fb_storage: Adding default label of mount_point to ' + "#{partition['mount_point']}(partition #{pidx})", ) node.default['fb_storage']['devices'][didx][ 'partitions'][pidx]['label'] = partition['mount_point'] end if partition['mount_point'] if seen_mountpoints.include?(partition['mount_point']) fail 'fb_storage: Mount point ' + "#{partition['mount_point']} specified multiple times." end seen_mountpoints << partition['mount_point'] end end end storage['arrays']&.each_with_index do |array, aidx| next if array['_skip'] # this is roughly the same logic as the label in the partition # loop above... but I don't see a good way of abstracting it out next if array['_skip'] if array['whole_device'] fail "fb_storage: 'whole_device' was set on array #{aidx}" + ", but that's not a valid setting on an array." end if seen_mountpoints.include?(array['mount_point']) fail "fb_storage: Mount point #{array['mount_point']}" + ' specified multiple times.' end seen_mountpoints << array['mount_point'] unless array['label'] Chef::Log.debug( 'fb_storage: Adding default label of mount_point to ' + "#{array['mount_point']}(array #{aidx})", ) node.default['fb_storage']['arrays'][aidx][ 'label'] = array['mount_point'] end end end end ohai 'filesystem' do if node['filesystem2'] plugin 'filesystem2' else plugin 'filesystem' end action :nothing end ohai 'mdadm' do plugin 'mdadm' action :nothing end ruby_block 'convert persistent storage file' do # simple only_if to short-circuit if we don't use Storage API # or aren't in the conversion shard only_if do !node['fb_storage']['devices'].empty? && FB::Storage.can_use_dev_id?(node) end # now the actual idempotency check only_if do x = FB::Storage.persistent_data_file_version x && x != 2 end block do FB::Storage.convert_persistent_data_file end end package 'mdadm' do only_if do # we are enabled... !node['fb_storage']['devices'].empty? && # and we are supposed to manage packages node['fb_storage']['manage_packages'] && # and we manage arrays node['fb_storage']['arrays'] && !node['fb_storage']['arrays'].empty? end action :upgrade notifies :reload, 'ohai[mdadm]', :immediately end fb_storage_format_devices 'go' do not_if { node['fb_storage']['devices'].empty? } do_reprobe lazy { node['fb_storage']['format']['reprobe_before_repartition'] } # fb_fstab won't mount properly if we don't update data. notifies :reload, 'ohai[filesystem]', :immediately end template '/etc/mdadm.conf' do only_if do # we are enabled... !node['fb_storage']['devices'].empty? && # and we manage arrays node['fb_storage']['arrays'] && !node['fb_storage']['arrays'].empty? && # and we've been asked to create this node['fb_storage']['manage_mdadm_conf'] end owner 'root' group 'root' mode '0755' end file '/var/chef/storage_api_active' do not_if { node['fb_storage']['devices'].empty? } owner 'root' group 'root' mode '0644' end