cookbooks/fb_fstab/spec/public_spec.rb (1,085 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 'chef/node'
# we know relative_requires is bad, but it allows us to handle pathing
# differences between internal repo and github
require_relative '../libraries/default'
require_relative '../libraries/provider'
describe 'FB::Fstab' do
let(:node) { Chef::Node.new }
let(:attr_name) do
if node['filesystem2']
'filesystem2'
else
'filesystem'
end
end
full_contents = <<EOF
#
# /etc/fstab
# Created by anaconda on Mon May 13 06:43:59 2013
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
UUID=28137926-9c39-44c0-90d3-3b158fc97ff9 / ext4 defaults,discard 1 1
UUID=9ebfe8b9-c188-4cda-8383-393deb0ac59c /boot ext3 defaults 1 2
UUID=2ace4f5f-c8c5-4d3a-a027-d12076bdab0c swap swap defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
tmpfs /dev/shm tmpfs defaults,size=4G 0 0
/dev/crazy /mount/foo xfs defaults 0 0
EOF
base_contents = <<EOF
UUID=28137926-9c39-44c0-90d3-3b158fc97ff9 / ext4 defaults,discard 1 1
UUID=9ebfe8b9-c188-4cda-8383-393deb0ac59c /boot ext3 defaults 1 2
UUID=2ace4f5f-c8c5-4d3a-a027-d12076bdab0c swap swap defaults 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
sysfs /sys sysfs defaults 0 0
proc /proc proc defaults 0 0
tmpfs /dev/shm tmpfs defaults,size=4G 0 0
EOF
context 'generate_base_fstab' do
it 'should not regenerate base fstab' do
File.should_receive(:exist?).with(FB::Fstab::BASE_FILENAME).
and_return(true)
File.should_receive(:size?).with(FB::Fstab::BASE_FILENAME).
and_return(true)
File.should_not_receive(:open)
FB::Fstab.generate_base_fstab
end
it 'should generate new base fstab' do
File.should_receive(:exist?).with(FB::Fstab::BASE_FILENAME).
and_return(false)
FileUtils.should_receive(:cp).and_return(nil)
FileUtils.should_receive(:chmod).and_return(
['/root/fstab.before_fb_fstab'],
)
File.should_receive(:read).and_return(full_contents)
File.should_receive(:write).with(FB::Fstab::BASE_FILENAME, base_contents)
FB::Fstab.generate_base_fstab
end
end
context 'determine_base_fstab_entries' do
it 'should regenerate base fstab properly' do
FB::Fstab.determine_base_fstab_entries(full_contents).
should eq(base_contents)
end
it 'should not include exta tmpfs mounts' do
FB::Fstab.determine_base_fstab_entries(full_contents).
should eq(base_contents)
end
end
context 'autofs_parent' do
before(:each) do
node.default[attr_name]['by_pair']['foo:/bar/baz,/mnt/foo'] = {
'device' => 'foo:/bar/baz',
'mount' => '/mnt/foo',
'fs_type' => 'autofs',
}
end
it 'should return the autofs parent when one exists' do
FB::Fstab.autofs_parent('/mnt/foo/bar', node).should eq('/mnt/foo')
end
it 'should return the autofs dir when same as mount' do
FB::Fstab.autofs_parent('/mnt/foo', node).should eq('/mnt/foo')
end
it 'should return false if no conflict with autofs' do
FB::Fstab.autofs_parent('/mnt/waka', node).should eq(false)
end
end
context 'label_to_device' do
it 'should find the right label' do
node.default[attr_name]['by_device'] = {
'foo' => {
'label' => 'label1',
},
'bar' => {
'label' => 'label2',
},
}
FB::Fstab.label_to_device('label2', node).should eq('bar')
end
it 'should not get confused by fs without label' do
node.default[attr_name]['by_device'] = {
'foo' => {
'uuid' => 'uuid1',
},
'bar' => {
'label' => 'label2',
},
}
FB::Fstab.label_to_device('label2', node).should eq('bar')
end
it 'should fail on missing label' do
node.default[attr_name]['by_device'] = {
'foo' => {
'uuid' => 'uuid1',
},
'bar' => {
'label' => 'label2',
},
}
expect do
FB::Fstab.label_to_device('label1', node)
end.to raise_error(RuntimeError)
end
end
context 'uuid_to_device' do
it 'should find the right uuid' do
node.default[attr_name]['by_device'] = {
'foo' => {
'uuid' => 'uuid1',
},
'bar' => {
'uuid' => 'uuid2',
},
}
FB::Fstab.uuid_to_device('uuid2', node).should eq('bar')
end
it 'should not get confused by fs without uuid' do
node.default[attr_name]['by_device'] = {
'foo' => {
'label' => 'label1',
},
'bar' => {
'uuid' => 'uuid2',
},
}
FB::Fstab.uuid_to_device('uuid2', node).should eq('bar')
end
it 'should fail on missing uuid' do
node.default[attr_name]['by_device'] = {
'foo' => {
'label' => 'label1',
},
'bar' => {
'uuid' => 'uuid2',
},
}
expect do
FB::Fstab.uuid_to_device('uuid1', node)
end.to raise_error(RuntimeError)
end
end
context 'parse_in_maint_file' do
it 'should return an empty array if file does not exist' do
File.should_receive(:exist?).with(FB::Fstab::IN_MAINT_DISKS_FILENAME).
and_return(false)
FB::Fstab.parse_in_maint_file(FB::Fstab::IN_MAINT_DISKS_FILENAME).
should eq([])
end
it 'should delete stale files' do
stat = double('FSstat')
stat.should_receive(:mtime).and_return(Time.new - 60 * 60 * 24 * 8)
File.should_receive(:exist?).with(FB::Fstab::IN_MAINT_DISKS_FILENAME).
and_return(true)
File.should_receive(:stat).with(FB::Fstab::IN_MAINT_DISKS_FILENAME).
and_return(stat)
File.should_receive(:unlink).with(FB::Fstab::IN_MAINT_DISKS_FILENAME).
and_return(true)
FB::Fstab.parse_in_maint_file(FB::Fstab::IN_MAINT_DISKS_FILENAME).
should eq([])
end
end
context 'get_in_maint_mounts' do
it 'should canonicalize mount paths' do
expect(FB::Fstab).to receive(:parse_in_maint_file).
with(FB::Fstab::IN_MAINT_MOUNTS_FILENAME).and_return(['/mnt/d0/'])
FB::Fstab.get_in_maint_mounts.should eq(['/mnt/d0'])
end
end
context 'get_unmasked_base_mounts' do
let(:default_ret) do
{
'/dev/sda1' => {
'mount_point' => '/',
'type' => 'ext4',
'opts' => 'defaults,discard',
'dump' => '1',
'pass' => '1',
},
'/dev/sda2' => {
'mount_point' => '/boot',
'type' => 'ext3',
'opts' => 'defaults',
'dump' => '1',
'pass' => '2',
},
'/dev/sda3' => {
'mount_point' => 'swap',
'type' => 'swap',
'opts' => 'defaults',
'dump' => '0',
'pass' => '0',
},
'devpts' => {
'mount_point' => '/dev/pts',
'type' => 'devpts',
'opts' => 'gid=5,mode=620',
'dump' => '0',
'pass' => '0',
},
'sysfs' => {
'mount_point' => '/sys',
'type' => 'sysfs',
'opts' => 'defaults',
'dump' => '0',
'pass' => '0',
},
'proc' => {
'mount_point' => '/proc',
'type' => 'proc',
'opts' => 'defaults',
'dump' => '0',
'pass' => '0',
},
'tmpfs' => {
'mount_point' => '/dev/shm',
'type' => 'tmpfs',
'opts' => 'defaults,size=4G',
'dump' => '0',
'pass' => '0',
},
}
end
before do
node.default[attr_name]['by_device'] = {
'/dev/sda1' => {
'mounts' => ['/'],
'fs_type' => 'ext4',
'uuid' => '28137926-9c39-44c0-90d3-3b158fc97ff9',
'label' => '/',
},
'/dev/sda2' => {
'mounts' => ['/boot'],
'fs_type' => 'ext3',
'uuid' => '9ebfe8b9-c188-4cda-8383-393deb0ac59c',
'label' => '/boot',
},
'/dev/sda3' => {
'fs_type' => 'swap',
'uuid' => '2ace4f5f-c8c5-4d3a-a027-d12076bdab0c',
},
}
node.default['fb_fstab']['mounts'] = {}
node.default['fb_swap']['enabled'] = true
end
it 'should parse base mounts correctly' do
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(base_contents)
m = FB::Fstab.get_unmasked_base_mounts(:hash, node)
m.should eq(default_ret)
m = FB::Fstab.get_unmasked_base_mounts(:lines, node).join("\n") + "\n"
m.should eq(base_contents)
end
it 'should drop swap if masked' do
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(base_contents)
node.default['fb_fstab']['exclude_base_swap'] = true
m = FB::Fstab.get_unmasked_base_mounts(:hash, node)
default_ret.delete('/dev/sda3')
m.should eq(default_ret)
end
it 'should not return overridden mounts' do
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(base_contents)
node.default['fb_fstab']['mounts'] = {
'phild override' => {
'device' => '/dev/sda1',
'mount_point' => '/',
'type' => 'ext4',
'opts' => 'defaults,discard,noatume',
'dump' => '1',
'pass' => '1',
},
}
m = FB::Fstab.get_unmasked_base_mounts(:hash, node)
default_ret.delete('/dev/sda1')
m.should eq(default_ret)
end
it 'should raise an exception if base has bad label' do
contents = base_contents +
"LABEL=nonexistent / ext4 opts 0 0\n"
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(contents)
expect { FB::Fstab.get_unmasked_base_mounts(:hash, node) }.
to raise_error(RuntimeError)
end
it 'should not raise an exception if base has bad UUID overwridden' do
contents = base_contents +
"UUID=nonexistent / ext4 opts 0 0\n"
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(contents)
node.default['fb_fstab']['mounts'] = {
'phild override' => {
'device' => 'LABEL=/',
'mount_point' => '/',
'type' => 'ext4',
'opts' => 'defaults,discard,noatume',
'dump' => '1',
'pass' => '1',
},
}
m = FB::Fstab.get_unmasked_base_mounts(:hash, node)
default_ret.delete('/dev/sda1')
m.should eq(default_ret)
end
it 'should raise an exception if user specifies bad label' do
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(base_contents)
node.default['fb_fstab']['mounts'] = {
'phild mount' => {
'device' => 'LABEL=nonexistent',
'mount_point' => '/stuff',
'type' => 'ext4',
'opts' => 'defaults,discard,noatume',
'dump' => '1',
'pass' => '1',
},
}
expect { FB::Fstab.get_unmasked_base_mounts(:hash, node) }.
to raise_error(RuntimeError)
end
it 'should not raise an exception if user specifies bad label and' +
'allow_failure' do
expect(File).to receive(:read).with(FB::Fstab::BASE_FILENAME).
and_return(base_contents)
node.default['fb_fstab']['mounts'] = {
'phild mount' => {
'device' => 'LABEL=nonexistent',
'mount_point' => '/stuff',
'type' => 'ext4',
'opts' => 'defaults,discard,noatume',
'dump' => '1',
'pass' => '1',
'allow_mount_failure' => true,
'allow_remount_failure' => true,
},
}
m = FB::Fstab.get_unmasked_base_mounts(:hash, node)
m.should eq(default_ret)
end
end
end
describe 'FB::FstabProvider', :include_provider do
include FB::FstabProvider
let(:node) { Chef::Node.new }
let(:attr_name) do
if node['filesystem2']
'filesystem2'
else
'filesystem'
end
end
before do
node.default[attr_name] = {
'by_pair' => {},
'by_device' => {},
'by_mountpoint' => {},
}
end
context 'compare_opts' do
before(:each) do
node.default['fb_fstab']['ignorable_opts'] = []
end
it 'should find identical things identical' do
compare_opts(
'rw,size=1G',
'rw,size=1G',
).should eq(true)
end
it 'should find different-order strings identical' do
compare_opts(
'size=1G,rw',
'rw,size=1G',
).should eq(true)
end
it 'should find arrays and strings identical' do
compare_opts(
'rw,size=1G',
['size=1G', 'rw'],
).should eq(true)
end
it 'should treat missing-rw opts as identical' do
compare_opts(
'size=1G',
['size=1G', 'rw'],
).should eq(true)
end
it 'should not treat ro and rw as the same' do
compare_opts(
'size=1G,ro',
['size=1G', 'rw'],
).should eq(false)
end
it 'should handle arrays the same' do
compare_opts(
['size=1G'],
['size=1G', 'rw'],
).should eq(true)
end
it 'should catch different sizes as different opts' do
compare_opts(
['rw', 'size=2G'],
['size=1G', 'rw'],
).should eq(false)
end
it 'should honor ignored string opts' do
node.default['fb_fstab']['ignorable_opts'] << 'nofail'
compare_opts(
['rw', 'nofail', 'noatime'],
['rw', 'noatime'],
).should eq(true)
end
it 'should honor ignored regex opts' do
node.default['fb_fstab']['ignorable_opts'] << /^addr=.*/
compare_opts(
['rw', 'addr=10.0.0.1', 'noatime'],
['rw', 'noatime'],
).should eq(true)
end
it 'should normalize size opts' do
compare_opts(
'size=4K',
'size=4096',
).should eq(true)
compare_opts(
'size=4M',
'size=4194304',
).should eq(true)
compare_opts(
'size=4g',
'size=4294967296',
).should eq(true)
compare_opts(
'size=4t',
'size=4398046511104',
).should eq(true)
end
it 'should treat sizes it does not understand as opaque' do
compare_opts(
'size=4L',
'size=4L',
).should eq(true)
compare_opts(
'size=4L',
'size=4',
).should eq(false)
compare_opts(
'size=4L',
'size=4T',
).should eq(false)
end
it 'should not normalize different values to be the same' do
compare_opts(
'size=4K',
'size=4000',
).should eq(false)
end
end
context 'compare_fstype' do
before(:each) do
node.default['fb_fstab']['type_normalization_map'] = {}
end
it 'should see identical types as identical' do
compare_fstype('xfs', 'xfs').should eq(true)
end
it 'should not see auto as the same as anything else' do
compare_fstype('xfs', 'auto').should eq(false)
end
it 'should not see auto as the same as anything else - left' do
compare_fstype('auto', 'ext4').should eq(false)
end
it 'should see different things as different' do
compare_fstype('xfs', 'ext4').should eq(false)
end
it 'should normalize types according to the map' do
node.default['fb_fstab']['type_normalization_map']['fuse.gluster'] =
'gluster'
compare_fstype('fuse.gluster', 'gluster')
end
end
context 'should_keep' do
mounted_sdc1 = {
'device' => '/dev/sdc1',
'mount' => '/mnt/foo',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
}
basemounts_sdc1 = {
'/dev/sdc1' => {
'mount_point' => '/mnt/foo',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
},
}
desired_sdc1 = {
'extradrive' => {
'device' => '/dev/sdc1',
'type' => 'xfs',
'opts' => 'rw',
'mountpoint' => '/mnt/foo',
},
}
it 'should keep identical desired fs' do
should_keep(
mounted_sdc1,
desired_sdc1,
{},
).should eq(true)
end
it 'should keep identical base fs' do
should_keep(
mounted_sdc1,
{},
basemounts_sdc1,
).should eq(true)
end
it 'should not keep random fs' do
should_keep(
{
'device' => '/dev/sdd1',
'mount' => '/mnt/bar',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
},
desired_sdc1,
{},
).should eq(false)
end
it 'should keep desired devices mounted elsewhere' do
should_keep(
{
'device' => '/dev/sdc1',
'mount' => '/mnt/bar',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
},
desired_sdc1,
{},
).should eq(true)
end
it 'should not keep base devices mounted elsewhere' do
should_keep(
{
'device' => '/dev/sdc1',
'mount' => '/mnt/bar',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
},
{},
basemounts_sdc1,
).should eq(false)
end
it 'should keep autofs-parented mounts' do
node.default[attr_name]['by_pair']['auto.waka,/foo'] = {
'device' => 'auto.waka',
'mount' => '/foo',
'fs_type' => 'autofs',
}
should_keep(
{
'device' => 'some.host:/dev/stupid',
'mount' => '/foo/bar',
'fs_type' => 'nfs',
},
{},
{},
).should eq(true)
end
it 'should keep autofs-parented mounts - non NFS' do
node.default[attr_name]['by_pair']['auto.waka,/foo'] = {
'device' => 'auto.waka',
'mount' => '/foo',
'fs_type' => 'autofs',
}
should_keep(
{
'device' => 'some.host:/dev/stupid',
'mount' => '/foo/bar',
'fs_type' => 'fuse',
},
{},
{},
).should eq(true)
end
it 'should not keep non-autofs-parented NFS mounts' do
node.default[attr_name]['by_pair']['auto.waka,/foo'] = {
'device' => 'auto.waka',
'mount' => '/foo',
'fs_type' => 'autofs',
}
should_keep(
{
'device' => 'some.host:/dev/stupid',
'mount' => '/thing/bar',
'fs_type' => 'fuse',
},
{},
{},
).should eq(false)
end
end
context 'tmpfs_mount_status' do
before(:each) do
node.default['fb_fstab']['ignorable_opts'] = []
node.default['fb_fstab']['type_normalization_map'] = {}
end
it 'should detect oldschool tmpfs as the same' do
node.default[attr_name]['by_pair']['tmpfs,/mnt/waka'] = {
'device' => 'tmpfs',
'mount' => '/mnt/waka',
'fs_type' => 'tmpfs',
'mount_options' => ['size=100M', 'rw'],
}
node.default[attr_name]['by_mountpoint']['/mnt/waka'] = {
'devices' => ['tmpfs'],
'fs_type' => 'tmpfs',
'mount_options' => ['size=100M', 'rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => 'awesomesauce',
'mount_point' => '/mnt/waka',
'type' => 'tmpfs',
'opts' => 'size=100M,rw',
},
}
Chef::Log.should_receive(:warn).with(
'fb_fstab: Treating ["tmpfs"] on /mnt/waka the same as awesomesauce ' +
'on /mnt/waka because they are both tmpfs.',
)
tmpfs_mount_status(
desired_mounts['awesomemount'],
).should eq(:same)
end
it 'should detect identical filesystems as such' do
node.default[attr_name]['by_pair']['awesomesauce,/mnt/waka'] = {
'device' => 'awesomesauce',
'mount' => '/mnt/waka',
'fs_type' => 'tmpfs',
'mount_options' => ['size=100M', 'rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => 'awesomesauce',
'mount_point' => '/mnt/waka',
'type' => 'tmpfs',
'opts' => 'size=100M,rw',
},
}
tmpfs_mount_status(
desired_mounts['awesomemount'],
).should eq(:same)
end
it 'should detect remounts' do
node.default[attr_name]['by_pair']['awesomesauce,/mnt/waka'] = {
'device' => 'awesomesauce',
'mount' => '/mnt/waka',
'fs_type' => 'tmpfs',
'mount_options' => ['size=100M', 'rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => 'awesomesauce',
'mount_point' => '/mnt/waka',
'type' => 'tmpfs',
'opts' => 'size=200M,rw',
},
}
tmpfs_mount_status(
desired_mounts['awesomemount'],
).should eq(:remount)
end
it 'should detect conflict' do
node.default[attr_name]['by_pair']['/dev/sdc1,/mnt/waka'] = {
'device' => '/dev/sdc1',
'mount' => '/mnt/waka',
'fs_type' => 'tmpfs',
'mount_options' => ['size=100M', 'rw'],
}
node.default[attr_name]['by_mountpoint']['/mnt/waka'] = {
'device' => '/dev/sdc1',
'mount' => '/mnt/waka',
'fs_type' => 'xfs',
'mount_options' => ['defaults'],
}
desired_mounts = {
'awesomemount' => {
'device' => 'awesomesauce',
'mount_point' => '/mnt/waka',
'type' => 'tmpfs',
'opts' => 'size=200M,rw',
},
}
tmpfs_mount_status(
desired_mounts['awesomemount'],
).should eq(:conflict)
end
it 'should detect missing fs' do
node.default[attr_name] = {
'by_pair' => {},
'by_device' => {},
'by_mountpoint' => {},
}
desired_mounts = {
'awesomemount' => {
'device' => 'awesomesauce',
'mount_point' => '/mnt/waka',
'type' => 'tmpfs',
'opts' => 'size=200M,rw',
},
}
tmpfs_mount_status(
desired_mounts['awesomemount'],
).should eq(:missing)
end
end
context 'mount_status' do
before(:each) do
node.default['fb_fstab']['ignorable_opts'] = []
node.default['fb_fstab']['type_normalization_map'] = {}
end
it 'should detect identical mounts as such' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:same)
end
it 'should detect identical mounts subvolumes' do
node.default[attr_name]['by_device']['/dev/sdd1'] = {
'device' => '/dev/sdd1',
'mounts' => '/mnt/d0',
'fs_type' => 'btrfs',
'mount_options' => ['noatime', 'subvolid=123'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d1',
'type' => 'btrfs',
'opts' => 'subvolid=123',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:moved)
end
it 'should detect remount needed' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:remount)
end
it 'should detect moved filesystems - with different opts' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
}
node.default[attr_name]['by_device']['/dev/sdd1'] = {
'mounts' => ['/mnt/d0'],
'fs_type' => 'xfs',
'mount_options' => ['rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d1',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:moved)
end
it 'should detect handle auto as not an fstype conflict' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'auto',
'mount_options' => ['rw', 'noatime'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'ext4',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:same)
end
it 'should detect fstype conflict - with different opts' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'xfs',
'mount_options' => ['rw'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'ext4',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:conflict)
end
it 'should detect something-already-there conflict' do
node.default[attr_name]['by_pair']['/dev/sdd1,/mnt/d0'] = {
'device' => '/dev/sdd1',
'mount' => '/mnt/d0',
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
}
node.default[attr_name]['by_device']['/dev/sdd1'] = {
'mounts' => ['/mnt/d0'],
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
}
node.default[attr_name]['by_mountpoint']['/mnt/d0'] = {
'devices' => ['/dev/sdd1'],
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sde1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:conflict)
end
it 'should detect missing filesystems' do
node.default[attr_name]['by_pair']['/dev/sda1,/mnt/waka'] = {
'device' => '/dev/sda1',
'mount' => '/mnt/waka',
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:missing)
end
it 'should detect missing filesystems even if device is in ohai' do
node.default[attr_name]['by_pair'] = {
'/dev/sda1,/mnt/waka' => {
'device' => '/dev/sda1',
'mount' => '/mnt/waka',
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
},
# No 'mount' entry, it's not mounted
'/dev/sdd1,' => {
'device' => '/dev/sdd1',
'fs_type' => 'xfs',
'uuid' => '4e91b8f3-5fce-47a1-ba05-56f1cfa1acb7',
'label' => '/mnt/d0',
},
}
node.default[attr_name]['by_device'] = {
'/dev/sda1' => {
'mounts' => ['/mnt/waka'],
'fs_type' => 'xfs',
'mount_options' => ['rw', 'noatime'],
},
# No 'mount' entry, it's not mounted
'/dev/sdd1' => {
'mounts' => [],
'fs_type' => 'xfs',
'uuid' => '4e91b8f3-5fce-47a1-ba05-56f1cfa1acb7',
'label' => '/mnt/d0',
},
}
desired_mounts = {
'awesomemount' => {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
},
}
mount_status(
desired_mounts['awesomemount'],
).should eq(:missing)
end
end
context 'mount' do
it 'should attempt to mount by mount_point' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
}
expect(node).to receive(:systemd?).and_return(false)
File.should_receive(:exist?).with(desired_mount['mount_point']).
and_return(true)
so = double('FSshell_out1')
so.should_receive(:run_command).and_return(so)
so.should_receive(:error?).and_return(false)
so.should_receive(:error!).and_return(nil)
Mixlib::ShellOut.should_receive(:new).with(
"cd /dev/shm && /bin/mount #{desired_mount['mount_point']}",
).and_return(so)
mount(desired_mount, [], []).should eq(true)
end
it 'should attempt to mount by systemd mount unit on systemd hosts' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
}
expect(node).to receive(:systemd?).and_return(true)
File.should_receive(:exist?).with(desired_mount['mount_point']).
and_return(true)
so = double('FSshell_out2')
so.should_receive(:run_command).and_return(so)
so.should_receive(:error!).and_return(nil)
so.should_receive(:stdout).and_return('thisisaunit')
so2 = double('FSshell_out1')
so2.should_receive(:run_command).and_return(so2)
so2.should_receive(:error?).and_return(false)
so2.should_receive(:error!).and_return(nil)
Mixlib::ShellOut.should_receive(:new).with(
"/bin/systemd-escape -p --suffix=mount #{desired_mount['mount_point']}",
).and_return(so)
Mixlib::ShellOut.should_receive(:new).with(
'/bin/systemctl start thisisaunit',
).and_return(so2)
mount(desired_mount, [], []).should eq(true)
end
it 'should raise failures on mount failure' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
}
expect(node).to receive(:systemd?).and_return(false)
File.should_receive(:exist?).with(desired_mount['mount_point']).
and_return(true)
so = double('FSshell_out2')
so.should_receive(:run_command).and_return(so)
so.should_receive(:error?).and_return(true)
so.should_receive(:error!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
Mixlib::ShellOut.should_receive(:new).with(
"cd /dev/shm && /bin/mount #{desired_mount['mount_point']}",
).and_return(so)
expect do
mount(desired_mount, [], [])
end.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
end
it 'should not raise failures on mount failure, if failure allowed' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
'allow_mount_failure' => true,
'allow_remount_failure' => true,
}
expect(node).to receive(:systemd?).and_return(false)
File.should_receive(:exist?).with(desired_mount['mount_point']).
and_return(true)
so = double('FSshell_out3')
so.should_receive(:run_command).and_return(so)
so.should_receive(:error?).and_return(true)
Mixlib::ShellOut.should_receive(:new).with(
"cd /dev/shm && /bin/mount #{desired_mount['mount_point']}",
).and_return(so)
mount(desired_mount, [], []).should eq(true)
end
it 'should not try to mount in-maintenance disks' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
'mp_perms' => '0700',
'mp_owner' => 'nobody',
'mp_group' => 'nobody',
}
mount(desired_mount, ['/dev/sdd1'], []).should eq(true)
end
it 'should not try to mount in-maintenance mounts' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
'mp_perms' => '0700',
'mp_owner' => 'nobody',
'mp_group' => 'nobody',
}
mount(desired_mount, [], ['/mnt/d0']).should eq(true)
end
it 'should create the mountpoint for you' do
desired_mount = {
'device' => '/dev/sdd1',
'mount_point' => '/mnt/d0',
'type' => 'xfs',
'opts' => 'rw,noatime',
'mp_perms' => '0700',
'mp_owner' => 'nobody',
'mp_group' => 'nobody',
}
expect(node).to receive(:systemd?).and_return(false)
File.should_receive(:exist?).with(desired_mount['mount_point']).
and_return(false)
FileUtils.should_receive(:mkdir_p).with(desired_mount['mount_point'],
:mode => 0755).and_return(true)
FileUtils.should_receive(:chmod).with(
desired_mount['mp_perms'].to_i(8), desired_mount['mount_point']
).and_return(true)
FileUtils.should_receive(:chown).with(
desired_mount['mp_owner'], desired_mount['mp_group'],
desired_mount['mount_point']
).and_return(true)
so = double('FSshell_out4')
so.should_receive(:run_command).and_return(so)
so.should_receive(:error?).and_return(false)
so.should_receive(:error!).and_return(nil)
Mixlib::ShellOut.should_receive(:new).with(
"cd /dev/shm && /bin/mount #{desired_mount['mount_point']}",
).and_return(so)
mount(desired_mount, [], []).should eq(true)
end
end
end