Skip to content

Commit

Permalink
WIP: first auto-bond implementation
Browse files Browse the repository at this point in the history
refs #23851
  • Loading branch information
Thomas-Gelf committed Jun 8, 2018
1 parent 67c2fde commit 2f33af8
Show file tree
Hide file tree
Showing 6 changed files with 372 additions and 0 deletions.
1 change: 1 addition & 0 deletions app/models/setting/discovered.rb
Expand Up @@ -17,6 +17,7 @@ def self.load_defaults
Setting.transaction do
[
self.set('discovery_fact', N_("Fact name to use for primary interface detection"), "discovery_bootif", N_("Interface fact")),
self.set('discovery_auto_bond', N_("Automatic bond interface (if another interface is detected on the same VLAN via LLDP)"), false, N_("Create bond interfaces")),
self.set('discovery_clean_facts', N_("Clean all reported facts during provisioning (except discovery facts)"), false, N_("Clean all facts")),
self.set('discovery_hostname', N_("List of facts to use for the hostname (separated by comma, first wins)"), "discovery_bootif", N_("Hostname facts")),
self.set('discovery_auto', N_("Automatically provision newly discovered hosts, according to the provisioning rules"), false, N_("Auto provisioning")),
Expand Down
36 changes: 36 additions & 0 deletions app/services/foreman_discovery/host_converter.rb
Expand Up @@ -4,6 +4,7 @@ class ForemanDiscovery::HostConverter
# Record must be saved explicitly (using save! or update_attributes! or similar).
# Creates shallow copy.
def self.to_managed(original_host, set_managed = true, set_build = true, added_attributes = {})
eventually_make_bond(original_host) if Setting[:discovery_auto_bond]
host = original_host.becomes(::Host::Managed)
host.type = 'Host::Managed'
host.attributes = host.apply_inherited_attributes(added_attributes)
Expand All @@ -19,6 +20,41 @@ def self.to_managed(original_host, set_managed = true, set_build = true, added_a
host
end

def self.eventually_make_bond(host)
primary = host.primary_interface
return if primary.nil?
return if primary.type == 'Nic::Bond'

neighbors = ::ForemanDiscovery::LldpNeighbors
.from_facts(host.facts_hash)
.get_neighbors_by_interface(primary.identifier)

return if neighbors.nil?

ip = primary.ip
name = primary.name
primary.update(
:primary => false,
:provision => false,
:managed => false,
:name => nil,
:ip => nil
)

bond = Nic::Bond.create(
:identifier => "bond0",
:attached_devices => neighbors,
:primary => true,
:provision => true,
:name => name,
:ip => ip,
:host => host
)

bond.save!
host.interfaces.push bond
end

def self.set_build_clean_facts(host)
# fact cleaning
if Setting['discovery_clean_facts']
Expand Down
67 changes: 67 additions & 0 deletions app/services/foreman_discovery/lldp_neighbors.rb
@@ -0,0 +1,67 @@
module ForemanDiscovery
class LldpNeighbors
def set(iface, neighbor)
interfaces[iface] = neighbor
end

def get(iface)
interfaces[iface]
end

def list_by_pvid
list = {}
interfaces.each do |name, neighbor|
next unless neighbor.has_key? 'PVID'
vlan = neighbor['PVID']
list[vlan] = [] unless list.has_key? vlan
list[vlan].push name
end

list.each { |_, interfaces| interfaces.sort!}
list
end

def interfaces
@interfaces ||= {}
end

def get_neighbors_by_interface(ifname)
return nil unless interfaces.has_key? ifname

interface = get(ifname)
return nil unless interface.has_key? 'PVID'

list = []
interfaces.each do |name, neighbor|
next unless neighbor.has_key? 'PVID'
list.push name if neighbor['PVID'] == interface['PVID']
end

return if list.size < 2
list.sort
end

def self.from_facts(facts)
neighbors = self.new
interfaces = {}
facts.keys.each do |key|
key_s = key.to_s
next unless key_s.start_with? 'lldp_neighbor_'

property, iface = key_s[14..-1].split('_', 2)
if property == 'mngAddr'
protocol, iface = iface.split('_', 2)
property += '_' + protocol
end
interfaces[iface] = {} unless interfaces.has_key? iface
interfaces[iface][property] = facts[key]
end

interfaces.each do |name, properties|
neighbors.set name, properties
end

neighbors
end
end
end
107 changes: 107 additions & 0 deletions test/unit/facts_with_lldp.json
@@ -0,0 +1,107 @@
{
"name": "sinn1636.lan",
"facts": {
"processor3": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"productname": "IBM System x -[7870K4G]-",
"kernelmajversion": "2.6",
"kernelversion": "2.6.32",
"macaddress_vnet0": "FE:54:00:1E:45:13",
"macaddress_br181": "E4:1F:13:CC:36:58",
"macaddress_usb0": "E6:1F:13:D0:43:A3",
"macaddress_vnet1": "FE:54:00:45:E5:3E",
"rubysitedir": "/usr/lib/ruby/site_ruby/1.8",
"macaddress_br182": "E4:1F:13:CC:36:58",
"processor4": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet2": "FE:54:00:93:F5:56",
"macaddress_br183": "E4:1F:13:CC:36:58",
"ps": "ps -ef",
"processor5": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet10": "FE:54:00:7D:F8:24",
"uniqueid": "230a021b",
"macaddress_vnet3": "FE:54:00:05:49:DE",
"lsbdistcodename": "Santiago",
"hardwareisa": "x86_64",
"lsbdistrelease": "6.2",
"processor6": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"serialnumber": "abcdefg",
"processor7": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet4": "FE:54:00:62:53:67",
"hostname": "h02",
"osfamily": "RedHat",
"gateway_if": "br180",
"lsbrelease": ":core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch",
"kernelrelease": "2.6.32-131.12.1.el6.x86_64",
"kernel": "Linux",
"facterversion": "1.6.2",
"uptime_seconds": "13651752",
"macaddress_vnet5": "FE:54:00:01:B9:9C",
"interfaces": "br180,br181,br182,br183,eth0,eth0_181,eth0_182,eth0_183,eth1,lo,usb0,vnet0,vnet1,vnet2,vnet3,vnet4,vnet5,vnet7,vnet10",
"macaddress_vnet7": "FE:54:00:D8:F9:38",
"netmask_br180": "255.255.255.192",
"macaddress_eth0": "E4:1F:13:CC:36:58",
"is_virtual": "false",
"macaddress": "E4:1F:13:CC:36:58",
"sshdsakey": "AAAAB3NzaC1kc3MAAACBAIbK60jlhYKSd4Nx6LCQPMUZcq/P1lTY3sQVrwpmfbF7d4JOGed2RWSoTBAzP3/Tjwlf+8DZ3Jd8a6Yhd6WuBzinE+owym8LXo2gDTvelhnzOVrTytM8hAi8uFEiBTGbnN1zka2y7qCPVxEu+kV20qC3mPODGxpV6y/4KOZ5Y4SHAAAAFQCyjeF+Jy9CDhiD2tfnmtMGAn1xzQAAAIBwtnUhhkB0r3ivPdQZgVy6qha8AJQG94I9FU9FnGJJEjkkfqlgNrj9SxV6Cg9EcrntnAKxZxk7PQhgvDesx69Lap9PAx3Ffb5Pl47cd7ilOXv2RJGKuxZQHNdKUF2445POwNjxSuhEkIB3/zpFXBB/zPMRfqUtlXJEwP9m6z9UFgAAAIAQDBpmcGChFP7i8T2RdMZo3hSMnU4y5lpEGhmwfbODoLSAX37wWQwUmwRP8byTEqFL0+5bp3hRbt/67EoD1O6n39/We19g/UqHkbUdZQqLqFjJfyzqfepq4HXtIIT8GbqW+FGJkH9x2fY5gtQrn9nVdmPDTwERFS98qU9glufbIw==",
"uptime": "158 days",
"manufacturer": "IBM",
"macaddress_eth1": "E4:1F:13:CC:36:5A",
"timezone": "GMT",
"fqdn": "a.server.b.domain",
"ipaddress_br180": "10.35.27.2",
"ipaddress_eth0": "10.35.27.3",
"ipaddress_eth1": "10.35.27.4",
"puppetversion": "2.6.12",
"path": "/root/.gem/bin:/root/.gem/ruby/1.8/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/sbin:/usr/sbin:/root/scripts",
"hardwaremodel": "x86_64",
"macaddress_eth0_181": "E4:1F:13:CC:36:58",
"netmask_lo": "255.0.0.0",
"uptime_days": "158",
"virtual": "physical",
"operatingsystemrelease": "6.2",
"processorcount": "8",
"sshrsakey": "AAAAB3NzaC1xcEabbAABIwAAAQEAwXfnuA0+6C6G152kzCbSZvTyUBrMwc0PGnmZgFpC3x4SkcMgRmtayPNqmY1vyYwyka1lNUoyU9u3TXOUkd7WrodoNYBbDQXWCAVpUci5099zUuZ8RVLcokKwNmQO0d1URgSLt2TtuLiUDB7JU2mHkoOuW9n7ODq+UzYsd2wf+/TQrvgsuyi25MROH2Z3WfMO+fmsj77h34gkuaavwB4e6lNRynjZLuEc4POFvDEW6kGCYm1g/8kRwUlB/Z1Cu0egxtxnTjILFFwfYFQu2Ksig020q1gLX3o4YL9X/YZz+JQuAOLGpK3F0owQUL7Ie3vokDsVmz2AweuLDFeh54i2Bw==",
"macaddress_eth0_182": "E4:1F:13:CC:36:58",
"memorysize": "15.57 GB",
"ipaddress": "10.35.27.2",
"rubyversion": "1.8.7",
"macaddress_eth0_183": "E4:1F:13:CC:36:58",
"physicalprocessorcount": "1",
"network_lo": "127.0.0.0",
"gateway": "10.35.27.62",
"lsbdistid": "RedHatEnterpriseServer",
"network_br180": "10.35.27.0",
"lsbdistdescription": "Red Hat Enterprise Linux Server release 6.2 (Santiago)",
"swapsize": "17.70 GB",
"processor0": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"processor1": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_br180": "E4:1F:13:CC:36:58",
"selinux": "false",
"domain": "server.b.domain",
"architecture": "x86_64",
"uptime_hours": "3792",
"memoryfree": "6.87 GB",
"augeasversion": "0.9.0",
"operatingsystem": "RedHat",
"ipaddress_lo": "127.0.0.1",
"processor2": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"type": "Main Server Chassis",
"id": "root",
"vlans": "181,182,183",
"lsbmajdistrelease": "6",
"swapfree": "16.72 GB",
"netmask": "255.255.255.192",
"discovery_bootif": "E4:1F:13:CC:36:58",
"memorysize_mb": "742.13",
"blockdevice_sda_size": "214749184",
"lldp_neighbor_chassisID_eth1": "0c:27:24:2c:c5:04",
"lldp_neighbor_portID_eth1": "Eth170/1/3",
"lldp_neighbor_sysName_eth1": "DC2-A03-DR-01",
"lldp_neighbor_mngAddr_ipv4_eth1": "192.0.2.13",
"lldp_neighbor_PVID_eth1": "184",
"lldp_neighbor_chassisID_eth2": "e0:d1:73:38:5a:84",
"lldp_neighbor_portID_eth2": "Eth171/1/3",
"lldp_neighbor_sysName_eth2": "DC2-A04-DR-03",
"lldp_neighbor_mngAddr_ipv4_eth2": "192.0.2.13",
"lldp_neighbor_PVID_eth2": "182"
}
}
107 changes: 107 additions & 0 deletions test/unit/facts_with_lldp_bond_candidate.json
@@ -0,0 +1,107 @@
{
"name": "sinn1636.lan",
"facts": {
"processor3": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"productname": "IBM System x -[7870K4G]-",
"kernelmajversion": "2.6",
"kernelversion": "2.6.32",
"macaddress_vnet0": "FE:54:00:1E:45:13",
"macaddress_br181": "E4:1F:13:CC:36:58",
"macaddress_usb0": "E6:1F:13:D0:43:A3",
"macaddress_vnet1": "FE:54:00:45:E5:3E",
"rubysitedir": "/usr/lib/ruby/site_ruby/1.8",
"macaddress_br182": "E4:1F:13:CC:36:58",
"processor4": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet2": "FE:54:00:93:F5:56",
"macaddress_br183": "E4:1F:13:CC:36:58",
"ps": "ps -ef",
"processor5": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet10": "FE:54:00:7D:F8:24",
"uniqueid": "230a021b",
"macaddress_vnet3": "FE:54:00:05:49:DE",
"lsbdistcodename": "Santiago",
"hardwareisa": "x86_64",
"lsbdistrelease": "6.2",
"processor6": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"serialnumber": "abcdefg",
"processor7": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_vnet4": "FE:54:00:62:53:67",
"hostname": "h02",
"osfamily": "RedHat",
"gateway_if": "br180",
"lsbrelease": ":core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch",
"kernelrelease": "2.6.32-131.12.1.el6.x86_64",
"kernel": "Linux",
"facterversion": "1.6.2",
"uptime_seconds": "13651752",
"macaddress_vnet5": "FE:54:00:01:B9:9C",
"interfaces": "br180,br181,br182,br183,eth0,eth0_181,eth0_182,eth0_183,eth1,lo,usb0,vnet0,vnet1,vnet2,vnet3,vnet4,vnet5,vnet7,vnet10",
"macaddress_vnet7": "FE:54:00:D8:F9:38",
"netmask_br180": "255.255.255.192",
"macaddress_eth0": "E4:1F:13:CC:36:58",
"is_virtual": "false",
"macaddress": "E4:1F:13:CC:36:58",
"sshdsakey": "AAAAB3NzaC1kc3MAAACBAIbK60jlhYKSd4Nx6LCQPMUZcq/P1lTY3sQVrwpmfbF7d4JOGed2RWSoTBAzP3/Tjwlf+8DZ3Jd8a6Yhd6WuBzinE+owym8LXo2gDTvelhnzOVrTytM8hAi8uFEiBTGbnN1zka2y7qCPVxEu+kV20qC3mPODGxpV6y/4KOZ5Y4SHAAAAFQCyjeF+Jy9CDhiD2tfnmtMGAn1xzQAAAIBwtnUhhkB0r3ivPdQZgVy6qha8AJQG94I9FU9FnGJJEjkkfqlgNrj9SxV6Cg9EcrntnAKxZxk7PQhgvDesx69Lap9PAx3Ffb5Pl47cd7ilOXv2RJGKuxZQHNdKUF2445POwNjxSuhEkIB3/zpFXBB/zPMRfqUtlXJEwP9m6z9UFgAAAIAQDBpmcGChFP7i8T2RdMZo3hSMnU4y5lpEGhmwfbODoLSAX37wWQwUmwRP8byTEqFL0+5bp3hRbt/67EoD1O6n39/We19g/UqHkbUdZQqLqFjJfyzqfepq4HXtIIT8GbqW+FGJkH9x2fY5gtQrn9nVdmPDTwERFS98qU9glufbIw==",
"uptime": "158 days",
"manufacturer": "IBM",
"macaddress_eth1": "E4:1F:13:CC:36:5A",
"timezone": "GMT",
"fqdn": "a.server.b.domain",
"ipaddress_br180": "10.35.27.2",
"ipaddress_eth0": "10.35.27.3",
"ipaddress_eth1": "10.35.27.4",
"puppetversion": "2.6.12",
"path": "/root/.gem/bin:/root/.gem/ruby/1.8/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/sbin:/usr/sbin:/root/scripts",
"hardwaremodel": "x86_64",
"macaddress_eth0_181": "E4:1F:13:CC:36:58",
"netmask_lo": "255.0.0.0",
"uptime_days": "158",
"virtual": "physical",
"operatingsystemrelease": "6.2",
"processorcount": "8",
"sshrsakey": "AAAAB3NzaC1xcEabbAABIwAAAQEAwXfnuA0+6C6G152kzCbSZvTyUBrMwc0PGnmZgFpC3x4SkcMgRmtayPNqmY1vyYwyka1lNUoyU9u3TXOUkd7WrodoNYBbDQXWCAVpUci5099zUuZ8RVLcokKwNmQO0d1URgSLt2TtuLiUDB7JU2mHkoOuW9n7ODq+UzYsd2wf+/TQrvgsuyi25MROH2Z3WfMO+fmsj77h34gkuaavwB4e6lNRynjZLuEc4POFvDEW6kGCYm1g/8kRwUlB/Z1Cu0egxtxnTjILFFwfYFQu2Ksig020q1gLX3o4YL9X/YZz+JQuAOLGpK3F0owQUL7Ie3vokDsVmz2AweuLDFeh54i2Bw==",
"macaddress_eth0_182": "E4:1F:13:CC:36:58",
"memorysize": "15.57 GB",
"ipaddress": "10.35.27.2",
"rubyversion": "1.8.7",
"macaddress_eth0_183": "E4:1F:13:CC:36:58",
"physicalprocessorcount": "1",
"network_lo": "127.0.0.0",
"gateway": "10.35.27.62",
"lsbdistid": "RedHatEnterpriseServer",
"network_br180": "10.35.27.0",
"lsbdistdescription": "Red Hat Enterprise Linux Server release 6.2 (Santiago)",
"swapsize": "17.70 GB",
"processor0": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"processor1": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"macaddress_br180": "E4:1F:13:CC:36:58",
"selinux": "false",
"domain": "server.b.domain",
"architecture": "x86_64",
"uptime_hours": "3792",
"memoryfree": "6.87 GB",
"augeasversion": "0.9.0",
"operatingsystem": "RedHat",
"ipaddress_lo": "127.0.0.1",
"processor2": "Intel(R) Xeon(R) CPU E5620 @ 2.40GHz",
"type": "Main Server Chassis",
"id": "root",
"vlans": "181,182,183",
"lsbmajdistrelease": "6",
"swapfree": "16.72 GB",
"netmask": "255.255.255.192",
"discovery_bootif": "E4:1F:13:CC:36:58",
"memorysize_mb": "742.13",
"blockdevice_sda_size": "214749184",
"lldp_neighbor_chassisID_eth1": "0c:27:24:2c:c5:04",
"lldp_neighbor_portID_eth1": "Eth170/1/3",
"lldp_neighbor_sysName_eth1": "DC2-A03-DR-01",
"lldp_neighbor_mngAddr_ipv4_eth1": "192.0.2.13",
"lldp_neighbor_PVID_eth1": "182",
"lldp_neighbor_chassisID_eth0": "e0:d1:73:38:5a:84",
"lldp_neighbor_portID_eth0": "Eth171/1/3",
"lldp_neighbor_sysName_eth0": "DC2-A04-DR-03",
"lldp_neighbor_mngAddr_ipv4_eth0": "192.0.2.13",
"lldp_neighbor_PVID_eth0": "182"
}
}
54 changes: 54 additions & 0 deletions test/unit/lldp_neighbors_test.rb
@@ -0,0 +1,54 @@
require 'test_plugin_helper'

class LldpNeighborsTest < ActiveSupport::TestCase
test "#get_neighbors_by_interface gives nothing with no LLDP facts" do
assert_nil simple_facts.get_neighbors_by_interface('eth0')
end

test "#get_neighbors_by_interface gives nothing with unmatching PVID in LLDP facts" do
assert_nil lldp_facts.get_neighbors_by_interface('eth0')
end

test "#get_neighbors_by_interface gives a valid pair for LLDP facts with matching PVID" do
assert_equal %w(eth0 eth1), bond_facts.get_neighbors_by_interface('eth0')
assert_equal %w(eth0 eth1), bond_facts.get_neighbors_by_interface('eth1')
assert_nil bond_facts.get_neighbors_by_interface('br182')
end

test "#list_by_pvid gives an empty Hash without LLDP facts" do
assert_equal Hash({}), simple_facts.list_by_pvid
assert_equal Hash({}), ForemanDiscovery::LldpNeighbors.from_facts({}).list_by_pvid
end

test "#list_by_pvid gives an empty Hash without LLDP facts" do
assert_equal Hash('182' => %w(eth2), '184' => %w(eth1)), lldp_facts.list_by_pvid
end

test "#list_by_pvid pairs interfaces by PVID" do
assert_equal Hash('182' => %w(eth0 eth1)), bond_facts.list_by_pvid
end

test "#list_by_pvid supports multiple PVIDs" do
assert_equal Hash('182' => %w(eth2), '184' => %w(eth1)), lldp_facts.list_by_pvid
end

def simple_facts
neighbors 'facts'
end

def lldp_facts
neighbors'facts_with_lldp'
end

def bond_facts
neighbors 'facts_with_lldp_bond_candidate'
end

def neighbors(name)
ForemanDiscovery::LldpNeighbors.from_facts(get_facts name)
end

def get_facts(name)
JSON.parse(File.read(File.expand_path(File.dirname(__FILE__) + "/#{name}.json")))['facts']
end
end

0 comments on commit 2f33af8

Please sign in to comment.