cookbooks/fb_network_scripts/spec/default_spec.rb (359 lines of code) (raw):
# vim: syntax=ruby:expandtab:shiftwidth=2:softtabstop=2:tabstop=2
#
# Copyright (c) 2012-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/default'
require_relative '../libraries/rh_int_helpers'
describe FB::NetworkScripts do
context '#len2mask' do
it 'should handle class netmasks' do
FB::NetworkScripts.len2mask(24).should eq('255.255.255.0')
end
it 'should handle CIDR netmasks' do
FB::NetworkScripts.len2mask(27).should eq('255.255.255.224')
end
end
context '#v6range2list' do
it 'should make a list of IPs' do
start = 'fe80::202:c9ff:fe4f:0'
finish = 'fe80::202:c9ff:fe4f:5'
FB::NetworkScripts.v6range2list(start, finish).should eq(
[
'fe80::202:c9ff:fe4f:0/128',
'fe80::202:c9ff:fe4f:1/128',
'fe80::202:c9ff:fe4f:2/128',
'fe80::202:c9ff:fe4f:3/128',
'fe80::202:c9ff:fe4f:4/128',
'fe80::202:c9ff:fe4f:5/128',
],
)
end
end
end
RSpec.configure do |c|
c.include FB::NetworkScripts::RHInterfaceHelpers
end
describe FB::NetworkScripts::RHInterfaceHelpers do
context '#running?' do
let(:node) { Chef::Node.new }
it 'should return true for interfaces that are up' do
int = 'oogabooga0'
f = "/sys/class/net/#{int}/flags"
File.should_receive(:exist?).with(f).and_return(true)
File.should_receive(:read).with(f).and_return("0x1003\n")
running?(int, node).should eq(true)
end
# most virtual interfaces report '0x9' when up
it 'should return true for virtual interfaces up' do
int = 'oogabooga0'
f = "/sys/class/net/#{int}/flags"
File.should_receive(:exist?).with(f).and_return(true)
File.should_receive(:read).with(f).and_return("0x9\n")
running?(int, node).should eq(true)
end
it 'should return false for interfaces that are down' do
int = 'oogabooga0'
f = "/sys/class/net/#{int}/flags"
File.should_receive(:exist?).with(f).and_return(true)
File.should_receive(:read).with(f).and_return("0x1002\n")
running?(int, node).should eq(false)
end
it 'should return false for interfaces that are non-existent' do
int = 'oogabooga0'
f = "/sys/class/net/#{int}/flags"
File.should_receive(:exist?).with(f).and_return(false)
running?(int, node).should eq(false)
end
end
context '#read_ifcfg_file' do
it 'should ignore comments' do
f = '/tmp/foof'
File.should_receive(:read).with(f).and_return("# foo\n# bar\n")
read_ifcfg(f).should eq({})
end
it 'should parse keyval pairs' do
f = '/tmp/foof'
File.should_receive(:read).with(f).and_return("KEY=VAL\nKEY2=VAL2\n")
read_ifcfg(f).should eq({ 'KEY' => 'VAL', 'KEY2' => 'VAL2' })
end
it 'should handle quoted values' do
f = '/tmp/foof'
File.should_receive(:read).with(f).and_return(
"KEY=\"VAL\"\nKEY2=\"VAL2\"\n",
)
read_ifcfg(f).should eq({ 'KEY' => 'VAL', 'KEY2' => 'VAL2' })
end
it 'should handle empty values' do
f = '/tmp/foof'
File.should_receive(:read).with(f).and_return(
"KEY=VAL\nKEY2=\nKEY3=\"\"\n",
)
read_ifcfg(f).should eq({ 'KEY' => 'VAL', 'KEY2' => '', 'KEY3' => '' })
end
end
context '#get_changed_keys' do
it 'should report removed keys' do
c1 = { 'KEY1' => 'stuff here', 'KEY2' => 'more stuff' }
c2 = { 'KEY2' => 'more stuff' }
get_changed_keys(c1, c2).should eq(['KEY1'])
end
it 'should report added keys' do
c1 = { 'KEY2' => 'more stuff' }
c2 = { 'KEY1' => 'stuff here', 'KEY2' => 'more stuff' }
get_changed_keys(c1, c2).should eq(['KEY1'])
end
it 'should report modified keys' do
c1 = { 'KEY1' => 'stuff here', 'KEY2' => 'more stuff' }
c2 = { 'KEY1' => 'different here', 'KEY2' => 'more stuff' }
get_changed_keys(c1, c2).should eq(['KEY1'])
end
it 'should report combinations' do
c1 = {
'KEY1' => 'stuff here',
'KEY2' => 'more stuff',
'KEY3' => 'same stuff',
}
c2 = {
'KEY2' => 'different here',
'KEY3' => 'same stuff',
'KEY4' => 'more stuff',
}
get_changed_keys(c1, c2).should eq(['KEY1', 'KEY2', 'KEY4'])
end
it 'should see IPv6 addresses with different casing the same' do
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:FACE:0000:00a9:0000',
}
get_changed_keys(c1, c2).should eq([])
end
it 'should see IPv6 addresses with different expansion the same' do
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0:00a9:0',
}
get_changed_keys(c1, c2).should eq([])
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face::00a9:0',
}
get_changed_keys(c1, c2).should eq([])
end
it 'should see IPv6 addresses with and without /64 the same' do
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000/64',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
get_changed_keys(c1, c2).should eq([])
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000/64',
}
get_changed_keys(c1, c2).should eq([])
end
it 'should see IPv6 addresses with different CIDR as different' do
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000/128',
}
get_changed_keys(c1, c2).should eq(['IPV6ADDR'])
c1 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000/128',
}
c2 = {
'IPV6ADDR' => '2401:db00:0021:70dd:face:0000:00a9:0000',
}
get_changed_keys(c1, c2).should eq(['IPV6ADDR'])
end
it 'should see IPV6 secondary address with different casing the same' do
c1 = {
'IPV6ADDR_SECONDARIES' =>
'2401:db00:0021:70dd:face:0000:00a9:0000/64 ' +
'2803:6080:c898:74a9::1/64',
}
c2 = {
'IPV6ADDR_SECONDARIES' =>
'2401:DB00:0021:70DD:FACE:0000:00A9:0000/64 ' +
'2803:6080:C898:74A9::1/64',
}
get_changed_keys(c1, c2).should eq([])
end
it 'should see IPV6 secondary address with different expansion the same' do
c1 = {
'IPV6ADDR_SECONDARIES' =>
'2401:db00:0021:70dd:face:0000:00a9:0000/64 ' +
'2803:6080:c898:74a9::1/64',
}
c2 = {
'IPV6ADDR_SECONDARIES' =>
'2401:db00:0021:70dd:face:0:00a9:0/64 ' +
'2803:6080:c898:74a9::1/64',
}
get_changed_keys(c1, c2).should eq([])
c1 = {
'IPV6ADDR_SECONDARIES' =>
'2401:db00:0021:70dd:face:0000:00a9:0000/64 ' +
'2803:6080:c898:74a9::1/64',
}
c2 = {
'IPV6ADDR_SECONDARIES' =>
'2401:db00:0021:70dd:face::00a9:0/64 ' +
'2803:6080:c898:74a9::1/64',
}
get_changed_keys(c1, c2).should eq([])
end
end
context '#get_v6addrs' do
let(:node) { Chef::Node.new }
it 'should report v6 addresses' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'fe80::1' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
}
get_v6addrs(node, iface).should eq(['fe80::1/64'])
end
it 'should not report local v6 addresses' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'fe80::1' => {
'scope' => 'local',
'family' => 'inet6',
'prefixlen' => '64',
},
}
get_v6addrs(node, iface).should eq([])
end
it 'should not report v4 addresses' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'fe80::1' => {
'scope' => 'Link',
'family' => 'inet',
'prefixlen' => '64',
},
}
get_v6addrs(node, iface).should eq([])
end
end
context '#get_v6_changes' do
let(:node) { Chef::Node.new }
it 'should not remove primary addr' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'2401:db00:11:d0d8:face:0:47:0' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
}
config = {
'ipv6' => '2401:db00:11:d0d8:face:0:47:0',
}
get_v6_changes(node, iface, config).should eq([Set.new, Set.new])
end
it 'should not remove primary addr even if formatted differently' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
# 0-padded
'2401:db00:0011:d0d8:face:0000:0047:0' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
}
config = {
'ipv6' => '2401:db00:11:d0d8:face:0:47:0',
}
get_v6_changes(node, iface, config).should eq([Set.new, Set.new])
end
it 'should add extra addresses' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'2401:db00:11:d0d8:face:0:47:0' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
}
config = {
'ipv6' => '2401:db00:11:d0d8:face:0:47:0',
'v6secondaries' => [
'2401:ffff:6969:0:0:0:0:4/64',
],
}
get_v6_changes(node, iface, config).should eq(
[Set.new(['2401:ffff:6969::4/64']), Set.new],
)
end
it 'should remove extra addresses' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
'2401:db00:11:d0d8:face:0:47:0' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
'2401:ffff:6969:0:0:0:0:4' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
}
config = {
'ipv6' => '2401:db00:11:d0d8:face:0:47:0',
}
get_v6_changes(node, iface, config).should eq(
[Set.new, Set.new(['2401:ffff:6969::4/64'])],
)
end
it 'should handle complex combinations' do
iface = 'ooga0'
node.default['network']['interfaces'][iface]['addresses'] = {
# primary address
'2401:db00:11:d0d8:face:0:47:0' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
# address that is staying, but not in compacted format
'2401:ffff:6969:0:0:0:0:4' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
# address that is being removed
'2401:1111:0666:0:0:0:0:99' => {
'scope' => 'global',
'family' => 'inet6',
'prefixlen' => '64',
},
'fe80::202:c9ff:fe4f:bae0' => {
'family' => 'inet6',
'prefixlen' => '64',
'scope' => 'Link',
},
}
config = {
'ipv6' => '2401:db00:11:d0d8:face:0:47:0',
'v6secondaries' => [
# already here and staying
'2401:ffff:6969:0:0:0:0:4/64',
# being added
'2401:69ff:6669:0:0:0:0:8/64',
],
}
get_v6_changes(node, iface, config).should eq(
[
Set.new(['2401:69ff:6669::8/64']),
Set.new(['2401:1111:666::99/64']),
],
)
end
end
end