Skip to content

Instantly share code, notes, and snippets.

@ytti
Created December 10, 2015 08:31
Show Gist options
  • Save ytti/6b315a0331aeb78e7d42 to your computer and use it in GitHub Desktop.
Save ytti/6b315a0331aeb78e7d42 to your computer and use it in GitHub Desktop.
get ISIS neighbours and their latencies
#!/usr/bin/env ruby
# this requires ruby2.0, if important I'll package this
require 'pty'
require 'expect'
require 'ipaddr'
require 'json'
require 'pry'
require 'pp'
class GetLatency
GET_NODES_IOSXR = %q(psql cfgtools -qtc "select name from routers where os_name = 'iox' and name ~ '^r[0-9].*'")
GET_NODES_JUNOS = %q(psql cfgtools -qtc "select name from routers where os_name = 'junos' and name ~ '^r[0-9].*'")
LOGIN = %q(clogin %s)
TIMEOUT = 5
THREADS = 20
OS = {
junos: {
prompt: /^\w+@r[0-9].*> /,
pager: %q(set cli screen-length 0),
isis: %q(show isis adjacency),
isis_peer: /^[a-z][a-z]-?[0-9]/,
show_int: %q(show configuration interfaces %s | display set),
ip: /^set interfaces .* family inet address (\S+).*/,
ping: %q(ping %s source %s bypass-routing wait 1 size 8 count 20 rapid ttl 1 no-resolve inet),
exit: %q(exit),
ping_str: %q(ping statistics),
},
iosxr: {
prompt: /^\r?(?:\e\[K)?RP.*#/,
pager: %q(term len 0),
isis: %q(show isis neighbors),
isis_peer: /^r[0-9][0-9]\..*/,
show_int: %q(show run int %s),
ip: /^\s+ipv4 address (.*)/,
ping: %q(ping %s source %s timeout 1 size 36 count 20),
exit: %q(exit),
},
}
ISIS_NAME = /^r[0-9][0-9]\..*/
class Error < StandardError; end
class Timeout < Error; end
class PingFail < Error; end
attr_reader :nodes
def self.run
get_latency = self.new
get_latency.run
end
def run
latency = {}
threads = []
@nodes = get_nodes
@nodes.each do |node|
sleep 1 while threads.size >= THREADS
node, os = node
##node, os = 'r05.labxtx01.us.bb', :junos # to debug single node
Thread.new {
threads << node
data = nil
begin
case os
when :junos then data=get_junos(node)
when :iosxr then data=get_iosxr(node)
end
rescue => error
$stderr.puts "#{node}: #{error.message}"
#raise
end
latency[node] = data
threads.delete node
}
#break # to debug single node
end
Thread.list.each { |t| next if t == Thread.current; t.join }
latency
end
def get_nodes
nodes = []
%x(#{GET_NODES_IOSXR}).lines.map(&:strip).reject(&:empty?).each { |node| nodes << [node, :iosxr] }
%x(#{GET_NODES_JUNOS}).lines.map(&:strip).reject(&:empty?).each { |node| nodes << [node, :junos] }
nodes
end
private
def get_junos node
peers = {}
os = OS[:junos]
PTY.spawn(LOGIN % [node]) do |r, w, pid|
w.sync = true
r.expt os[:prompt]
r.expect os[:prompt], 2 # why clogin pukes it twice?
w.puts os[:pager]
r.expt os[:prompt]
w.puts os[:isis]
isis_peers = parse_junos_isis r.expt(os[:prompt]).first, os
isis_peers.each do |peer|
name, interface = peer
w.puts os[:show_int] % [interface]
ip, peer, mask = parse_junos_ip r.expt(os[:prompt]).first, os
peers[name] = {
ip: ip,
peer: peer,
mask: mask,
}
end
peers.each do |name, info|
ip, peer = info[:ip], info[:peer]
w.puts os[:ping] % [peer, ip]
peers[name][:latency] = parse_junos_ping r.expt(os[:prompt], 22).first, os
end
w.puts os[:exit]
end
peers
end
def get_iosxr node
peers = {}
os = OS[:iosxr]
PTY.spawn(LOGIN % [node]) do |r, w, pid|
w.sync = true
r.expt os[:prompt]
r.expect os[:prompt], 2 # why clogin pukes it twice?
w.puts os[:pager]
r.expt os[:prompt]
w.puts os[:isis]
isis_peers = parse_iosxr_isis r.expt(os[:prompt]).first, os
isis_peers.each do |peer|
name, interface = peer
interface.sub!(/^BE/, 'bundle-ether') # ISIS shows BE, but old XR won't show config with it
w.puts os[:show_int] % [interface]
ip, peer, mask = parse_iosxr_ip r.expt(os[:prompt]).first, os
peers[name] = {
ip: ip,
peer: peer,
mask: mask,
}
end
peers.each do |name, info|
ip, peer = info[:ip], info[:peer]
w.puts os[:ping] % [peer, ip]
peers[name][:latency] = parse_iosxr_ping r.expt(os[:prompt], 22).first, os
end
w.puts os[:exit]
end
peers
end
def parse_junos_isis str, os
peers = []
str.each_line do |line|
words = line.split
next unless words.size > 4
next unless words.first.match os[:isis_peer]
interface, name = words[0..1]
name = clean_peer_name name
next unless name.match ISIS_NAME
peers << [name, interface]
end
peers
end
def parse_iosxr_isis str, os
peers = []
str.each_line do |line|
words = line.split
next unless words.size > 6
next unless words.first.match os[:isis_peer]
name, interface = words[0..1]
name = clean_peer_name name
next unless name.match ISIS_NAME
peers << [name, interface]
end
peers
end
def parse_junos_ip str, os
net = nil
str.each_line do |line|
next unless line.match os[:ip]
net = Regexp.last_match(1)
break
end
ip, mask = net.strip.split('/')
peer = IP.new([ip,mask].join('/')).peer
mask = peer.mask_cidr
peer = peer.to_s
[ip, peer, mask]
end
def parse_iosxr_ip str, os
net = nil
str.each_line do |line|
next unless line.match os[:ip]
net = Regexp.last_match(1)
break
end
ip, mask = net.strip.split
peer = IP.new([ip,mask].join('/')).peer
mask = peer.mask_cidr
peer = peer.to_s
[ip, peer, mask]
end
def parse_junos_ping str, os
lines = str.lines
index = nil
str.lines.each_with_index do |line, line_number|
index = line_number if line.match os[:ping_str]
end
raise PingFail, "'#{os[:ping_strs]}' not found in ping output" unless index
success = lines[index+1].split[3].to_i
return nil unless success > 0
words = lines[index+2].split
words[3].split('/')[0..-2].map(&:to_f)
end
def parse_iosxr_ping str, os
line = str.lines[-2]
words = line.split
raise PingFail, "unexpected line '#{line}'" unless words.first == 'Success'
success = words[5][1..-2].split('/').first.to_i
return nil unless success > 0
words[-2].split('/').map(&:to_f)
end
def clean_peer_name name
name.sub(/-re\d/, '')
end
end
class IO
def expt re, timeout=GetLatency::TIMEOUT
raise GetLatency::Timeout, "timeout while waiting for '#{re.to_s}'" unless (out = expect re, timeout)
out
end
end
class IP < IPAddr
def initialize(addr = '::', family = Socket::AF_UNSPEC)
addr_org = addr
@addr_org, @prefix = addr_org.split '/'
super
if @family == Socket::AF_UNSPEC or @family == Socket::AF_INET
@addr_org = in_addr(@addr_org)
else
@addr_org = in6_addr(@addr_org)
end
end
def mask_cidr
@mask_addr.to_s(2).delete('0').size
end
def peer
addr = @addr_org.to_i
if addr.even?
addr = [127, 31].include?(mask_cidr) ? addr+=1 : addr-=1
else
addr = [127, 31].include?(mask_cidr) ? addr-=1 : addr+=1
end
self.clone.set addr
end
end
if $0 == __FILE__
puts JSON.pretty_generate GetLatency.run
en
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment