#!/usr/bin/env perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT_plugin::switchdiscover; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } use lib "$::XCATROOT/lib/perl"; use Getopt::Long; use xCAT::Usage; use xCAT::NodeRange; use xCAT::NetworkUtils; use xCAT::Utils; use xCAT::SvrUtils; use xCAT::MacMap; use xCAT::Table; use XML::Simple; no strict; use Data::Dumper; use Socket; use Expect; #global variables for this module my %globalopt; my @filternodes; my @iprange; my %global_scan_type = ( nmap => "nmap_scan", lldp => "lldp_scan", snmp => "snmp_scan" ); my %global_mac_identity = ( "a8:97:dc" => "BNT G8052 switch", "6c:ae:8b" => "BNT G8264-T switch", "fc:cf:62" => "BNT G8124 switch", "7c:fe:90" => "Mellanox IB switch", "8c:ea:1b" => "Edgecore switch" ); my %global_switch_type = ( Juniper => "Juniper", juniper => "Juniper", Cisco => "Cisco", cisco => "Cisco", BNT => "BNT", Blade => "BNT", G8052 => "BNT", RackSwitch => "BNT", Mellanox => "Mellanox", mellanox => "Mellanox", MLNX => "Mellanox", MELLAN => "Mellanox", Edgecore => "cumulus" ); #------------------------------------------------------------------------------- =head1 xCAT_plugin:switchdiscover =head2 Package Description Handles switch discovery functions. It uses lldp, nmap or snmap to scan the network to find out the switches attached to the network. =cut #------------------------------------------------------------------------------- #-------------------------------------------------------------------------------- =head3 send_msg Invokes the callback with the specified message Arguments: request: request structure for plguin calls ecode: error code. 0 for succeful. msg: messages to be displayed. Returns: none =cut #-------------------------------------------------------------------------------- sub send_msg { my $request = shift; my $ecode = shift; my $msg = shift; my %output; ################################################# # Called from child process - send to parent ################################################# if ( exists( $request->{pipe} )) { my $out = $request->{pipe}; $output{errorcode} = $ecode; $output{data} = \@_; print $out freeze( [\%output] ); print $out "\nENDOFFREEZE6sK4ci\n"; } ################################################# # Called from parent - invoke callback directly ################################################# elsif ( exists( $request->{callback} )) { my $callback = $request->{callback}; $output{errorcode} = $ecode; $output{data} = $msg; $callback->( \%output ); } } #-------------------------------------------------------------------------------- =head3 handled_commands It returns a list of commands handled by this plugin. Arguments: none Returns: a list of commands. =cut #-------------------------------------------------------------------------------- sub handled_commands { return( {switchdiscover=>"switchdiscover"} ); } #-------------------------------------------------------------------------------- =head3 parse_args Parse the command line options and operands. Arguments: request: the request structure for plugin Returns: Usage string or error message. 0 if no user promp needed. =cut #-------------------------------------------------------------------------------- sub parse_args { my $request = shift; my $args = $request->{arg}; my $cmd = $request->{command}; my %opt; ############################################# # Responds with usage statement ############################################# local *usage = sub { my $usage_string = xCAT::Usage->getUsage($cmd); return( [$_[0], $usage_string] ); }; ############################################# # No command-line arguments - use defaults ############################################# if ( !defined( $args )) { return(0); } ############################################# # Checks case in GetOptions, allows opts # to be grouped (e.g. -vx), and terminates # at the first unrecognized option. ############################################# @ARGV = @$args; $Getopt::Long::ignorecase = 0; Getopt::Long::Configure( "bundling" ); ############################################# # Process command-line flags ############################################# if (!GetOptions( \%opt, qw(h|help V|Verbose v|version x z w r n range=s s=s setup))) { return( usage() ); } ############################################# # Check for node range ############################################# if ( scalar(@ARGV) == 1 ) { my @nodes = xCAT::NodeRange::noderange( @ARGV ); if (nodesmissed) { return (usage( "The following nodes are not defined in xCAT DB:\n " . join(',', nodesmissed))); } foreach (@nodes) { push @filternodes, $_; } unless (@filternodes) { return(usage( "Invalid Argument: $ARGV[0]" )); } if ( exists( $opt{range} )) { return(usage( "--range flag cannot be used with noderange." )); } } elsif ( scalar(@ARGV) > 1 ) { return(usage( "Invalid flag, please check and retry." )); } ############################################# # Option -V for verbose output ############################################# if ( exists( $opt{V} )) { $globalopt{verbose} = 1; } ############################################# # Check for mutually-exclusive formatting ############################################# if ( (exists($opt{r}) + exists($opt{x}) + exists($opt{z}) ) > 1 ) { return( usage() ); } ############################################# # Check for unsupported scan types ############################################# if ( exists( $opt{s} )) { my @stypes = split ',', $opt{s}; my $error; foreach my $st (@stypes) { if (! exists($global_scan_type{$st})) { $error = $error . "Invalide scan type: $st\n"; } } if ($error) { return usage($error); } $globalopt{scan_types} = \@stypes; } ############################################# # Check the --range ip range option ############################################# if ( exists( $opt{range} )) { $globalopt{range} = $opt{range}; my @ips = split /,/, $opt{range}; foreach my $ip (@ips) { if (($ip =~ /^(\d{1,3})(-\d{1,3})?\.(\d{1,3})(-\d{1,3})?\.(\d{1,3})(-\d{1,3})?\.(\d{1,3})(-\d{1,3})?$/) || ($ip =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d+)$/)) { push @iprange, $ip; } else { return usage("Invalid ip or ip range specified: $ip."); } } } ############################################# # write to the database ############################################# if ( exists( $opt{w} )) { $globalopt{w} = 1; } ############################################# # list the raw information ############################################# if ( exists( $opt{r} )) { $globalopt{r} = 1; } ############################################# # list the xml formate data ############################################# if ( exists( $opt{x} )) { $globalopt{x} = 1; } ############################################# # list the stanza formate data ############################################# if ( exists( $opt{z} )) { $globalopt{z} = 1; } ######################################################### # only list the nodes that discovered for the first time ######################################################### if ( exists( $opt{n} )) { $globalopt{n} = 1; } ######################################################### # setup discover switch ######################################################### if ( exists( $opt{setup} )) { $globalopt{setup} = 1; } return; } #-------------------------------------------------------------------------------- =head3 preprocess_request Parse the arguments and display the usage or the version string. =cut #-------------------------------------------------------------------------------- sub preprocess_request { my $req = shift; if ($req->{_xcatpreprocessed}->[0] == 1) { return [$req]; } my $callback=shift; my $command = $req->{command}->[0]; my $extrargs = $req->{arg}; my @exargs=($req->{arg}); if (ref($extrargs)) { @exargs=@$extrargs; } my $usage_string=xCAT::Usage->parseCommand($command, @exargs); if ($usage_string) { $callback->({data=>[$usage_string]}); $req = {}; return; } my @result = (); my $mncopy = {%$req}; push @result, $mncopy; return \@result; } #-------------------------------------------------------------------------------- =head3 process_request Pasrse the arguments and call the correspondent functions to do switch discovery. =cut #-------------------------------------------------------------------------------- sub process_request { my $req = shift; my $callback = shift; my $sub_req = shift; ########################################### # Build hash to pass around ########################################### my %request; $request{arg} = $req->{arg}; $request{callback} = $callback; $request{command} = $req->{command}->[0]; #################################### # Process command-specific options #################################### my $result = parse_args( \%request ); #################################### # Return error #################################### if ( ref($result) eq 'ARRAY' ) { send_msg( \%request, 1, @$result ); return(1); } # call the relavant functions to start the scan my @scan_types = ("nmap"); if (exists($globalopt{scan_types})) { @scan_types = @{$globalopt{scan_types}}; } my $all_result; foreach my $st (@scan_types) { no strict; my $fn = $global_scan_type{$st}; my $tmp_result = &$fn(\%request, $callback); if (ref($tmp_result) eq 'HASH') { $all_result->{$st} = $tmp_result; } } #consolidate the results by merging the swithes with the same ip or same mac #or same hostname my $result; my $merged; my $counter=0; foreach my $st (keys %$all_result) { my $tmp_result = $all_result->{$st}; #send_msg( \%request, 1, Dumper($tmp_result)); foreach my $old_mac (keys %$tmp_result) { $same = 0; foreach my $new_mac (keys %$result) { my $old_ip = $tmp_result->{$old_mac}->{ip}; my $old_name = $tmp_result->{$old_mac}->{name}; my $old_vendor = $tmp_result->{$old_mac}->{vendor}; my $new_ip = $result->{$new_mac}->{ip}; my $new_name = $result->{$new_mac}->{name}; my $new_vendor = $result->{$new_mac}->{vendor}; if (($old_mac eq $new_mac) || ($old_ip && ($old_ip eq $new_ip)) || ($old_name && ($old_name eq $new_name))) { $same = 1; my $key =$new_mac; if ($new_mac =~ /nomac/) { if ($old_mac =~ /nomac/) { $key = "nomac_$counter"; $counter++; } else { $key = $old_mac; } } if ($old_name) { $result->{$key}->{name} = $old_name; } if ($old_ip) { $result->{$key}->{ip} = $old_ip; } $result->{$key}->{vendor} = $new_vendor; if ($old_vendor) { if ($old_vendor ne $new_vendor) { $result->{$key}->{vendor} .= " " . $old_vendor; } else { $result->{$key}->{vendor} = $old_vendor; } } if ($key ne $new_mac) { delete $result->{$new_mac}; } } } if (!$same) { $result->{$old_mac} = $tmp_result->{$old_mac}; } } } if (!($result)) { send_msg( \%request, 0, " No switch found "); return; } my $display_done = 0; if (exists($globalopt{r})) { #do nothing since is done by the scan functions. $display_done = 1; } if (exists($globalopt{x})) { send_msg( \%request, 0, format_xml( $result )); $display_done = 1; } if (exists($globalopt{z})) { my $stanza_output = format_stanza( $result ); send_msg( \%request, 0, $stanza_output ); $display_done = 1; } if (!$display_done) { #display header $format = "%-12s\t%-20s\t%-50s\t%-12s"; $header = sprintf $format, "ip", "name","vendor", "mac"; send_msg(\%request, 0, $header); my $sep = "------------"; send_msg(\%request, 0, sprintf($format, $sep, $sep, $sep, $sep )); #display switches one by one foreach my $key (keys(%$result)) { my $ip = " "; my $vendor = " "; my $mac = " "; if (exists($result->{$key}->{ip})) { $ip = $result->{$key}->{ip}; } my $name = get_hostname($result->{$key}->{name}, $ip); if (exists($result->{$key}->{vendor})) { $vendor = $result->{$key}->{vendor}; } if ($key !~ /nomac/) { $mac = $key; } my $msg = sprintf $format, $ip, $name, $vendor, $mac; send_msg(\%request, 0, $msg); } } # writes the data into xCAT db if (exists($globalopt{w})) { send_msg(\%request, 0, "Writing the data into xCAT DB...."); xCATdB($result, \%request, $sub_req); } if (exists($globalopt{setup})) { switchsetup($result, \%request, $sub_req); } return; } #-------------------------------------------------------------------------------- =head3 lldp_scan Use lldpd to scan the subnets to do switch discovery. Arguments: request: request structure with callback pointer. Returns: A hash containing the swithes discovered. Each element is a hash of switch attributes. For examples: { "AABBCCDDEEFA" =>{name=>"switch1", vendor=>"ibm", ip=>"10.1.2.3"}, "112233445566" =>{name=>"switch2", vendor=>"cisco", ip=>"11.4.5.6"} } returns 1 if there are errors occurred. =cut #-------------------------------------------------------------------------------- sub lldp_scan { my $request = shift; send_msg($request, 0, "Discovering switches using lldp..."); # get the PID of the currently running lldpd if it is running. if (exists($globalopt{verbose})) { send_msg($request, 0, "...Checking if lldpd is up and running:\n ps -ef | grep lldpd | grep -v grep | awk '{print \$2}'\n"); } my $pid; chomp($pid= `ps -ef | grep lldpd | grep -v grep | awk '{print \$2}'`); unless($pid){ my $dcmd = "lldpd -c -s -e -f"; #my $outref = xCAT::Utils->runcmd($dcmd, 0); #if ($::RUNCMD_RC != 0) #{ # send_msg($request, 1, "Could not start lldpd process. The command was: $dcmd" #); # return 1; #} #xCAT::Utils->runcmd("sleep 30"); send_msg($request, 1, "Warning: lldpd is not running. Please start it with the following flags:\n $dcmd\nThen wait a few minutes before running switchdiscover command again.\n"); return 1; } #now run the lldpcli to collect the data my $ccmd = "lldpcli show neighbors -f xml"; if (exists($globalopt{verbose})) { send_msg($request, 0, "...Discovering switches using lldpd:\n $ccmd\n"); } my $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { send_msg($request, 1, "Could not start lldpd process. The command was: $ccmd" ); return 1; } if (exists($globalopt{verbose})) { send_msg($request, 0, "$result\n"); } #display the raw output if (exists($globalopt{r})) { my $ccmd = "lldpcli show neighbors"; my $raw_result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC == 0) { send_msg($request, 0, "$raw_result\n\n"); } } if (exists($globalopt{verbose})) { send_msg($request, 0, "...Converting XML output to hash.\n"); } my $result_ref = XMLin($result, KeyAttr => 'interface', ForceArray => 1); my $switches; my $counter=0; if ($result_ref) { if (exists($result_ref->{interface})) { my $ref1 = $result_ref->{interface}; foreach my $interface (@$ref1) { if (exists($interface->{chassis})) { my $chassis = $interface->{chassis}->[0]; my $name = $chassis->{name}->[0]->{content}; my $ip = $chassis->{'mgmt-ip'}->[0]->{content}; # resolve the ip from name if (!$ip) { if ($name) { $ip = xCAT::NetworkUtils->getipaddr($name); } } my $id = $chassis->{id}->[0]->{content}; if (!$id) { $id="nomac_lldp_$counter"; $counter++; } my $desc = $chassis->{descr}->[0]->{content}; if ($desc) { $desc =~ s/\n/ /g; $desc =~ s/\"//g; } if ($id) { $switches->{$id}->{name} = $name; $switches->{$id}->{ip} = $ip; $switches->{$id}->{vendor} = $desc; } } } } } # filter out the uwanted entries if noderange or ip range is specified. if ((@filternodes> 0) || (@iprange>0)) { my $ranges = get_ip_ranges($request); if (exists($globalopt{verbose})) { send_msg($request, 0, "...Removing the switches that are not within the following ranges:\n @$ranges\n"); } foreach my $mac (keys %$switches) { my $ip_r = $switches->{$mac}->{ip}; $match = 0; foreach my $ip_f (@$ranges) { my ($net, $mask) = split '/', $ip_f; if ($mask) { #this is a subnet $mask = xCAT::NetworkUtils::formatNetmask($mask, 1, 0); if (xCAT::NetworkUtils->ishostinsubnet($ip_r, $mask, $net)) { $match = 1; last; } } else { #this is an ip if ($ip_r eq $net) { $match = 1; last; } #TODO: handles the case where the range is something like 10.2-3.4.5-6 } } if (!$match) { delete $switches->{$mac}; } } } return $switches } #-------------------------------------------------------------------------------- =head3 nmap_scan Use nmap to scan the subnets to do switch discovery. Arguments: request: request structure with callback pointer. Returns: A hash containing the swithes discovered. Each element is a hash of switch attributes. For examples: { "AABBCCDDEEFA" =>{name=>"switch1", vendor=>"ibm", ip=>"10.1.2.3"}, "112233445566" =>{name=>"switch2", vendor=>"cisco", ip=>"11.4.5.6"} } returns 1 if there are errors occurred. =cut #-------------------------------------------------------------------------------- sub nmap_scan { my $request = shift; my $ccmd; ################################################# # If --range options, take iprange, if noderange is defined # us the ip addresses of the nodes. If none is define, use the # subnets for all the interfaces. ################################################## my $ranges = get_ip_ranges($request); send_msg($request, 0, "Discovering switches using nmap for @$ranges. It may take long time..."); #warning the user if the range is too big foreach my $r (@$ranges) { if ($r =~ /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/(\d+)$/) { if ($5 < 24) { send_msg($request, 0, "You can modify the --range parameters to cut down the time.\n" ); last; } } } # handle ctrl-c $SIG{TERM} = $SIG{INT} = sub { #clean up the nmap processes my $nmap_pid = `ps -ef | grep nmap | grep -v grep | awk '{print \$2}'`; if ($nmap_pid) { system("kill -9 $nmap_pid >/dev/null 2>&1"); exit 0; } }; my $nmap_version = xCAT::Utils->get_nmapversion(); if (xCAT::Utils->version_cmp($nmap_version,"5.10") < 0) { $ccmd = "/usr/bin/nmap -sP -oX - @$ranges"; } else { $ccmd = "/usr/bin/nmap -sn -oX - @$ranges"; } if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $ccmd\n"); } my $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { send_msg($request, 1, "Could not process this command: $ccmd" ); return 1; } ################################################# #display the raw output ################################################# if (defined($globalopt{r}) || defined($globalopt{verbose})) { send_msg($request, 0, "$result\n" ); } ################################################# #compose the switch hash ################################################# my $result_ref = XMLin($result, ForceArray => 1); my $switches; my $found; my $counter=0; my $osguess_ips=[]; if ($result_ref) { if (exists($result_ref->{host})) { my $host_ref = $result_ref->{host}; foreach my $host ( @$host_ref ) { my $ip; my $mac; if (exists($host->{address})) { my $addr_ref = $host->{address}; foreach my $addr ( @$addr_ref ) { my $type = $addr->{addrtype}; if ( $type ne "mac" ) { $ip = $addr->{addr}; $found = 0; } else { $mac = $addr->{addr}; } if (!$mac) { $mac="nomac_nmap_$counter"; $counter++; } my $vendor = $addr->{vendor}; if ($vendor) { my $search_string = join '|', keys(%global_switch_type); if ($vendor =~ /($search_string)/) { $found = 1; } } if ( ($found == 0) && ($type eq "mac") ) { my $search_string = join '|', keys(%global_mac_identity); if ($mac =~ /($search_string)/i) { my $key = $1; $vendor = $global_mac_identity{lc $key}; $found = 1; } # still not found, used nmap osscan command if ( $found == 0) { push(@$osguess_ips, $ip); } } if ($found == 1) { $switches->{$mac}->{ip} = $ip; $switches->{$mac}->{vendor} = $vendor; $switches->{$mac}->{name} = $host->{hostname}; if (exists($globalopt{verbose})) { send_msg($request, 0, "FOUND Switch: found = $found, ip=$ip, mac=$mac, vendor=$vendor\n"); } } } #end for each address } } #end for each host } } if (!$osguess_ips) { my $guess_switches = nmap_osguess($request, $osguess_ips); foreach my $guess_mac ( keys %$guess_switches ) { $switches->{$guess_mac}->{ip} = $guess_switches->{$guess_mac}->{ip};; $switches->{$guess_mac}->{vendor} = $guess_switches->{$guess_mac}->{vendor}; } } return $switches; } ########################################################## # If there is no vendor or other than %global_switch_type, # issue the nmap again to do more aggresively discovery # Choose best guess from osscan # only search port 22 and 23 for fast performance ########################################################### sub nmap_osguess { my $request = shift; my $ranges = shift; my $switches; my $cmd; if (exists($globalopt{verbose})) { send_msg($request, 0, "Couldn't find vendor info, use nmap osscan command to choose best guess"); } my $nmap_version = xCAT::Utils->get_nmapversion(); if (xCAT::Utils->version_cmp($nmap_version,"5.10") < 0) { $cmd = "/usr/bin/nmap -O --osscan-guess -A -p 22,23 @$ranges | grep -E 'Interesting ports on|MAC Addres|Device|Running|Aggressive OS guesses' "; } else { $cmd = "/usr/bin/nmap -O --osscan-guess -A -p 22,23 @$ranges | grep -E 'Nmap scan report|MAC Addres|Device|Running|Aggressive OS guesses' "; } if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $cmd"); } my $result = xCAT::Utils->runcmd($cmd, 0); if (defined($globalopt{r}) || defined($globalopt{verbose})) { send_msg($request, 0, "$result\n" ); } if ($::RUNCMD_RC == 0) { my @lines; if (xCAT::Utils->version_cmp($nmap_version,"5.10") < 0) { @lines = split /Interesting ports /, $result; } else { @lines = split /Nmap scan /, $result; } foreach my $lines_per_ip (@lines) { my @lines2 = split /\n/, $lines_per_ip; my $isswitch=0; my $ip; my $mac; my $vendor; foreach my $line (@lines2) { if ($line =~ /\b(\d{1,3}(?:\.\d{1,3}){3})\b/) { $ip = $1; } if ($line =~ /MAC Address/) { my @array = split / /, $line; $mac = $array[2]; } if ( $line =~ /Device type/ ) { if ( ( $line =~ /switch/) || ( $line =~ /router/) ) { $isswitch=1; } else { last; } } my $search_string = join '|', keys(%global_switch_type); if ($line =~ /Running/) { if ($line =~ /($search_string)/){ $vendor = $1; last; } } if ($line =~ /Aggressive OS/) { if ($line =~ /($search_string)/){ $vendor = $1; $isswitch=1; } } } if ($isswitch == 1) { $switches->{$mac}->{ip} = $ip; $switches->{$mac}->{vendor} = $vendor; if (exists($globalopt{verbose})) { send_msg($request, 0, "FOUND switch from osscan-guess: $ip, $mac, $vendor"); } } } } return $switches; } #-------------------------------------------------------------------------------- =head3 snmp_scan Use snmp to scan the subnets to do switch discovery. Arguments: request: request structure with callback pointer. Returns: A hash containing the swithes discovered. Each element is a hash of switch attributes. For examples: { "AABBCCDDEEFA" =>{name=>"switch1", vendor=>"ibm", ip=>"10.1.2.3"}, "112233445566" =>{name=>"switch2", vendor=>"cisco", ip=>"11.4.5.6"} } returns 1 if there are errors occurred. =cut #-------------------------------------------------------------------------------- sub snmp_scan { my $request = shift; my $ccmd; my $result; my $switches; my $counter = 0; ################################################# # If --range options, take iprange, if noderange is defined # us the ip addresses of the nodes. If none is define, use the # subnets for all the interfaces. ################################################## my $ranges = get_ip_ranges($request); # snmpwalk command has to be available for snmp_scan if (-x "/usr/bin/snmpwalk" ){ send_msg($request, 0, "Discovering switches using snmpwalk for @$ranges ....."); } else { send_msg($request, 0, "snmpwalk is not available, please install snmpwalk command first"); return 1; } # handle ctrl-c $SIG{TERM} = $SIG{INT} = sub { #clean up the nmap processes my $nmap_pid = `ps -ef | grep /usr/bin/nmap | grep -v grep | grep -v "sh -c" |awk '{print \$2}'`; if ($nmap_pid) { system("kill -9 $nmap_pid >/dev/null 2>&1"); exit 0; } }; ########################################################## #use nmap to parse the ip range and possible output from the command: # Nmap scan report for switch-10-5-22-1 (10.5.22.1) 161/udp open snmp # Nmap scan report for 10.5.23.1 161/udp open snmp # Nmap scan report for 10.5.24.1 161/udp closed snmp ########################################################## my $nmap_version = xCAT::Utils->get_nmapversion(); if (xCAT::Utils->version_cmp($nmap_version,"5.10") < 0) { $ccmd = "/usr/bin/nmap -P0 -v -sU -p 161 -oA snmp_scan @$ranges | grep -E 'appears to be up|^161' | perl -pe 's/\\n/ / if \$. % 2'"; } else { $ccmd = "/usr/bin/nmap -P0 -v -sU -p 161 -oA snmp_scan @$ranges | grep -v 'host down' | grep -E 'Nmap scan report|^161' | perl -pe 's/\\n/ / if \$. % 2'"; } if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $ccmd\n"); } $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { send_msg($request, 1, "Could not process this command: $ccmd" ); return 1; } ################################################# #display the raw output ################################################# if (defined($globalopt{r}) || defined($globalopt{verbose})) { send_msg($request, 0, "$result\n" ); } my @lines = split /\n/, $result; foreach my $line (@lines) { my @array = split / /, $line; if ($line =~ /\b(\d{1,3}(?:\.\d{1,3}){3})\b/) { $ip = $1; } if (exists($globalopt{verbose})) { send_msg($request, 0, "Run snmpwalk command to get information for $ip"); } if ($line =~ /close/) { send_msg($request, 0, "*** snmp port is disabled for $ip ***"); next; } my $vendor = get_snmpvendorinfo($request, $ip); if ($vendor) { my $display = ""; my $nopping = "nopping"; my $output = xCAT::SvrUtils->get_mac_by_arp([$ip], $display, $nopping); my ($desc,$mac) = split /: /, $output->{$ip}; if (exists($globalopt{verbose})) { send_msg($request, 0, "node: $ip, mac: $mac"); } if (!$mac) { $mac="nomac_nmap_$counter"; $counter++; } my $hostname = get_snmphostname($request, $ip); my $stype = get_switchtype($vendor); $switches->{$mac}->{ip} = $ip; $switches->{$mac}->{vendor} = $vendor; $switches->{$mac}->{name} = $hostname; if (exists($globalopt{verbose})) { send_msg($request, 0, "found switch: $hostname, $ip, $stype, $vendor"); } } } return $switches; } #-------------------------------------------------------------------------------- =head3 get_snmpvendorinfo return vendor info from snmpwalk command Arguments: ip : IP address passed by the switch after scan Returns: vendor: vendor info of the switch =cut #-------------------------------------------------------------------------------- sub get_snmpvendorinfo { my $request = shift; my $ip = shift; my $snmpwalk_vendor; #Ubuntu only takes OID #my $ccmd = "snmpwalk -Os -v1 -c public $ip sysDescr.0"; my $ccmd = "snmpwalk -Os -v1 -c public $ip 1.3.6.1.2.1.1.1"; if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $ccmd\n"); } my $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { if (exists($globalopt{verbose})) { send_msg($request, 1, "Could not process this command: $ccmd" ); } return $snmpwalk_vendor; } my ($desc,$model) = split /: /, $result; if (exists($globalopt{verbose})) { send_msg($request, 0, "switch model = $model\n" ); } return $model; } #-------------------------------------------------------------------------------- =head3 get_snmpmac return mac address from snmpwalk command Arguments: ip : IP address passed by the switch after scan Returns: mac: mac address of the switch =cut #-------------------------------------------------------------------------------- sub get_snmpmac { my $request = shift; my $ip = shift; my $mac; #Ubuntu only takes OID #my $ccmd = "snmpwalk -Os -v1 -c public $ip ipNetToMediaPhysAddress | grep $ip"; my $ccmd = "snmpwalk -Os -v1 -c public $ip 1.3.6.1.2.1.4.22.1.2 | grep $ip"; if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $ccmd\n"); } my $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { if (exists($globalopt{verbose})) { send_msg($request, 1, "Could not process this command: $ccmd" ); } return $mac; } my ($desc,$mac) = split /: /, $result; #trim the white space at begin and end of mac $mac =~ s/^\s+|\s+$//g; #replace space to : $mac =~ tr/ /:/; if (exists($globalopt{verbose})) { send_msg($request, 0, "switch mac = $mac\n" ); } return $mac; } #-------------------------------------------------------------------------------- =head3 get_snmphostname return hostname from snmpwalk command Arguments: ip : IP address passed by the switch after scan Returns: mac: hostname of the switch =cut #-------------------------------------------------------------------------------- sub get_snmphostname { my $request = shift; my $ip = shift; my $hostname; #Ubuntu only takes OID #my $ccmd = "snmpwalk -Os -v1 -c public $ip sysName"; my $ccmd = "snmpwalk -Os -v1 -c public $ip 1.3.6.1.2.1.1.5"; if (exists($globalopt{verbose})) { send_msg($request, 0, "Process command: $ccmd\n"); } my $result = xCAT::Utils->runcmd($ccmd, 0); if ($::RUNCMD_RC != 0) { if (exists($globalopt{verbose})) { send_msg($request, 1, "Could not process this command: $ccmd" ); } return $hostname; } my ($desc,$hostname) = split /: /, $result; if (exists($globalopt{verbose})) { send_msg($request, 0, "switch hostname = $hostname\n" ); } return $hostname; } #-------------------------------------------------------------------------------- =head3 get_hostname return hostname for the switch discovered Arguments: host: hostname passed by the switch after scan ip : IP address passed by the switch after scan Returns: host: hostname of the switch if host is empty, try to lookup use ip address, otherwise format hostname as switch and ip combination. ex: switch-9-114-5-6 =cut #-------------------------------------------------------------------------------- sub get_hostname { my $host = shift; my $ip = shift; if ($host) { return $host; } if ( !$host ) { $host = gethostbyaddr( inet_aton($ip), AF_INET ); if ( !$host ) { my $ip_str = $ip; $ip_str =~ s/\./\-/g; $host = "switch-$ip_str"; } } return $host; } #-------------------------------------------------------------------------------- =head3 get_switchtype determine the switch type based on the switch vendor Arguments: vendor: switch vendor Returns: stype: type of switch, supports Juniper, Cisco, BNT and Mellanox =cut #-------------------------------------------------------------------------------- sub get_switchtype { my $vendor = shift; my $key = "Not support"; my $search_string = join '|', keys(%global_switch_type); if ($vendor =~ /($search_string)/) { $key = $1; return $global_switch_type{$key}; } else { return $key; } } #-------------------------------------------------------------------------------- =head3 xCATdB Write discovered switch information to xCAT database. Arguments: outhash: A hash containing the swithes discovered. request: The request structure for plugin. sub_req: The request structure for runxcmd. Returns: none =cut #-------------------------------------------------------------------------------- sub xCATdB { my $outhash = shift; my $request = shift; my $sub_req = shift; my $ret; ################################################# # write each switch to xcat database ################################################## foreach my $mac ( keys %$outhash ) { my $ip = $outhash->{$mac}->{ip}; my $vendor = $outhash->{$mac}->{vendor}; #Get hostname and switch type my $host = get_hostname($outhash->{$mac}->{name}, $ip); my $stype = get_switchtype($vendor); if ($mac =~ /nomac/) { $mac=" "; } ################################################# # use lsdef command to check if this switch is # already in the switch table # if it is, use chdef to update it's attribute # otherwise, use mkdef to add this switch to # switch table ################################################## $ret = xCAT::Utils->runxcmd( { command => ['lsdef'], arg => ['-t','node','-o',$host] }, $sub_req, 0, 1); if ($::RUNCMD_RC == 0) { $ret = xCAT::Utils->runxcmd({ command => ['chdef'], arg => ['-t','node','-o',$host,"ip=$ip","mac=$mac",'nodetype=switch','mgt=switch',"switchtype=$stype","usercomment=$vendor"] }, $sub_req, 0, 1); $ret = xCAT::Utils->runxcmd({ command => ['chdef'], arg => ['-t','node','-o',$host,'-p','groups=switch'] }, $sub_req, 0, 1); } else { $ret = xCAT::Utils->runxcmd( { command => ['mkdef'], arg => ['-t','node','-o',$host,'groups=switch',"ip=$ip","mac=$mac",'nodetype=switch','mgt=switch',"switchtype=$stype","usercomment=$vendor"] }, $sub_req, 0, 1); } if ($::RUNCMD_RC != 0) { send_msg($request, 0, "$$ret[0]"); } } } #-------------------------------------------------------------------------------- =head3 get_ip_ranges Return the an array of ip ranges. If --range is specified, use it. If noderange is specified, use the ip address of the nodes. Otherwise, use the subnets for all the live nics on the xCAT mn. Arguments: request: request structure with callback pointer. Returns: A pointer of an array of ip ranges. =cut #-------------------------------------------------------------------------------- sub get_ip_ranges { $request = shift; # if --range is defined, just return the ranges specified by the user if (@iprange > 0) { return \@iprange; } # if noderange is defined, then put the ip addresses of the nodes in if (@filternodes > 0) { my @ipranges=(); foreach my $node (@filternodes) { my $ip = xCAT::NetworkUtils->getipaddr($node); push(@ipranges, $ip); } return \@ipranges; } # for default, use the subnets for all the enabled networks # defined in the networks table. my $ranges=[]; my $nettab = xCAT::Table->new('networks'); if ($nettab) { my $netents = $nettab->getAllEntries(); foreach (@$netents) { my $net = $_->{'net'}; my $nm = $_->{'mask'}; my $fnm = xCAT::NetworkUtils::formatNetmask($nm, 0 , 1); $net .="/$fnm"; push(@$ranges, $net); } } if (!@$ranges) { send_msg($request, 1, "ip range is empty, nothing to discover" ); exit 0; } return $ranges; } #------------------------------------------------------------------------------- =head3 format_stanza list the stanza format for swithes Arguments: outhash: a hash containing the switches discovered Returns: result: return lists as stanza format for swithes =cut #-------------------------------------------------------------------------------- sub format_stanza { my $outhash = shift; my $result; ##################################### # Write attributes ##################################### foreach my $mac ( keys %$outhash ) { my $ip = $outhash->{$mac}->{ip}; my $vendor = $outhash->{$mac}->{vendor}; #Get hostname and switch type my $host = get_hostname($outhash->{$mac}->{name}, $ip); my $stype = get_switchtype($vendor); if ($mac =~ /nomac/) { $mac = " "; } $result .= "$host:\n\tobjtype=node\n"; $result .= "\tgroups=switch\n"; $result .= "\tip=$ip\n"; $result .= "\tmac=$mac\n"; $result .= "\tmgt=switch\n"; $result .= "\tnodetype=switch\n"; $result .= "\tswitchtype=$stype\n"; } return ($result); } #-------------------------------------------------------------------------------- =head3 format_xml list the xml format for swithes Arguments: outhash: a hash containing the switches discovered Returns: result: return lists as xml format for swithes =cut #-------------------------------------------------------------------------------- sub format_xml { my $outhash = shift; my $xml; ##################################### # Write attributes ##################################### foreach my $mac ( keys %$outhash ) { my $result; my $ip = $outhash->{$mac}->{ip}; my $vendor = $outhash->{$mac}->{vendor}; #Get hostname and switch type my $host = get_hostname($outhash->{$mac}->{name}, $ip); my $stype = get_switchtype($vendor); if ($mac =~ /nomac/) { $mac = " "; } $result .= "hostname=$host\n"; $result .= "objtype=node\n"; $result .= "groups=switch\n"; $result .= "ip=$ip\n"; $result .= "mac=$mac\n"; $result .= "mgt=switch\n"; $result .= "nodetype=switch\n"; $result .= "switchtype=$stype\n"; my $href = { Switch => { } }; my @attr = split '\\n', $result; for (my $i = 0; $i < scalar(@attr); $i++ ){ if( $attr[$i] =~ /(\w+)\=(.*)/){ $href->{Switch}->{$1} = $2; } } $xml.= XMLout($href, NoAttr => 1, KeyAttr => [], RootName => undef ); } return ($xml); } #-------------------------------------------------------------------------------- =head3 switchsetup find discovered switches with predefine switches for each discovered switches: 1) matching mac to a predefined node 2) if get predefined node, config the discovered switch, if failed, update 'otherinterface' attribute of predefined node 3) remove hosts record and node definition for the discovered switch Arguments: outhash: a hash containing the switches discovered Returns: result: =cut #-------------------------------------------------------------------------------- sub switchsetup { my $outhash = shift; my $request = shift; my $sub_req = shift; my @switchnode = (); my $static_ip; my $discover_switch; my $nodes_to_config; #print Dumper($outhash); my $macmap = xCAT::MacMap->new(); ################################################# # call find_mac to match pre-defined switch and # discovery switch ################################################## foreach my $mac ( keys %$outhash ) { my $ip = $outhash->{$mac}->{ip}; my $vendor = $outhash->{$mac}->{vendor}; # issue makehosts so we can use xdsh my $dswitch = get_hostname($outhash->{$mac}->{name}, $ip); my $node = $macmap->find_mac($mac,0,1); if (!$node) { send_msg($request, 0, "NO predefined switch matched this switch $dswitch with ip address $ip and mac address $mac"); next; } # get predefine node ip address $static_ip = xCAT::NetworkUtils->getipaddr($node); my $stype = get_switchtype($vendor); if (exists($globalopt{verbose})) { send_msg($request, 0, "Found Discovery switch $dswitch, $ip, $mac with predefine switch $node, $static_ip $stype switch\n" ); } xCAT::Utils->runxcmd({ command => ['chdef'], arg => ['-t','node','-o',$node,"otherinterfaces=$ip",'status=Matched',"mac=$mac","switchtype=$stype","usercomment=$vendor"] }, $sub_req, 0, 1); push (@{$nodes_to_config->{$stype}}, $node); } foreach my $mytype (keys %$nodes_to_config) { my $config_script = "$::XCATROOT/share/xcat/scripts/config".$mytype; if (-r -x $config_script) { my $switches = join(",",@{${nodes_to_config}->{$mytype}}); if ($mytype eq "cumulus") { send_msg($request, 0, "Cumulus switch needs to take 50 mins to install, please run /opt/xcat/share/xcat/script/configcumulus after cumulus OS installed on switch\n"); } else { send_msg($request, 0, "call to config $mytype switches $switches\n"); my $out = `$config_script --switches $switches --all`; send_msg($request, 0, "output = $out\n"); } } else { send_msg($request, 0, "the switch type $mytype is not support yet\n"); } } return; } 1;