mirror of
				https://github.com/xcat2/xcat-core.git
				synced 2025-10-31 03:12:30 +00:00 
			
		
		
		
	* Check in init version of cumulus support * Add cumulus installation doc * modify victor's comments.
		
			
				
	
	
		
			1451 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			1451 lines
		
	
	
		
			47 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| #!/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;
 | |
| 
 |