cookbooks/fb_helpers/spec/node_spec.rb (249 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 '../libraries/fb_helpers'
require_relative '../libraries/node_methods'
describe 'Chef::Node' do
let(:node) { Chef::Node.new }
let(:attr_name) do
if node['filesystem2']
'filesystem2'
else
'filesystem'
end
end
context 'Chef::Node.fs_size_gb' do
before(:each) do
node.default[attr_name]['by_mountpoint']['/'] = {
'kb_size' => '959110616',
'devices' => ['/dev/sda3'],
}
end
it 'should return size in GB' do
Chef::Log.should_not_receive(:warn)
node.fs_size_gb('/').should eq(914.6791610717773)
end
it 'should warn and return true' do
Chef::Log.should_receive(:warn)
node.fs_size_gb('/fake')
end
end
context 'Chef::Node.fs_size_kb' do
before do
node.default[attr_name]['by_mountpoint']['/'] = {
'kb_size' => '959110616',
'devices' => ['/dev/sda3'],
}
end
it 'should return size in KB' do
Chef::Log.should_not_receive(:warn)
node.fs_size_kb('/').should eq(959110616.0)
end
end
context 'Chef::Node.fs_value' do
before do
node.default[attr_name]['by_mountpoint']['/'] = {
'kb_size' => '959110616',
'kb_available' => '810110218',
'kb_used' => '149000398',
'percent_used' => '15%',
'devices' => ['/dev/sda3'],
}
end
it 'should return various values as requested' do
Chef::Log.should_not_receive(:warn)
node.fs_value('/', 'size').should eq(959110616.0)
node.fs_value('/', 'available').should eq(810110218.0)
node.fs_value('/', 'used').should eq(149000398.0)
node.fs_value('/', 'percent').should eq(15.0)
end
it 'should throw an error on invalid args' do
expect { node.fs_value('/', 'wasdfa') }.to raise_error(RuntimeError)
end
end
context 'Chef::Node.in_flexible_shard?' do
before do
# Taken from dev1020.prn2.facebook.com
# This will be shard 66 in 100
# or 466 in 1000
# or 116 in 255
# or 2 in 12
node.default['shard_seed'] = 244690466
node.default['fqdn'] = 'dev1020.prn2.facebook.com'
end
it 'should return true if we are in flexible_shard' do
node.in_flexible_shard?(66, 100).should eq(true)
node.in_flexible_shard?(99, 100).should eq(true)
node.in_flexible_shard?(466, 1000).should eq(true)
node.in_flexible_shard?(116, 255).should eq(true)
node.in_flexible_shard?(2, 12).should eq(true)
end
it 'should return false if we are not in flexible_shard' do
node.in_flexible_shard?(0, 100).should eq(false)
node.in_flexible_shard?(65, 100).should eq(false)
node.in_flexible_shard?(465, 1000).should eq(false)
node.in_flexible_shard?(115, 255).should eq(false)
node.in_flexible_shard?(1, 12).should eq(false)
end
it 'should have consistent overflow behaviour' do
node.in_flexible_shard?(100, 100).should eq(true)
node.in_flexible_shard?(199, 100).should eq(true)
end
it 'should have consistent underflow behaviour' do
node.in_flexible_shard?(-1, 100).should eq(false)
node.in_flexible_shard?(-99, 100).should eq(false)
end
end
context 'Chef::Node.get_flexible_shard' do
before do
# Taken from dev1020.prn2.facebook.com
# This will be shard 66 in 100
node.default['shard_seed'] = 244690466
node.default['fqdn'] = 'dev1020.prn2.facebook.com'
end
it 'should return correct shard on multiple calls' do
node.get_flexible_shard(100).should eq(66)
# Should remain 66 on second calling
node.get_flexible_shard(100).should eq(66)
end
end
context 'Chef::Node.in_shard?' do
before do
# Taken from dev1020.prn2.facebook.com
# This will be shard 66 in 100
node.default['shard_seed'] = 244690466
node.default['fqdn'] = 'dev1020.prn2.facebook.com'
end
it 'should return true if we are in shard' do
node.in_shard?(66).should eq(true)
# Should remain true on second calling
node.in_shard?(66).should eq(true)
node.in_shard?(67).should eq(true)
end
it 'should return false if we are not in shard' do
node.in_shard?(65).should eq(false)
# Should remain false on second calling
node.in_shard?(65).should eq(false)
end
it 'should retain legacy overflow behaviour' do
# avoid using literals so linters don't fire
[100, 199].each do |v|
node.in_shard?(v).should eq(true)
end
end
it 'should retain legacy underflow behaviour' do
# avoid using literals so linters don't fire
[-1, -99].each do |v|
node.in_shard?(v).should eq(false)
end
end
end
context 'Chef::Node.timeshard_parsed_values' do
# for the purposes of this test we want a consistent shard_seed
# this will map to 51336 seconds into a 24h (86400 second) period.
before do
node.default['fb']['shard_seed'] = 31328136
end
{
'24h' => (24 * 60 * 60),
'1h' => (60 * 60),
'7d' => (7 * 24 * 60 * 60),
}.each do |duration, seconds|
it "should return the correct values for each duration - #{duration}" do
our_shard = node.get_flexible_shard(seconds)
start_time = Time.now - (our_shard - 1)
duration_value = seconds
time_threshold_value = our_shard + start_time.tv_sec
node.timeshard_parsed_values(
start_time.to_s,
duration,
).should eq({
'start_time' => start_time.tv_sec,
'duration' => duration_value,
'time_threshold' => time_threshold_value,
})
end
end
end
context 'Chef::Node.in_timeshard?' do
# for the purposes of this test we want a consistent shard_seed
# this will map to 51336 seconds into a 24h (86400 second) period.
before do
node.default['fb']['shard_seed'] = 31328136
end
{
'24h' => (24 * 60 * 60),
'1h' => (60 * 60),
'7d' => (7 * 24 * 60 * 60),
}.each do |duration, seconds|
it "should return false the second before our shard - #{duration}" do
our_shard = node.get_flexible_shard(seconds)
node.in_timeshard?(
(Time.now - (our_shard - 1)).to_s,
duration,
).should eq(false)
end
it "should return true the second of our shard - #{duration}" do
our_shard = node.get_flexible_shard(seconds)
node.in_timeshard?(
(Time.now - our_shard).to_s,
duration,
).should eq(true)
end
it "should return true much later than our shard - #{duration}" do
node.in_timeshard?(
(Time.now - (seconds - 1)).to_s,
duration,
).should eq(true)
end
it "should return false much earlier than our shard - #{duration}" do
node.in_timeshard?(
(Time.now - 1).to_s,
duration,
).should eq(false)
end
it "should fail if start_time is an invalid time - #{duration}" do
expect do
node.in_timeshard?(
'2018-14-1 9:00:00',
duration,
)
end.to raise_error(RuntimeError)
end
end
it 'should return true for valid times w single digits' do
last_month = Date.today.prev_month
start_time = Time.new(last_month.year, last_month.month, 1, 0, 0, 0)
# Build a string that always has a single digit hour and day value.
start_time = start_time.strftime('%Y-%m-%-d %-H:%M:%S')
node.in_timeshard?(
start_time,
'40d',
).should eq(true)
end
end
context 'Chef::Node.systemd?' do
it 'should check the running system for running systemd' do
::File.stub(:directory?).with(anything).and_call_original
::File.stub(:directory?).with('/run/systemd/system').
and_return true
node.systemd?.should eq(true)
end
it 'should check the running system for systemd not running' do
::File.stub(:directory?).with(anything).and_call_original
::File.stub(:directory?).with('/run/systemd/system').
and_return false
node.systemd?.should eq(false)
end
end
context 'Chef::Node.validate_and_fail_on_dynamic_addresses' do
it 'no addresses; no failure' do
node.validate_and_fail_on_dynamic_addresses
end
before do
node.automatic['network']['interfaces']['eth0']['addresses'][
'2001:db8:3c4d:15::1a2f:1a2b']
end
it 'no address family; no failure' do
node.validate_and_fail_on_dynamic_addresses
end
before do
node.automatic['network']['interfaces']['eth0']['addresses'][
'2001:db8:3c4d:15::1a2f:1a2b']['family'] = 'inet6'
end
it 'no tags; no failure' do
node.validate_and_fail_on_dynamic_addresses
end
it 'no dynamic tags; no failure' do
node.automatic['network']['interfaces']['eth0']['addresses'][
'2001:db8:3c4d:15::1a2f:1a2b']['tags'] = ['scope', 'global']
node.validate_and_fail_on_dynamic_addresses
end
it 'should fail because of a dynamic address' do
node.automatic['network']['interfaces']['eth0']['addresses'][
'2001:db8:3c4d:15::1a2f:1a2b']['tags'] = ['scope', 'global', 'dynamic']
expect do
node.validate_and_fail_on_dynamic_addresses
end.to raise_error(RuntimeError)
end
end
end