Created
December 10, 2015 08:31
-
-
Save ytti/6b315a0331aeb78e7d42 to your computer and use it in GitHub Desktop.
get ISIS neighbours and their latencies
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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