FAQ
Hello,

for the last couple of hours I've been trying to wrap my head around a
problem writing a custom facter plugin and I'm not getting it.

I'm trying to export LLDP neighbors as facter variables. I've already
found https://gist.github.com/1424959 and I'm quite happy with it, so
the issue is not pressing. But I'd like to understand it.

If the neighbor provides an ifAlias (like Cisco) I want to use this,
but if it doesn't I want to use description.

Sidenote: This is my first time with Ruby and it looks quite ugly

First try:
---
require 'facter/util/ip'

Facter::Util::IP.get_interfaces.each do |interface|

# Make a fact for each detail of each interface. Yay.
Facter.add("lldp_neighbor_" + Facter::Util::IP.alphafy(interface)) do
setcode do
device = Facter::Util::Resolution.exec("/usr/sbin/lldpctl -f keyvalue " + interface + " | grep chassis.name | cut -d '=' -f 2")
ifdescr = Facter::Util::Resolution.exec("/usr/sbin/lldpctl -f keyvalue " + interface + " | grep port.descr | cut -d '=' -f 2")
ifalias = Facter::Util::Resolution.exec("/usr/sbin/lldpctl -f keyvalue " + interface + " | grep port.ifalias | cut -d '=' -f 2")
if device
if ifalias
device + " " + ifalias
else
device + " " + ifdescr
end
end
end
end
end
---

This is highly inefficient, but works

lldp_neighbor_eth0 => swg1-kic B23
lldp_neighbor_eth4 => csr1-kic Gi4/3


Next I wanted to try to parse the whole output to get rid of all the
spawning

---
require 'facter'
require 'facter/util/ip'

if not FileTest.exists?("/usr/sbin/lldpctl")
nil
end

config = Hash[]
output = Facter::Util::Resolution.exec("/usr/sbin/lldpctl -f keyvalue")

if output == ""
nil
end

for line in output.split("\n") do
# Drop Multiline
if line =~ /^lldp/
key, value = line.split('=')
prefix, int, key2 = key.split('.', 3)
if not config.has_key?(int)
config[int] = Hash[]
end
config[int][key2] = value
end
end

for interface in config.keys do
Facter.add("lldp_neighbor_" + Facter::Util::IP.alphafy(interface)) do
setcode do
device = config[interface].fetch('chassis.name', "")
ifdescr = config[interface].fetch('port.descr', "")
ifalias = config[interface].fetch('port.ifalias', "")
if device != ""
if ifalias != ""
device + " " + ifalias
else
device + " " + ifdescr
end
end
end
end
end
---

This errorneously reports the same neighbor for both interfaces

lldp_neighbor_eth0 => csr1-kic Gi4/3
lldp_neighbor_eth4 => csr1-kic Gi4/3

I somehow cannot execute the whole facter plugin using irb, but when I
replace the second part of the script with

---
for interface in config.keys do
device = config[interface].fetch('chassis.name', "")
ifdescr = config[interface].fetch('port.descr', "")
ifalias = config[interface].fetch('port.ifalias', "")
if device != ""
if ifalias != ""
puts interface + " " + device + " " + ifalias
else
puts interface + " " + device + " " + ifdescr
end
end
end
---

it does display the right information

eth0 swg1-kic B23
eth4 csr1-kic Gi4/3

Also, if I just open irb and run the first part of the script (the data
collection) the resulting hash looks fine

irb(main):030:0> pp config
{"eth0"=>
{"via"=>"LLDP",
"rid"=>"1",
"age"=>"0 day, 00:51:37",
"chassis.mac"=>"00:01:e7:df:df:00",
"chassis.name"=>"swg1-kic",
"chassis.descr"=>
"HP J4865A ProCurve Switch 4108GL, revision G.07.109, ROM G.05.02 (/sw/code/build/gamo(m03))",
"chassis.mgmt-ip"=>"xxxx",
"chassis.Bridge.enabled"=>"on",
"chassis.Router.enabled"=>"off",
"port.local"=>"47",
"port.descr"=>"B23"},
"eth4"=>
{"via"=>"LLDP",
"rid"=>"2",
"age"=>"0 day, 00:47:03",
"chassis.mac"=>"68:ef:bd:7e:ee:86",
"chassis.name"=>"csr1-kic",
"chassis.descr"=>
"Cisco IOS Software, s72033_rp Software (s72033_rp-ADVIPSERVICESK9_WAN-M), Version 12.2(33)SXI2a, RELEASE SOFTWARE (fc2)",
"chassis.mgmt-ip"=>"xxx.xxx.xxx.xxx",
"chassis.Bridge.enabled"=>"off",
"chassis.Router.enabled"=>"on",
"port.ifalias"=>"Gi4/3",
"port.descr"=>"Mirror-Port, moni-kic",
"port.auto-negotiation.supported"=>"no",
"port.auto-negotiation.enabled"=>"yes",
"port.auto-negotiation.1000Base-T.hd"=>"no",
"port.auto-negotiation.1000Base-T.fd"=>"yes",
"port.auto-negotiation.current"=>"unknown"}}

So something is obviously wrong in my facter part, but I cannot find it.

I've tried several attempts (like explicitly resetting the variables
in the setcode block), but ...

Help! :-)

Bernhard

--
You received this message because you are subscribed to the Google Groups "Puppet Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/puppet-users?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.

Search Discussions

  • Jcbollinger at Feb 4, 2013 at 6:47 pm

    On Friday, February 1, 2013 8:16:12 AM UTC-6, Bernhard Schmidt wrote:
    ---
    require 'facter'
    require 'facter/util/ip'

    if not FileTest.exists?("/usr/sbin/lldpctl")
    nil
    end

    That last bit probably doesn't do what you want. That is to say, it
    evaluates to nil in every case (implicitly when the condition is not
    satisfied), and has no effect on anything else in the fact implementation.
    Perhaps you meant to put the rest of the body in an 'else' clause? Or,
    reverse the sense of the test and put everything else inside the condition?


    config = Hash[]
    output = Facter::Util::Resolution.exec("/usr/sbin/lldpctl -f keyvalue")

    if output == ""
    nil
    end


    Again, a not-what-you-intended conditional statement. In this case, it's
    probably altogether unnecessary.


    for line in output.split("\n") do
    # Drop Multiline
    if line =~ /^lldp/
    key, value = line.split('=')
    prefix, int, key2 = key.split('.', 3)
    if not config.has_key?(int)
    config[int] = Hash[]
    end
    config[int][key2] = value
    end
    end

    More idiomatic Ruby might be:

    # This hash automatically creates appropriate new
    # entries as needed:
    config = Hash.new { |h, k| h[k] = Hash.new }

    output.lines.grep(/^lldp/).each do |line|
    # note the 'chomp':
    key, value = line.chomp.split('=')
    prefix, int, key2 = key.split('.', 3)
    config[int][key2] = value
    end

    Other than the "chomp", however, I don't think that will change the results.


    for interface in config.keys do
    Facter.add("lldp_neighbor_" + Facter::Util::IP.alphafy(interface)) do
    setcode do
    device = config[interface].fetch('chassis.name', "")
    ifdescr = config[interface].fetch('port.descr', "")
    ifalias = config[interface].fetch('port.ifalias', "")

    if device != ""
    if ifalias != ""
    device + " " + ifalias
    else
    device + " " + ifdescr
    end
    end
    end
    end
    end

    More idiomatic:

    config.each_pair do | interface, props |
    Facter.add("lldp_neighbor_" + Facter::Util::IP.alphafy(interface)) do
    setcode do
    device = props.fetch('chassis.name', "")
    if device != ""
    # note: although clean and concise, this is not 100%
    # equivalent to the original if 'port.ifalias' can be present but
    # empty:
    device + ' ' + props.fetch('port.ifalias', props['port.descr'])
    else
    # default value when device is empty
    'no config'
    end
    end
    end
    end

    I think that might solve your problem, too. Here's what I think is
    happening to you: your 'for' loop runs, setting variable 'interface' in
    turn to each key of the 'config' hash. At each iteration, it creates a
    block of code that Facter will execute (later) to determine the value of
    one fact. Ruby blocks are closures, so where yours later reference the
    value of 'interface' they are getting the then-current value in the context
    where the block was created. By that time, that's the value after the
    iteration is done (apparently the last key iterated).

    I think the rewritten version will solve that problem because at each
    iteration, the values of the key and (inner) hash are captured as distinct
    local variables ('interface' and 'props'), very much as if the block passed
    to 'each_pair' were an independent function. This is a Ruby subtlety, so
    you have chosen a good example to attempt to expand your understanding of
    Ruby.


    John

    --
    You received this message because you are subscribed to the Google Groups "Puppet Users" group.
    To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
    To post to this group, send email to [email protected].
    Visit this group at http://groups.google.com/group/puppet-users?hl=en.
    For more options, visit https://groups.google.com/groups/opt_out.

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
grouppuppet-users @
categoriespuppet
postedFeb 1, '13 at 2:19p
activeFeb 4, '13 at 6:47p
posts2
users2
websitepuppetlabs.com

2 users in discussion

Jcbollinger: 1 post Bernhard Schmidt: 1 post

People

Translate

site design / logo © 2023 Grokbase