cookbooks/fb_storage/spec/storage_spec.rb (2,551 lines of code) (raw):
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# 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.
require './spec/spec_helper'
require_relative '../../fb_fstab/libraries/default'
require_relative '../../fb_helpers/libraries/node_methods'
require_relative '../libraries/storage'
describe FB::Storage do
let(:node) { Chef::Node.new }
let(:attr_name) do
if node['filesystem2']
'filesystem2'
else
'filesystem'
end
end
before do
node.automatic['fb'] = {}
end
context '#eligble_devices' do
before do
{
'/' => '/dev/sda',
'/boot' => '/dev/sdb',
}.each do |mp, device|
allow(node).to receive(:device_of_mount).with(mp).and_return(device)
allow(File).to receive(:realpath).with(device).and_return(device)
if mp == '/boot'
boot_mock = double
allow(Pathname).to receive(:new).with(mp).and_return(boot_mock)
allow(boot_mock).to receive(:mountpoint?).and_return(device)
end
end
end
it 'should not include root nor boot drives' do
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdc' => {},
}
expect(FB::Storage.eligible_devices(node)).to eq(['sdc'])
end
it 'should skip ram, loop, and md devices' do
node.automatic['block_device'] = {
'sda' => {},
'fioa' => {},
'loop0' => {},
'ram0' => {},
'md0' => {},
'dm-0' => {},
'sr0' => {},
}
expect(FB::Storage.eligible_devices(node)).to eq(['fioa'])
end
# This isn't actually handled yet, so comment it out until we fix the code
# it 'should skip when we cannot find root\'s device' do
# node.automatic['block_device'] = {
# 'sda' => {},
# 'sdb' => {},
# 'sdc' => {},
# }
# allow(node).to receive(:device_of_mount).with('/').and_return(nil)
# expect(FB::Storage.eligible_devices(node)).to eq([])
# end
end
context '#root_device_name' do
it 'should work when root is a partition' do
{
'sda1' => 'sda',
'nvme0n1p1' => 'nvme0n1',
}.each do |partition, device|
node.automatic['block_device'][device] = {}
allow(node).to receive(:device_of_mount).with('/').and_return(
"/dev/#{partition}",
)
allow(File).to receive(:symlink?).with("/dev/#{partition}").
and_return(false)
expect(FB::Storage.root_device_name(node)).to eq(device)
end
end
it 'should work when root is a bare device' do
%w{
sda
md0
}.each do |device|
node.automatic['block_device'][device] = {}
allow(node).to receive(:device_of_mount).with('/').and_return(
"/dev/#{device}",
)
allow(File).to receive(:symlink?).with("/dev/#{device}").
and_return(false)
expect(FB::Storage.root_device_name(node)).to eq(device)
end
end
it 'should resolve dev mapper to the underlying device' do
# '/dev/mapper/transient' is a symlink to /dev/dm-0
mapper_dev = '/dev/mapper/transient'
root_dev = 'dm-0'
node.automatic['block_device'][root_dev] = {}
allow(node).to receive(:device_of_mount).with('/').and_return(
mapper_dev,
)
allow(File).to receive(:symlink?).with(mapper_dev).and_return(true)
allow(File).to receive(:realpath).with(mapper_dev).and_return(root_dev)
expect(FB::Storage.root_device_name(node)).to eq(root_dev)
end
it 'should skip resolving links when /dev is not visible' do
# some build environments, like antlir, don't expose /dev
# / is on /dev/loop0, but /dev/loop0 doesn't exist
root_dev = 'loop0'
node.automatic['block_device'][root_dev] = {}
allow(node).to receive(:device_of_mount).with('/').and_return(
root_dev,
)
allow(File).to receive(:symlink?).with(root_dev).and_return(false)
expect(FB::Storage.root_device_name(node)).to eq(root_dev)
end
end
context '#partition_device_name' do
it 'should insert a p when the device name ends in a number' do
FB::Storage.partition_device_name('abcd0', 2).
should eq('abcd0p2')
end
it 'should not insert a p when the device name ends in a number' do
FB::Storage.partition_device_name('abcd', 2).
should eq('abcd2')
end
end
context '#device_name_from_partition' do
it 'should handle devices that end in digits' do
{
'/dev/md0p1' => '/dev/md0',
'/dev/nvme0n1p0' => '/dev/nvme0n1',
'/dev/nvme1n2p1' => '/dev/nvme1n2',
'/dev/something0p0' => '/dev/something0',
}.each do |part, dev|
expect(FB::Storage.device_name_from_partition(part)).
to eq(dev)
end
end
# see comment in the code for why this is necessary
it 'should handle devices that end in digits, even when handed' +
' non-partitions' do
{
'/dev/md0' => '/dev/md0',
'/dev/nvme0n1' => '/dev/nvme0n1',
}.each do |part, dev|
expect(FB::Storage.device_name_from_partition(part)).
to eq(dev)
end
end
it 'should handle devices that do not end in digits' do
{
'/dev/sda1' => '/dev/sda',
'/dev/sdp1' => '/dev/sdp',
'/dev/hdb4' => '/dev/hdb',
'/dev/sdab9' => '/dev/sdab',
'/dev/fioa2' => '/dev/fioa',
}.each do |part, dev|
expect(FB::Storage.device_name_from_partition(part)).
to eq(dev)
end
end
end
context '#block_device_split' do
it 'splits correctly for valid inputs' do
{
'sda' => ['sd', 'a'],
'sdc' => ['sd', 'c'],
'sdaa' => ['sd', 'aa'],
'sdac' => ['sd', 'ac'],
'fioa' => ['fio', 'a'],
'fioc' => ['fio', 'c'],
'nvme0n1' => ['nvme', '0n1'],
'nvme1n4' => ['nvme', '1n4'],
'nvme10n24' => ['nvme', '10n24'],
'nbd3' => ['nbd', '3'],
'nbd12' => ['nbd', '12'],
}.each do |input, output|
expect(FB::Storage.block_device_split(input)).to eq(output)
end
end
it 'throws errors on invalid inputs' do
[
'hda',
'hdaa',
'fda',
'hda1',
'hda3',
# valid devices with a partition, which is invalid
'sda2',
'fioa1',
'nvme0n1p3',
'nbd2p2',
].each do |badinput|
expect do
FB::Storage.block_device_split(badinput)
end.to raise_error(
RuntimeError,
/fb_storage: Cannot parse #{badinput} for sorting/,
)
end
end
end
context '#block_device_sort' do
# normal sorting
it 'sorts sdb before sdc' do
expect(FB::Storage.block_device_sort('sdb', 'sdc', {})).
to eq(-1)
end
# b < aa
it 'sorts sdb before sdaa' do
expect(FB::Storage.block_device_sort('sdb', 'sdaa', {})).
to eq(-1)
end
it 'does not sort fioa between sdz and sdaa' do
expect(
['sdz', 'fioa', 'sdaa'].sort do |a, b|
FB::Storage.block_device_sort(a, b, {})
end,
).to eq(['sdz', 'sdaa', 'fioa'])
end
it 'sorts nvme by card number then namespace number' do
expect(
[
'nvme2n2', 'nvme1n2', 'nvme2n4', 'nvme22n1', 'nvme0n22', 'nvme0n2'
].sort do |a, b|
FB::Storage.block_device_sort(a, b, {})
end,
).to eq(
['nvme0n2', 'nvme0n22', 'nvme1n2', 'nvme2n2', 'nvme2n4', 'nvme22n1'],
)
end
it 'sorts prefixes together' do
# prefixes by length: sd, fio, nvme
expect(
[
'sdac', 'sdq', 'fiob', 'nvme1n1', 'sdb', 'fioa', 'sdaa', 'nvme0n1'
].sort do |a, b|
FB::Storage.block_device_sort(a, b, {})
end,
).to eq(
['sdb', 'sdq', 'sdaa', 'sdac', 'fioa', 'fiob', 'nvme0n1', 'nvme1n1'],
)
end
end
context '#hybrid_xfs_md_part_size' do
num_sectors_256gb = 500118192
num_sectors_1tb = 1758174768
it 'should return size divided by num_fses with sectors_reserved=0' do
allow(FB::Storage).to receive(
:hybrid_md_idx_size,
).and_return(num_sectors_256gb)
size = FB::Storage.hybrid_xfs_md_part_size(node, 0, 36)
expect(size).to eq(6783) # in MiB
end
it 'should subtract sectors_reserved when provided and then divide' do
allow(FB::Storage).to receive(
:hybrid_md_idx_size,
).and_return(num_sectors_1tb)
size = FB::Storage.hybrid_xfs_md_part_size(
node, 0, 36, num_sectors_256gb
)
expect(size).to eq(17063) # in MiB
end
end
context '#sort_scsi_slots' do
it 'should sort, by column, numerically' do
expect(
[
'0:2:3:0',
'1:0:0:91',
'5:01:00:1',
'0:12:3:0',
'5:10:00:9',
'5:9:9:19',
].sort do |a, b|
FB::Storage.sort_scsi_slots(a, b)
end,
).to eq(
[
'0:2:3:0',
'0:12:3:0',
'1:0:0:91',
'5:01:00:1',
'5:9:9:19',
'5:10:00:9',
],
)
end
end
context '#scsi_device_sort' do
let(:mapping) do
{
'/dev/sdc' => '0:2:3:0',
'/dev/sdq' => '1:0:0:91',
}
end
it 'should sort SCSI before non-SCSI' do
expect(
FB::Storage.scsi_device_sort(
'/dev/sdb',
'/dev/sdc',
mapping,
),
).to eq(1)
end
it 'should sort non-SCSI after SCSI' do
expect(
FB::Storage.scsi_device_sort(
'/dev/sdc',
'/dev/sdb',
mapping,
),
).to eq(-1)
end
it 'should treat two non-SCSI as equal' do
expect(
FB::Storage.scsi_device_sort(
'/dev/sdx',
'/dev/sdz',
mapping,
),
).to eq(0)
end
end
context '#sort_disk_shelves' do
it 'should sort shelves by shelf number then disk number' do
expect(
[
{
'shelf' => '/dev/bsg/6:0:4:0',
'disk' => 0,
},
{
'shelf' => '/dev/bsg/6:0:31:0',
'disk' => 0,
},
{
'shelf' => '/dev/bsg/6:1:0:0',
'disk' => 3,
},
{
'shelf' => '/dev/bsg/6:1:0:0',
'disk' => 9,
},
{
'shelf' => '/dev/bsg/6:0:31:0',
'disk' => 2,
},
{
'shelf' => '/dev/bsg/6:0:4:0',
'disk' => 1,
},
].sort do |a, b|
FB::Storage.sort_disk_shelves(a, b)
end,
).to eq([
# in particular we want to make sure 4 < 31
{
'shelf' => '/dev/bsg/6:0:4:0',
'disk' => 0,
},
{
'shelf' => '/dev/bsg/6:0:4:0',
'disk' => 1,
},
{
'shelf' => '/dev/bsg/6:0:31:0',
'disk' => 0,
},
{
'shelf' => '/dev/bsg/6:0:31:0',
'disk' => 2,
},
{
'shelf' => '/dev/bsg/6:1:0:0',
'disk' => 3,
},
{
'shelf' => '/dev/bsg/6:1:0:0',
'disk' => 9,
},
])
end
end
context '#load_previous_disk_order' do
it 'reads v1 files correctly' do
allow(File).to receive(:size?).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(100)
allow(File).to receive(:read).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(
'["sdl","sdr","sdy","sdb","sdc","sdt"]',
)
ret = FB::Storage.load_previous_disk_order
expect(ret).to eq(['sdl', 'sdr', 'sdy', 'sdb', 'sdc', 'sdt'])
end
end
context '#load_previous_disk_order' do
it 'reads v2 files correctly' do
mapping = {
'xxx' => 'sdl',
'aaa' => 'sdr',
'bbb' => 'sdy',
'ccc' => 'sdb',
'ddd' => 'sdc',
'eee' => 'sdt',
}
allow(File).to receive(:size?).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(100)
allow(File).to receive(:read).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(
'{"version":2,"disks":["xxx","aaa","bbb","ccc","ddd","eee"]}',
)
mapping.each do |id, dev|
sys = "#{FB::Storage::DEV_ID_DIR}/#{id}"
allow(File).to receive(:exist?).with(sys).and_return(dev)
allow(File).to receive(:readlink).with(sys).and_return("/dev/#{dev}")
end
ret = FB::Storage.load_previous_disk_order
expect(ret).to eq(['sdl', 'sdr', 'sdy', 'sdb', 'sdc', 'sdt'])
end
it 'does not accept other formats' do
allow(File).to receive(:size?).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(100)
allow(File).to receive(:read).with(
FB::Storage::PREVIOUS_DISK_ORDER,
).and_return(
'{"version":3,"disks":{}}',
)
expect do
FB::Storage.load_previous_disk_order
end.to raise_error(
RuntimeError,
'fb_storage: Unknown format of persistent-order cache file!',
)
end
end
context '#gen_persistent_disk_data' do
it 'writes v2 format' do
mapping = {
'xxx' => 'sdl',
'aaa' => 'sdr',
'bbb' => 'sdy',
'ccc' => 'sdb',
'ddd' => 'sdc',
'eee' => 'sdt',
}
allow(Dir).to receive(:open).with(
FB::Storage::DEV_ID_DIR,
).and_return(mapping.keys)
mapping.each do |k, v|
allow(File).to receive(:readlink).with(
"#{FB::Storage::DEV_ID_DIR}/#{k}",
).and_return("/dev/#{v}")
end
expected = {
'version' => 2,
'disks' => mapping.keys,
}
ret = FB::Storage.gen_persistent_disk_data(mapping.values)
expect(ret).to eq(expected)
end
end
context '#calculate_updated_order' do
it 'slots new disks into old slots' do
[
# simple example: replacing 'b' with 'f'
{
'previous' => ['a', 'b', 'c', 'd', 'e'],
'new' => ['a', 'c', 'd', 'e', 'f'],
'expected' => ['a', 'f', 'c', 'd', 'e'],
},
# slightly more complicated
# replacing 'b' with 'f' and 'd' with 'g'
{
'previous' => ['a', 'b', 'c', 'd', 'e'],
'new' => ['a', 'c', 'e', 'f', 'g'],
'expected' => ['a', 'f', 'c', 'g', 'e'],
},
# more complicated example. like the above, but the new ordering
# is different from expected, g and f ordered differently (for SCSI slot
# or JBOD slot). So... 'b' is now 'g' and 'd' is now 'f'
{
'previous' => ['a', 'b', 'c', 'd', 'e'],
'new' => ['a', 'c', 'e', 'g', 'f'],
'expected' => ['a', 'g', 'c', 'f', 'e'],
},
# another complicated example. like the above, but the both orderings
# are unexpected - 'e' replaced by 'q' and 'c' replaced with 'l'
{
'previous' => ['d', 'e', 'a', 'f', 'c'],
'new' => ['d', 'a', 'f', 'q', 'l'],
'expected' => ['d', 'q', 'a', 'f', 'l'],
},
# most complicated example... we totally changed our algorithm and
# fucked everything up so all the ordering is different, but only one
# disk changed. We should ignore the new algorithm and keep things
# as they are except for 'c' -> 'l'
{
'previous' => ['a', 'b', 'c', 'd', 'e'],
'new' => ['d', 'a', 'e', 'l', 'b'],
'expected' => ['a', 'b', 'l', 'd', 'e'],
},
].each do |example|
ret = FB::Storage.calculate_updated_order(
example['previous'],
example['new'],
)
expect(ret).to eq(example['expected'])
end
end
it 'fails when new and old are the same disks' do
disks = ['b', 'a', 'd', 'e', 'c']
expect do
FB::Storage.calculate_updated_order(disks, disks.sort)
end.to raise_error(
RuntimeError,
/fb_storage: Found no difference between old and new disks/,
)
end
end
context '#sorted_devices' do
before do
allow(node).to receive(:device_of_mount).and_return('/dev/sda')
allow(node).to receive(:virtual?).and_return(false)
allow(FB::Storage).to receive(:write_out_disk_order).
and_return(nil)
allow(FB::Storage).to receive(:devices_to_skip).with(node).
and_return(['sda'])
end
before(:each) do
node.default['fb_storage']['_ordered_disks'] = nil
end
context 'with a cache file' do
it 'should use the cache file when all disks are in the same order' do
previous_order = ['b', 'e', 'd', 'a', 'c']
expect(FB::Storage).to receive(:load_previous_disk_order).
and_return(previous_order)
expect(FB::Storage).to receive(:eligible_devices).
and_return(previous_order)
expect(Chef::Log).to receive(:debug).with(/Using previous/)
expect(FB::Storage.sorted_devices(node, [])).to eq(
previous_order,
)
end
it 'should use the cache file when all disks are in a different order' do
previous_order = ['b', 'e', 'd', 'a', 'c']
expect(FB::Storage).to receive(:load_previous_disk_order).
and_return(previous_order)
expect(FB::Storage).to receive(:eligible_devices).
and_return(['a', 'b', 'c', 'd', 'e'])
expect(Chef::Log).to receive(:debug).with(/Using previous/)
expect(FB::Storage.sorted_devices(node, [])).to eq(
previous_order,
)
end
it 'should base the new order on the old order when disks change' do
# for can_use_dev_id?
node.automatic['block_device'] = {}
previous_order = ['sdb', 'sde', 'sdd', 'sdc', 'sdf']
expect(FB::Storage).to receive(:load_previous_disk_order).
and_return(previous_order)
expect(FB::Storage).to receive(:eligible_devices).
and_return(['sdf', 'sdb', 'sdd', 'sde', 'sdg'])
expect(Chef::Log).not_to receive(:debug).with(/Using previous/)
expect(FB::Storage.sorted_devices(node, [])).to eq(
# only sdc was replaced (with sdg)
['sdb', 'sde', 'sdd', 'sdg', 'sdf'],
)
end
end
end
context '#_handle_custom_device_order_method' do
context 'with a _clowntown_device_order_method' do
before(:each) do
node.default['fb_storage'][
'_clowntown_device_order_method'] = double('custom_device_order')
allow(FB::Storage).to receive(
:write_out_disk_order,
).and_return(true)
end
it 'should call the custom method if firstboot' do
allow(node).to receive(:firstboot_tier?).and_return(true)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(false)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).to receive(:call).once
FB::Storage._handle_custom_device_order_method(node)
end
it 'should call the custom method if signal file exists (and delete ' +
'signal file)' do
allow(node).to receive(:firstboot_tier?).and_return(false)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(true)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).to receive(:call).once
expect(File).to receive(:delete).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
)
FB::Storage._handle_custom_device_order_method(node)
end
it 'should call the custom method if firstboot and signal file exists' +
' (and delete signal file)' do
allow(node).to receive(:firstboot_tier?).and_return(true)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(true)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).to receive(:call).once
expect(File).to receive(:delete).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
)
FB::Storage._handle_custom_device_order_method(node)
end
it 'should not call the custom method if not firstboot or signal file' do
allow(node).to receive(:firstboot_tier?).and_return(false)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(false)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).not_to receive(:call)
FB::Storage._handle_custom_device_order_method(node)
end
end
context 'without a _clowntown_device_order_method' do
before(:each) do
node.default['fb_storage'][
'_clowntown_device_order_method'] = nil
end
it 'should not call the method if firstboot' do
allow(node).to receive(:firstboot_tier?).and_return(true)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(false)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).not_to receive(:call)
FB::Storage._handle_custom_device_order_method(node)
end
it 'should not call the method if signal file (and delete file)' do
allow(node).to receive(:firstboot_tier?).and_return(false)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(true)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).not_to receive(:call)
FB::Storage._handle_custom_device_order_method(node)
end
it 'should not call the method if neither first boot nor signal file ' +
'(and delete the file)' do
allow(node).to receive(:firstboot_tier?).and_return(false)
allow(File).to receive(:exist?).with(
FB::Storage::FORCE_WRITE_CUSTOM_DISK_ORDER,
).and_return(false)
expect(
node['fb_storage']['_clowntown_device_order_method'],
).not_to receive(:call)
FB::Storage._handle_custom_device_order_method(node)
end
end
end
context '#build_mapping' do
before do
allow(node).to receive(:device_of_mount).with('/').and_return('/dev/sda')
allow(node).to receive(:device_of_mount).with('/boot').
and_return('/dev/sda')
boot_mock = double
allow(Pathname).to receive(:new).with('/boot').and_return(boot_mock)
allow(boot_mock).to receive(:mountpoint?).and_return('/dev/sda')
allow(File).to receive(:realpath).with('/dev/sda').and_return('/dev/sda')
allow(node).to receive(:virtual?).and_return(false)
allow(FB::Storage).to receive(:load_previous_disk_order).
and_return(nil)
allow(FB::Storage).to receive(:write_out_disk_order).
and_return(nil)
end
context 'straight disks' do
6.times.each do |i|
let("device#{i + 1}".to_sym) do
{
'partitions' => [
{
'type' => 'xfs',
'mount_point' => "/data/#{i + 1}",
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
],
}
end
end
it 'should build config in consistent order for local storage' do
node.default['fb_storage']['devices'] = [device1, device2]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'fioa' => {},
}
node.automatic['fb'] = {}
expected = {
:disks => {
'/dev/sdb' => device1,
'/dev/fioa' => device2,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
# same thing, but if `block_device` reports a different order
node.automatic['block_device'] = {
'fioa' => {},
'sda' => {},
'sdb' => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build config in consistent order for shelves' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4, device5, device6
]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdc' => {},
'sdd' => {},
'sdl' => {},
'sdn' => {},
'sdq' => {},
}
node.automatic['fb']['fbjbod']['shelves']['/dev/bsg/6:0:31:0'] = [
'/dev/sdb', '/dev/sdd', '/dev/sdc'
]
node.automatic['fb']['fbjbod']['shelves']['/dev/bsg/6:0:15:0'] = [
'/dev/sdq', '/dev/sdl', '/dev/sdn'
]
expected = {
:disks => {
'/dev/sdq' => device1,
'/dev/sdl' => device2,
'/dev/sdn' => device3,
'/dev/sdb' => device4,
'/dev/sdd' => device5,
'/dev/sdc' => device6,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'handles back-compat shelf names' do
node.default['fb_storage']['devices'] = [
device1, device2
]
node.automatic['fb']['fbjbod']['shelves']['/dev/sg14'] = [
'/dev/sdb',
]
node.automatic['fb']['fbjbod']['shelves']['/dev/sg2'] = [
'/dev/sdq',
]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdq' => {},
}
expected = {
:disks => {
'/dev/sdq' => device1,
'/dev/sdb' => device2,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build config in consistent order for shelves+flash' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4
]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {}, # fbjbod
'sdc' => {}, # lsicard
'sdd' => {}, # fbjbod
'fioa' => {}, # fio card
}
node.automatic['fb']['fbjbod']['shelves']['/dev/sg16'] = [
'/dev/sdd', '/dev/sdb'
]
expected = {
:disks => {
# local attached first
'/dev/sdc' => device1,
'/dev/fioa' => device2,
# then shelves, in shelf order
'/dev/sdd' => device3,
'/dev/sdb' => device4,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build config in consistent order for scsi slots' do
node.default['fb_storage']['devices'] = [
device1, device2, device3
]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdc' => {},
'sdd' => {},
}
node.automatic['scsi'] = {
'1:2:3:4' => {
'device' => '/dev/sda',
},
'0:99:1:2' => {
'device' => '/dev/sdb',
},
'9:1:6:5' => {
'device' => '/dev/sdc',
},
'0:9:1:2' => {
'device' => '/dev/sdd',
},
}
expected = {
:disks => {
# in order of SCSI bus
'/dev/sdd' => device1,
'/dev/sdb' => device2,
'/dev/sdc' => device3,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build config in consistent order for jbod+scsi+disks' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4, device5, device6
]
node.automatic['block_device'] = {
'sda' => {}, # root
'sdb' => {}, # jbod
'sdc' => {}, # scsi
'sdd' => {}, # jbod
'fioa' => {}, # flash
'sde' => {}, # drive w/out SCSI addr
'sdf' => {}, # scsi
}
node.automatic['scsi'] = {
'1:2:3:4' => {
'device' => '/dev/sda',
},
'9:12:6:5' => {
'device' => '/dev/sdc',
},
'9:1:6:5' => {
'device' => '/dev/sdf',
},
}
node.automatic['fb']['fbjbod']['shelves']['/dev/sg16'] = [
'/dev/sdd', '/dev/sdb'
]
expected = {
:disks => {
# in order of SCSI bus
'/dev/sdf' => device1,
'/dev/sdc' => device2,
# now leftover disk
'/dev/sde' => device3,
'/dev/fioa' => device4,
# now jbod
'/dev/sdd' => device5,
'/dev/sdb' => device6,
},
:arrays => {},
}
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
end
context 'arrays' do
3.times.each do |array|
5.times.each do |device|
let("device#{(array * 5) + device + 1}".to_sym) do
{
'partitions' => [
{
'_swraid_array' => array,
},
],
}
end
end
let("array#{array + 1}".to_sym) do
{
'type' => 'xfs',
'mount_point' => "/data/#{array + 1}",
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'raid_level' => 5,
}
end
end
it 'should build a config with single array mapping' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4, device5
]
node.default['fb_storage']['arrays'] = [array1]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdc' => {},
'sdd' => {},
'sde' => {},
'sdf' => {},
}
expected_disks = {
'/dev/sdb' => device1,
'/dev/sdc' => device2,
'/dev/sdd' => device3,
'/dev/sde' => device4,
'/dev/sdf' => device5,
}
expected_arrays = {
'/dev/md0' => array1.merge(
{ 'members' => expected_disks.keys.map { |x| "#{x}1" } },
),
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build a config with multiple array mapping' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4, device5,
device6, device7, device8, device9, device10,
device11, device12, device13, device14, device15
]
node.default['fb_storage']['arrays'] = [
array1, array2, array3
]
expected_disks = {
'/dev/sdb' => device1,
'/dev/sdc' => device2,
'/dev/sdd' => device3,
'/dev/sde' => device4,
'/dev/sdf' => device5,
'/dev/sdg' => device6,
'/dev/sdh' => device7,
'/dev/sdi' => device8,
'/dev/sdj' => device9,
'/dev/sdk' => device10,
'/dev/sdl' => device11,
'/dev/sdm' => device12,
'/dev/sdn' => device13,
'/dev/sdo' => device15,
'/dev/sdp' => device15,
}
expected_disks.each_key do |d|
node.automatic['block_device'][File.basename(d)] = {}
end
node.automatic['block_device']['sda'] = {}
expected_arrays = {
'/dev/md0' => array1.merge(
{ 'members' => expected_disks.keys[0..4].map { |x| "#{x}1" } },
),
'/dev/md1' => array2.merge(
{ 'members' => expected_disks.keys[5..9].map { |x| "#{x}1" } },
),
'/dev/md2' => array3.merge(
{ 'members' => expected_disks.keys[10..14].map { |x| "#{x}1" } },
),
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build a config with multiple array mapping, ' +
'and a skipped array' do
node.default['fb_storage']['devices'] = [
{ '_skip' => true }, { '_skip' => true }, { '_skip' => true },
{ '_skip' => true }, { '_skip' => true },
device6, device7, device8, device9, device10,
device11, device12, device13, device14, device15
]
node.default['fb_storage']['arrays'] = [
{ '_skip' => true }, array2, array3
]
expected_disks = {
'/dev/sdb' => { '_skip' => true },
'/dev/sdc' => { '_skip' => true },
'/dev/sdd' => { '_skip' => true },
'/dev/sde' => { '_skip' => true },
'/dev/sdf' => { '_skip' => true },
'/dev/sdg' => device6,
'/dev/sdh' => device7,
'/dev/sdi' => device8,
'/dev/sdj' => device9,
'/dev/sdk' => device10,
'/dev/sdl' => device11,
'/dev/sdm' => device12,
'/dev/sdn' => device13,
'/dev/sdo' => device15,
'/dev/sdp' => device15,
}
expected_disks.each_key do |d|
node.automatic['block_device'][File.basename(d)] = {}
end
node.automatic['block_device']['sda'] = {}
expected_arrays = {
'/dev/md0' => { '_skip' => true, 'members' => [] },
'/dev/md1' => array2.merge(
{ 'members' => expected_disks.keys[5..9].map { |x| "#{x}1" } },
),
'/dev/md2' => array3.merge(
{ 'members' => expected_disks.keys[10..14].map { |x| "#{x}1" } },
),
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build a config with multiple array mapping, ' +
'and / and /boot on their own RAID devices' do
node.default['fb_storage']['devices'] = [
device6, device7, device8, device9, device10,
device11, device12, device13, device14, device15
]
node.default['fb_storage']['arrays'] = [
# / is md0, need to skip it
{ '_skip' => true }, array2, array3
]
expected_disks = {
'/dev/sdg' => device6,
'/dev/sdh' => device7,
'/dev/sdi' => device8,
'/dev/sdj' => device9,
'/dev/sdk' => device10,
'/dev/sdl' => device11,
'/dev/sdm' => device12,
'/dev/sdn' => device13,
'/dev/sdo' => device15,
'/dev/sdp' => device15,
}
('sda'..'sdp').each do |d|
node.automatic['block_device'][d] = {}
end
node.automatic['block_device']['md0'] = {}
node.automatic['block_device']['md21'] = {}
{
'/' => '/dev/md0',
'/boot' => '/dev/md21',
}.each do |mp, device|
allow(node).to receive(:device_of_mount).with(mp).and_return(device)
allow(File).to receive(:symlink?).with(device).and_return(false)
if mp == '/boot'
boot_mock = double
allow(Pathname).to receive(:new).with(mp).and_return(boot_mock)
allow(boot_mock).to receive(:mountpoint?).and_return(device)
end
end
allow(Dir).to receive(:glob).with('/sys/block/md0/slaves/*').and_return(
('sdc'..'sdf').map { |disk| "/sys/block/md0/slaves/#{disk}" },
)
allow(Dir).to receive(:glob).with('/sys/block/md21/slaves/*').
and_return(
%w{sda sdb}.map { |disk| "/sys/block/md21/slaves/#{disk}" },
)
expected_arrays = {
'/dev/md0' => { '_skip' => true, 'members' => [] },
'/dev/md1' => array2.merge(
{ 'members' => expected_disks.keys[0..4].map { |x| "#{x}1" } },
),
'/dev/md21' => { '_skip' => true, 'members' => [] },
'/dev/md2' => array3.merge(
{ 'members' => expected_disks.keys[5..9].map { |x| "#{x}1" } },
),
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should build a config / and /boot on their own RAID devices' do
node.default['fb_storage']['devices'] = []
('sda'..'sdp').each do |d|
node.automatic['block_device'][d] = {}
end
node.automatic['block_device']['md0'] = {}
node.automatic['block_device']['md21'] = {}
{
'/' => '/dev/md0',
'/boot' => '/dev/md21',
}.each do |mp, device|
allow(node).to receive(:device_of_mount).with(mp).and_return(device)
allow(File).to receive(:symlink?).with(device).and_return(false)
end
allow(Dir).to receive(:glob).with('/sys/block/md0/slaves/*').and_return(
('sda'..'sdf').map { |disk| "/sys/block/md0/slaves/#{disk}" },
)
allow(Dir).to receive(:glob).with('/sys/block/md21/slaves/*').
and_return(
('sdg'..'sdp').map { |disk| "/sys/block/md21/slaves/#{disk}" },
)
expected_arrays = {
'/dev/md0' => { '_skip' => true, 'members' => [] },
'/dev/md21' => { '_skip' => true, 'members' => [] },
}
expected = { :disks => {}, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
it 'should fail to build a config with an array and / both on md0' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4, device5,
device6, device7, device8, device9, device10,
device11, device12, device13, device14, device15
]
node.default['fb_storage']['arrays'] = [
array1, array2, array3
]
('sda'..'sdp').each do |d|
node.automatic['block_device'][d] = {}
end
node.automatic['block_device']['md0'] = {}
node.automatic['block_device']['nvme0'] = {}
{
'/' => '/dev/md0',
'/boot' => '/dev/md0',
}.each do |mp, device|
allow(node).to receive(:device_of_mount).with(mp).and_return(device)
allow(File).to receive(:symlink?).with(device).and_return(false)
if mp == '/boot'
boot_mock = double
allow(Pathname).to receive(:new).with(mp).and_return(boot_mock)
allow(boot_mock).to receive(:mountpoint?).and_return(device)
end
end
allow(Dir).to receive(:glob).with('/sys/block/md0/slaves/*').and_return(
['sda', 'nvme0'].map { |disk| "/sys/block/md0/slaves/#{disk}" },
)
expect do
FB::Storage.build_mapping(node, [])
end.to raise_error(
RuntimeError, 'fb_storage: Asked to configure md0 but that is `/`!'
)
end
it 'should build a config without /boot mounted' do
node.default['fb_storage']['devices'] = [
device1, device2, device3
]
node.default['fb_storage']['arrays'] = [array1]
('sda'..'sdd').each do |d|
node.automatic['block_device'][d] = {}
end
root_dev = 'md4'
node.automatic['block_device'][root_dev] = {}
node.automatic['block_device']['nvme0'] = {}
root_md = "/dev/#{root_dev}"
allow(node).to receive(:device_of_mount).with('/').and_return(root_md)
allow(File).to receive(:symlink?).with(root_md).and_return(false)
allow(Dir).to receive(:glob).with("/sys/block/#{root_dev}/slaves/*").
and_return(
['sda', 'nvme0'].map do |disk|
"/sys/block/#{root_dev}/slaves/#{disk}"
end,
)
# Because we do Pathname.new('/boot'), we need to mock it
boot_mock = double
allow(Pathname).to receive(:new).with('/boot').and_return(boot_mock)
allow(boot_mock).to receive(:mountpoint?).and_return(nil)
expected_disks = {
'/dev/sdb' => device1,
'/dev/sdc' => device2,
'/dev/sdd' => device3,
}
expected_arrays = {
'/dev/md0' => array1.merge(
{ 'members' => expected_disks.keys.map { |x| "#{x}1" } },
),
root_md => { '_skip' => true, 'members' => [] },
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
end
context 'hybrid_xfs arrays' do
let(:device1) do
{
'partitions' => 3.times.map do |i|
{
'_xfs_rt_metadata' => i,
}
end,
}
end
3.times.each do |i|
let("device#{i + 2}".to_sym) do
{
'partitions' => [
{
'_xfs_rt_data' => i,
},
],
}
end
let("array#{i + 1}".to_sym) do
{
'type' => 'xfs',
'mount_point' => "/data/#{i + 1}",
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'raid_level' => 'hybrid_xfs',
}
end
end
it 'should build a config with hybrid_xfs mapping' do
node.default['fb_storage']['devices'] = [
device1, device2, device3, device4
]
node.default['fb_storage']['arrays'] = [
array1, array2, array3
]
node.automatic['block_device'] = {
'sda' => {},
'sdb' => {},
'sdc' => {},
'sdd' => {},
'sde' => {},
}
expected_disks = {
'/dev/sdb' => device1,
'/dev/sdc' => device2,
'/dev/sdd' => device3,
'/dev/sde' => device4,
}
%w{sdc sdd sde}.each_with_index do |d, idx|
expected_disks["/dev/#{d}"]['partitions'][0]['part_name'] =
"/data/#{idx + 1}"
end
3.times.map do |i|
expected_disks['/dev/sdb']['partitions'][i]['part_name'] =
"md:/data/#{i + 1}"
end
node.automatic['block_device']['sda'] = {}
expected_arrays = {
'/dev/md0' => array1.merge(
{
'members' => ['/dev/sdc1'],
'journal' => '/dev/sdb1',
},
),
'/dev/md1' => array2.merge(
{
'members' => ['/dev/sdd1'],
'journal' => '/dev/sdb2',
},
),
'/dev/md2' => array3.merge(
{
'members' => ['/dev/sde1'],
'journal' => '/dev/sdb3',
},
),
}
expected = { :disks => expected_disks, :arrays => expected_arrays }
expect(FB::Storage.build_mapping(node, [])).to eq(expected)
end
end
end
context '#partition_names' do
it 'should list all configured partitions for a device' do
device1 = {
'partitions' => [
{
'type' => 'xfs',
'mount_point' => '/data/1',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
],
}
device2 = {
'partitions' => [
{
'type' => 'xfs',
'mount_point' => '/waka/1',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
{
'type' => 'xfs',
'mount_point' => '/waka/2',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
{
'type' => 'xfs',
'mount_point' => '/waka/3',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
],
}
FB::Storage.partition_names('/dev/fioa', device1).
should eq(['/dev/fioa1'])
FB::Storage.partition_names('/dev/fioa', device2).
should eq(['/dev/fioa1', '/dev/fioa2', '/dev/fioa3'])
end
end
context 'common device needs' do
let(:single_partition_device) do
{
'partitions' => [
{
'type' => 'xfs',
'mount_point' => '/data/fa',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'label' => '/data/fa',
},
],
}
end
let(:double_partition_device) do
{
'partitions' => [
{
'type' => 'xfs',
'mount_point' => '/data/fa2',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
{
'type' => 'xfs',
'mount_point' => '/data/fa3',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
},
],
}
end
let(:single_array) do
{
'type' => 'xfs',
'mount_point' => '/data/fa',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'raid_level' => 1,
}
end
let(:raid0_array) do
{
'type' => 'xfs',
'mount_point' => '/data/flash',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'raid_level' => 0,
}
end
let(:array_member) do
{
'partitions' => [
{
'_swraid_array' => 0,
},
],
}
end
let(:whole_device_single_fs) do
{
'whole_device' => true,
'partitions' => [{
'type' => 'xfs',
'mount_point' => '/data/fa',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'label' => '/data/fa',
}],
}
end
context '#out_of_spec' do
before do
allow(FB::Storage).to receive(:get_actual_part_name).and_return(
nil, # ''
)
end
it 'should return nothing if everything is in spec' do
node.default['fb_storage']['devices'] = [
single_partition_device, double_partition_device,
whole_device_single_fs
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb1' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
'label' => '/data/fa',
},
'/dev/sdc1' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
},
'/dev/sdc2' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
},
'/dev/sdd' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
'label' => '/data/fa',
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => single_partition_device,
'/dev/sdc' => double_partition_device,
},
:arrays => {},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched filesystems' do
node.default['fb_storage']['devices'] = [
single_partition_device,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb1' => {
'fs_type' => 'ext4',
'mounts' => ['/data/fa'],
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => single_partition_device }, :arrays => {}
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/sdb1'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched partitions' do
node.default['fb_storage']['devices'] = [
single_partition_device,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb1' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
},
'/dev/sdb2' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => single_partition_device }, :arrays => {}
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => ['/dev/sdb'],
:mismatched_filesystems => ['/dev/sdb1'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify missing partitions' do
node.default['fb_storage']['devices'] = [
double_partition_device,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => double_partition_device }, :arrays => {}
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => ['/dev/sdb'],
:missing_filesystems => ['/dev/sdb1', '/dev/sdb2'],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched labels on a partition' do
node.default['fb_storage']['devices'] = [
single_partition_device,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb1' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
'label' => '/wrong_label',
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => single_partition_device }, :arrays => {}
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/sdb1'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify wrong partlabel on hybrid metadata device' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'wrong_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'correct_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => ['/dev/sdb'],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should not consider hybrid md out of spec when partlabel correct' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'correct_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'correct_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should flag wrong xfs label on hybrid md device as out of spec' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'correct_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'correct_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs', 'label' => '/wrong_label' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'],
'journal' => '/dev/sdb1',
'label' => '/data/fa' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0', '/dev/sdb1'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should not flag a hybrid xfs md partition with the correct label' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'correct_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'correct_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs', 'label' => '/data/fa' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'],
'journal' => '/dev/sdb1',
'label' => '/data/fa' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched partlabels for hybrid FS' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'correct_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'wrong_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => ['/dev/sdc'],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'it should match correct partlabels for hybrid FS' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
'part_name' => 'correct_part_name',
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
'part_name' => 'correct_part_name',
},
{
'_xfs_rt_rescue' => 0,
'part_name' => 'correct_part_name',
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
'/dev/sdc2' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
expect(FB::Storage).to receive(:get_actual_part_name).and_return(
'correct_part_name',
).exactly(3).times
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched labels on a whole device' do
node.default['fb_storage']['devices'] = [
whole_device_single_fs,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {
'fs_type' => 'xfs',
'mounts' => ['/data/fa'],
'label' => '/wrong_label',
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => whole_device_single_fs }, :arrays => {}
},
)
storage = FB::Storage.new(node)
puts(storage.out_of_spec)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/sdb'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify missing arrays' do
node.default['fb_storage']['devices'] = [
array_member, array_member
]
node.default['fb_storage']['arrays'] = [single_array]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdc' => {},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => array_member, '/dev/sdc' => array_member
},
:arrays => { '/dev/md0' => single_array },
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0'],
:mismatched_arrays => [],
:missing_partitions => ['/dev/sdb', '/dev/sdc'],
:missing_filesystems => ['/dev/sdb1', '/dev/sdc1'],
:missing_arrays => ['/dev/md0'],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched arrays' do
device1 = {
'_skip' => true,
}
node.default['fb_storage']['devices'] = [
device1, array_member, array_member
]
node.default['fb_storage']['arrays'] = [single_array]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdc' => {},
'/dev/sdd' => {},
'/dev/md0' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm']['md0'] = {
'level' => 1,
'members' => ['sdb1', 'sdc1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => device1,
'/dev/sdc' => array_member,
'/dev/sdd' => array_member,
},
:arrays => {
'/dev/md0' => single_array.merge(
{ 'members' => ['/dev/sdc1', '/dev/sdd1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0'],
:mismatched_arrays => ['/dev/md0'],
:missing_partitions => ['/dev/sdc', '/dev/sdd'],
:missing_filesystems => ['/dev/sdc1', '/dev/sdd1'],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify arrays with missing members' do
node.default['fb_storage']['devices'] = [
array_member, array_member
]
node.default['fb_storage']['arrays'] = [single_array]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdc' => {},
'/dev/md0' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm']['md0'] = {
'level' => 1,
'members' => ['sdb1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => array_member, '/dev/sdc' => array_member
},
:arrays => {
'/dev/md0' => single_array.merge(
{ 'members' => ['/dev/sdb1', '/dev/sdc1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => ['/dev/sdb', '/dev/sdc'],
:missing_filesystems => ['/dev/sdb1', '/dev/sdc1'],
:missing_arrays => [],
:incomplete_arrays => { '/dev/md0' => ['/dev/sdc1'] },
:extra_arrays => [],
},
)
end
it 'should identify partial RAID0 as mismatched when md is broken' do
# When all of the storage devices are already set up, but something
# goes wrong with assembling the md device, we should report a mismatch
# For this case there's no functional fs on top of the md device
# because a missing or broken device breaks RAID0. We can have the right
# devices be visible, but md fail to assemble the device
node.default['fb_storage']['devices'] = [
array_member, array_member, array_member
]
node.default['fb_storage']['arrays'] = [raid0_array]
node.automatic[attr_name]['by_device'] = {
'/dev/nbd0p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd1p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd2p1' => { :fs_type => 'linux_raid_member' },
# md0 exists, but is inactive, so no visible fs
'/dev/md0' => {},
}
node.automatic['mdadm']['md0'] = {
'level' => 0,
# inactive RAID0 has empty members
'members' => [],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/nbd0' => array_member,
'/dev/nbd1' => array_member,
'/dev/nbd2' => array_member,
},
:arrays => {
'/dev/md0' => raid0_array.merge(
{ 'members' => ['/dev/nbd0p1', '/dev/nbd1p1', '/dev/nbd2p1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0'],
:mismatched_arrays => ['/dev/md0'],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify mismatched raid levels' do
# If we want a stripe, but have a mirror, it's a mismatch
node.default['fb_storage']['devices'] = [
array_member, array_member, array_member
]
node.default['fb_storage']['arrays'] = [raid0_array]
node.automatic[attr_name]['by_device'] = {
'/dev/nbd0p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd1p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd2p1' => { :fs_type => 'linux_raid_member' },
'/dev/md0' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm']['md0'] = {
'level' => 1,
'members' => ['nbd0p1', 'nbd1p1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/nbd0' => array_member,
'/dev/nbd1' => array_member,
'/dev/nbd2' => array_member,
},
:arrays => {
'/dev/md0' => raid0_array.merge(
{ 'members' => ['/dev/nbd0p1', '/dev/nbd1p1', '/dev/nbd2p1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0'],
:mismatched_arrays => ['/dev/md0'],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify arrays with entirely wrong members as a mismatch' do
# If we have an array with A,B,C, but it should be D,E,F => mismatch
node.default['fb_storage']['devices'] = [
array_member, array_member, array_member
]
node.default['fb_storage']['arrays'] = [raid0_array]
node.automatic[attr_name]['by_device'] = {
'/dev/nbd0p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd1p1' => { :fs_type => 'linux_raid_member' },
'/dev/nbd2p1' => { :fs_type => 'linux_raid_member' },
}
node.automatic['mdadm']['md0'] = {
'level' => 0,
'members' => ['nbd3p1', 'nbd4p1', 'nbd5p1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/nbd0' => array_member,
'/dev/nbd1' => array_member,
'/dev/nbd2' => array_member,
},
:arrays => {
'/dev/md0' => raid0_array.merge(
{ 'members' => ['/dev/nbd0p1', '/dev/nbd1p1', '/dev/nbd2p1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0'],
:mismatched_arrays => ['/dev/md0'],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'should identify extra arrays' do
device1 = {
'partitions' => [
{
'_swraid_array' => 0,
},
],
}
device2 = {
'partitions' => [
{
'_swraid_array' => 0,
},
],
}
array1 = {
'type' => 'xfs',
'mount_point' => '/data/fa',
'opts' => 'default',
'pass' => 2,
'enable_remount' => true,
'raid_level' => 1,
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => { 'fs_type' => 'xfs' },
'/dev/md0' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm'] = {
'md0' => {
'level' => 1,
'members' => ['sdb1', 'sdc1'],
},
'md1' => {
'level' => 6,
'members' => ['whatever'],
},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdb1', '/dev/sdc1'] },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => ['/dev/md1'],
},
)
end
it 'it match hybrid_xfs metadata as xfs for matched FS' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'xfs' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'matches hybrid_xfs metadata as xfs for mismatched FSes' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => { 'fs_type' => 'ext4' },
'/dev/sdc' => {},
'/dev/sdc1' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => ['/dev/md0', '/dev/sdb1'],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => [],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
it 'matches hybrid_xfs metadata as xfs for missing FSes' do
device1 = {
'partitions' => [
{
'_xfs_rt_metadata' => 0,
},
],
}
device2 = {
'partitions' => [
{
'_xfs_rt_data' => 0,
},
],
}
array1 = {
'type' => 'xfs',
'raid_level' => 'hybrid_xfs',
'mount_point' => '/data/fa',
}
node.default['fb_storage']['devices'] = [device1, device2]
node.default['fb_storage']['arrays'] = [array1]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdb1' => {},
'/dev/sdc' => {},
'/dev/sdc1' => {},
}
node.automatic['mdadm'] = {}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => device1, '/dev/sdc' => device2 },
:arrays => {
'/dev/md0' => array1.merge(
{ 'members' => ['/dev/sdc1'], 'journal' => '/dev/sdb1' },
),
},
},
)
storage = FB::Storage.new(node)
expect(storage.out_of_spec).to eq(
{
:mismatched_partitions => [],
:mismatched_filesystems => [],
:mismatched_arrays => [],
:missing_partitions => [],
:missing_filesystems => ['/dev/md0', '/dev/sdb1'],
:missing_arrays => [],
:incomplete_arrays => {},
:extra_arrays => [],
},
)
end
end
context '#all_storage' do
it 'should return all storage with one device' do
node.default['fb_storage']['devices'] = [
single_partition_device,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => { '/dev/sdb' => single_partition_device }, :arrays => {}
},
)
storage = FB::Storage.new(node)
expect(storage.all_storage).to eq(
{
:devices => ['/dev/sdb'],
:partitions => ['/dev/sdb1'],
:arrays => [],
},
)
end
it 'should return all storage with many devices' do
node.default['fb_storage']['devices'] = [
single_partition_device, double_partition_device
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/fioa' => {},
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => single_partition_device,
'/dev/fioa' => double_partition_device,
},
:arrays => {},
},
)
storage = FB::Storage.new(node)
expect(storage.all_storage).to eq(
{
:devices => ['/dev/sdb', '/dev/fioa'],
:partitions => ['/dev/sdb1', '/dev/fioa1', '/dev/fioa2'],
:arrays => [],
},
)
end
it 'should return all storage with arrays' do
node.default['fb_storage']['devices'] = [
array_member, array_member
]
node.default['fb_storage']['arrays'] = [single_array]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/fioa' => {},
'/dev/md0' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm']['md0'] = {
'level' => 1,
'members' => ['sdb1', 'fioa1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => array_member,
'/dev/fioa' => array_member,
},
:arrays => { '/dev/md0' => single_array },
},
)
storage = FB::Storage.new(node)
expect(storage.all_storage).to eq(
{
:devices => ['/dev/sdb', '/dev/fioa'],
:partitions => ['/dev/sdb1', '/dev/fioa1', '/dev/md0'],
:arrays => ['/dev/md0'],
},
)
end
it 'should return all storage with arrays, skipping as necessary' do
node.default['fb_storage']['devices'] = [
{ '_skip' => true }, { '_skip' => true },
array_member, array_member
]
node.default['fb_storage']['arrays'] = [
{ '_skip' => true },
single_array,
]
node.automatic[attr_name]['by_device'] = {
'/dev/sdb' => {},
'/dev/sdc' => {},
'/dev/sdd' => {},
'/dev/sde' => {},
'/dev/md0' => { 'fs_type' => 'ext4' },
'/dev/md1' => { 'fs_type' => 'xfs' },
}
node.automatic['mdadm']['md0'] = {
'level' => 1,
'members' => ['sdb1', 'sdc1'],
}
node.automatic['mdadm']['md1'] = {
'level' => 1,
'members' => ['sdd1', 'sde1'],
}
expect(FB::Storage).to receive(:build_mapping).and_return(
{
:disks => {
'/dev/sdb' => { '_skip' => true },
'/dev/sdc' => { '_skip' => true },
'/dev/sdd' => array_member,
'/dev/sde' => array_member,
},
:arrays => {
'/dev/md0' => { '_skip' => true },
'/dev/md1' => single_array,
},
},
)
storage = FB::Storage.new(node)
expect(storage.all_storage).to eq(
{
:devices => ['/dev/sdd', '/dev/sde'],
:partitions => ['/dev/sdd1', '/dev/sde1', '/dev/md1'],
:arrays => ['/dev/md1'],
},
)
end
end
end
context '#disks_from_automation' do
it 'returns all disks from bar' do
disks = ['sdb', 'fioa', '.', '..']
allow(File).to receive(:directory?).with(
FB::Storage::REPLACED_DISKS_DIR,
).and_return(true)
allow(Dir).to receive(:new).and_return(disks)
expect(FB::Storage.disks_from_automation).
to eq(['/dev/sdb', '/dev/fioa'])
end
end
end