2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2025-06-18 12:20:40 +00:00

Merge pull request #1220 from zet809/switch_probe

add command switchprobe to retrieve information from switches and/or …
This commit is contained in:
Xiaopeng Wang
2016-05-30 20:13:52 +08:00
3 changed files with 334 additions and 18 deletions

View File

@ -236,6 +236,109 @@ sub scan_qbridge_vlans {
#TODO: try to scan
}
}
#--------------------------------------------------------------------------------
=head3 dump_mac_info
Descriptions:
Retrieve information (switchport and the mac addresses got for that port) for the specified switch or all switches if no specified.
Arguments:
$req: the xcat request hash
$callback: the function to output information
Returns:
The hash variable store retrieved inforamtions
Usage example:
my $macmap = xCAT::MacMap->new();
my $switch_data = $macmap->dump_mac_info($req, $callback);
foreach my $switch (keys %$switch_data) {...}
=cut
#--------------------------------------------------------------------------------
sub dump_mac_info {
my $self = shift;
my $req = shift;
my $callback = shift;
my $noderange = undef;
if (defined($req->{node})) {
$noderange = $req->{node};
}
my %ret = ();
$self->{collect_mac_info} = 1;
if (defined($req->{opt}->{verbose})) {
$self->{show_verbose_info} = 1;
$self->{callback} = $callback;
}
my $community = "public";
my $dump_all_switches = 0;
my %switches_to_dump = ();
if (!defined($noderange)) {
$dump_all_switches = 1;
}
else {
foreach (@$noderange) {
$switches_to_dump{$_} = 1;
}
}
my $switchestab = xCAT::Table->new('switches', -create=>0);
my @switchesents = $switchestab->getAllNodeAttribs([qw(switch snmpversion username password privacy auth)]);
$self->fill_switchparms(community=>$community,switchesents=>\@switchesents);
my $switchtab = xCAT::Table->new('switch', -create=>0);
my @entries = ();
if ($switchtab) {
@entries = $switchtab->getAllNodeAttribs(['node','switch','port']);
}
#Build hash of switch port names per switch
$self->{switches} = {};
foreach my $entry (@entries) {
if (defined($entry->{switch}) and $entry->{switch} ne "" and defined($entry->{port}) and $entry->{port} ne "") {
if ( !$self->{switches}->{$entry->{switch}}->{$entry->{port}}) {
$self->{switches}->{$entry->{switch}}->{$entry->{port}} = $entry->{node};
}
else {
$self->{switches}->{$entry->{switch}}->{$entry->{port}} .= ",$entry->{node}";
}
}
else {
xCAT::MsgUtils->message("S","xCAT Table error:".$entry->{node}."Has missing or invalid switch.switch and/or switch.port fields");
}
}
foreach my $switch (keys $self->{switchparmhash}) {
if ($dump_all_switches or defined($switches_to_dump{$switch})) {
if ($self->{show_verbose_info}) {
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: Start to get information"]}, $self->{callback});
}
$self->refresh_switch(undef,$community,$switch);
if ($self->{show_verbose_info}) {
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: Finish to get information"]}, $self->{callback});
}
if (!defined($self->{macinfo}->{$switch})) {
$ret{$switch}->{ErrorStr} = "No information get";
foreach my $defportname (keys %{$self->{switches}->{$switch}}) {
$ret{$switch}->{$defportname}->{Node} = $self->{switches}->{$switch}->{$defportname};
}
}
elsif (defined($self->{macinfo}->{$switch}->{ErrorStr})) {
$ret{$switch}->{ErrorStr} = $self->{macinfo}->{$switch}->{ErrorStr};
# To show the error message that the username/password related error is for SNMP only
if ($ret{$switch}->{ErrorStr} =~ /username|password/i) {
$ret{$switch}->{ErrorStr} .= " through SNMP";
}
} else {
foreach my $snmpportname (keys %{$self->{macinfo}->{$switch}}) {
foreach my $defportname (keys %{$self->{switches}->{$switch}}) {
if (namesmatch($defportname, $snmpportname)) {
$ret{$switch}->{$snmpportname}->{Node} = $self->{switches}->{$switch}->{$defportname};
}
}
@{$ret{$switch}->{$snmpportname}->{MACaddress}} = @{$self->{macinfo}->{$switch}->{$snmpportname}};
}
}
}
}
return \%ret;
}
sub find_mac {
# This function is given a mac address, checks for given mac address
# and returns undef if unable to find the node, and the nodename otherwise
@ -410,6 +513,15 @@ sub walkoid {
my $oid = shift;
my %namedargs = @_;
my $retmap = undef;
my $switch = undef;
my $callback = undef;
if (defined($namedargs{verbose})) {
$switch = $namedargs{switch};
$callback = $namedargs{callback};
}
if ($switch) {
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: SNMP Session query OID:\"$oid\""]}, $callback);
}
my $varbind = new SNMP::Varbind([$oid,'']);
$session->getnext($varbind);
if ($session->{ErrorStr}) {
@ -420,19 +532,28 @@ sub walkoid {
xCAT::MsgUtils->message("S","Error communicating with ".$session->{DestHost}.": ".$session->{ErrorStr});
}
}
if ($switch) {
xCAT::MsgUtils->message("I",{data=>["<ERROR>$switch: SNMP Session query OID:\"$oid\" Failed"]}, $callback);
}
return undef;
}
my $count=0;
my $data_string;
while ($varbind->[0] =~ /^$oid\.?(.*)/) {
$count++;
if ($1) {
$retmap->{$1.".".$varbind->[1]}=$varbind->[2]; #If $1 is set, means key should
$data_string .= "\t\t '".$1.".".$varbind->[1]."' => '$varbind->[2]'\n";
} else {
$retmap->{$varbind->[1]}=$varbind->[2]; #If $1 is set, means key should
$data_string .= "\t\t '$varbind->[1]' => '$varbind->[2]'\n";
}
$session->getnext($varbind);
}
if ($switch) {
chomp ($data_string);
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: SNMP Session get data for OID:\"$oid\":\n$data_string"]}, $callback);
}
return $retmap;
}
@ -460,6 +581,10 @@ sub getsnmpsession {
Community => $community,
UseNumeric => 1
);
if ($self->{show_verbose_info}) {
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: Generate SNMP session with parameter: \n\t\t'Version' => '$snmpver'\n\t\t'Community' => '$community'"]}, $self->{callback});
}
} else { #we have snmp3
my %args= (
DestHost => $switch,
@ -476,6 +601,15 @@ sub getsnmpsession {
$args{PrivProto} = uc($swent->{privacy});
$args{PrivPass} = $community;
}
if ($self->{show_verbose_info}) {
my $parameter_string = '';
foreach (keys %args) {
$parameter_string .= "\t\t'$_' => '$args{$_}'\n";
}
chomp($parameter_string);
xCAT::MsgUtils->message("I",{data=>["<INFO>$switch: Generate SNMP session with parameter: \n$parameter_string"]}, $self->{callback});
}
$session = new SNMP::Session(%args);
}
return $session;
@ -486,11 +620,19 @@ sub refresh_switch {
my $output = shift;
my $community = shift;
my $switch = shift;
#if ($error) { die $error; }
my $session = $self->getsnmpsession('community'=>$community,'switch'=>$switch);
unless ($session) { xCAT::MsgUtils->message("S","Failed to communicate with $switch"); return; }
my $namemap = walkoid($session,'.1.3.6.1.2.1.31.1.1.1.1');
unless ($session) {
xCAT::MsgUtils->message("S","Failed to communicate with $switch");
if ($self->{collect_mac_info}) {
$self->{macinfo}->{$switch}->{ErrorStr} = "Failed to communicate with $switch through SNMP";
}
return;
}
elsif ($session->{ErrorStr} and $self->{collect_mac_info}) {
$self->{macinfo}->{$switch}->{ErrorStr} = $session->{ErrorStr};
}
my $namemap = walkoid($session,'.1.3.6.1.2.1.31.1.1.1.1', verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback});
#namemap is the mapping of ifIndex->(human readable name)
if ($namemap) {
my $ifnamesupport=0; #Assume broken ifnamesupport until proven good... (Nortel switch)
@ -505,15 +647,17 @@ sub refresh_switch {
}
}
unless ($namemap) { #Failback to ifDescr. ifDescr is close, but not perfect on some switches
$namemap = walkoid($session,'.1.3.6.1.2.1.2.2.1.2');
$namemap = walkoid($session,'.1.3.6.1.2.1.2.2.1.2', verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback});
}
unless ($namemap) {
if ($session->{ErrorStr} and $self->{collect_mac_info}) {
$self->{macinfo}->{$switch}->{ErrorStr} = $session->{ErrorStr};
}
return;
}
#Above is valid without community string indexing, on cisco, we need it on the next one and onward
my $iftovlanmap = walkoid($session,'.1.3.6.1.4.1.9.9.68.1.2.2.1.2',silentfail=>1); #use cisco vlan membership mib to ascertain vlan
my $trunktovlanmap = walkoid($session,'.1.3.6.1.4.1.9.9.46.1.6.1.1.5',silentfail=>1); #for trunk ports, we are interested in the native vlan
#so we need cisco vtp mib too
my $iftovlanmap = walkoid($session,'.1.3.6.1.4.1.9.9.68.1.2.2.1.2',silentfail=>1,verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback}); #use cisco vlan membership mib to ascertain vlan
my $trunktovlanmap = walkoid($session,'.1.3.6.1.4.1.9.9.46.1.6.1.1.5', silentfail=>1, verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback}); #for trunk ports, we are interested in the native vlan, so we need cisco vtp mib too
my %vlans_to_check;
if (defined($iftovlanmap) or defined($trunktovlanmap)) { #We have a cisco, the intelligent thing is to do SNMP gets on the ports
$self->{switchinfo}->{$switch}->{vlanidtoindex}="NA";#mark this switch to ignore for qbridge scans
@ -551,22 +695,44 @@ sub refresh_switch {
$session = $self->getsnmpsession('switch'=>$switch,'community'=>$community,'vlanid'=>$vlan);
}
unless ($session) { return; }
my $bridgetoifmap = walkoid($session,'.1.3.6.1.2.1.17.1.4.1.2',ciscowarn=>$iscisco); # Good for all switches
my $bridgetoifmap = walkoid($session,'.1.3.6.1.2.1.17.1.4.1.2', ciscowarn=>$iscisco, verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback}); # Good for all switches
if (not ref $bridgetoifmap or !keys %{$bridgetoifmap}) {
xCAT::MsgUtils->message("S","Error communicating with ".$session->{DestHost}.": failed to get a valid response to BRIDGE-MIB request");
return;
xCAT::MsgUtils->message("S","Error communicating with ".$session->{DestHost}.": failed to get a valid response to BRIDGE-MIB request");
return;
}
# my $mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.4.3.1.2');
my $mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.7.1.2.2.1.2',silentfail=>1);
my $mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.7.1.2.2.1.2',silentfail=>1, verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback});
unless (defined($mactoindexmap)) { #if no qbridge defined, try bridge mib, probably cisco
#$mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.7.1.2.2.1.2');
$mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.4.3.1.2',ciscowarn=>$iscisco);
$mactoindexmap = walkoid($session,'.1.3.6.1.2.1.17.4.3.1.2',ciscowarn=>$iscisco, verbose=>$self->{show_verbose_info}, switch=>$switch, callback=>$self->{callback});
} #Ok, time to process the data
if (not ref $mactoindexmap or !keys %{$mactoindexmap}) {
xCAT::MsgUtils->message("S","Error communicating with ".$session->{DestHost}.": Unable to get MAC entries via either BRIDGE or Q-BRIDE MIB");
return;
}
if (defined($self->{collect_mac_info})) {
my %index_to_mac = ();
foreach (keys %$mactoindexmap) {
my $index = $mactoindexmap->{$_};
my @tmp = split /\./, $_;
my @mac = @tmp[-6 .. -1];
my $macstring=sprintf("%02x:%02x:%02x:%02x:%02x:%02x",@mac);
push @{$index_to_mac{$index}}, $macstring;
}
foreach my $boid (keys %$bridgetoifmap) {
my $port_index = $boid;
my $port_name = $namemap->{$bridgetoifmap->{$port_index}};
if (defined($index_to_mac{$port_index})) {
push @{$self->{macinfo}->{$switch}->{$port_name}}, @{$index_to_mac{$port_index}};
}
else {
$self->{macinfo}->{$switch}->{$port_name}->[0] = '';
}
}
return;
}
foreach my $oid (keys %$namemap) {
#$oid =~ m/1.3.6.1.2.1.31.1.1.1.1.(.*)/;
my $ifindex = $oid;

View File

@ -304,6 +304,8 @@ my %usage = (
"switchdiscover" =>
"Usage: switchdiscover [-h|--help|-v|--version]
switchdiscover [<noderange>|--range ipranges] [-s scan_methods] [-r|-x|-z] [-w] [-V|--verbose]",
"switchprobe" =>
"Usage: switchprobe [<noderange>] [-V|--verbose | -c|--check]",
"makentp" =>
"Usage: makentp [-h|--help|-v|--version]
makentp [-a|--all] [-V|--verbose]",

View File

@ -23,6 +23,7 @@ sub handled_commands {
return {
findme => 'switch',
findmac => 'switch',
switchprobe => 'switch',
rspconfig => 'nodehm:mgt',
};
}
@ -36,13 +37,13 @@ sub preprocess_request {
my $noderange = $request->{node};
my $command = $request->{command}->[0];
my $extrargs = $request->{arg};
my @exargs=($request->{arg});
if (ref($extrargs)) {
@exargs=@$extrargs;
}
if ($command eq "rspconfig") {
my $extrargs = $request->{arg};
my @exargs=($request->{arg});
if (ref($extrargs)) {
@exargs=@$extrargs;
}
my $usage_string=xCAT::Usage->parseCommand($command, @exargs);
if ($usage_string) {
$callback->({data=>$usage_string});
@ -100,6 +101,90 @@ sub preprocess_request {
}
return \@requests;
}
elsif ($command eq 'switchprobe') {
@ARGV = ();
if (ref($request->{arg})) {
@ARGV = @{$request->{arg}};
}
use Getopt::Long;
$Getopt::Long::ignorecase = 0;
Getopt::Long::Configure( "bundling" );
my $verbose = undef;
my $check = undef;
my $help = undef;
unless (GetOptions('h|help' => \$help, 'V|verbose' => \$verbose, 'c|check' => \$check)) {
$callback->({error=>["Parse args failed"], errorcode=>1});
return;
}
if (@ARGV) {
$callback->({error=>["Option @ARGV not supported.\n".xCAT::Usage->getUsage($command)], errorcode=>1});
return;
}
if (defined($help)) {
$callback->({data=>xCAT::Usage->getUsage($command)});
return;
}
if (defined($verbose)) {
$request->{opt}->{verbose} = $verbose;
}
if (defined($check)) {
$request->{opt}->{check} = $check;
}
if (defined($noderange)) {
my $nthash = undef;
my $swhash = undef;
my $nodetypetab=xCAT::Table->new('nodetype',-create=>0);
if ($nodetypetab) {
$nthash = $nodetypetab->getNodesAttribs($noderange, ['nodetype']);
if (!defined($nthash)) {
$callback->({error=>["Get attributes from table 'nodetype' failed"],errorcode=>1});
return;
}
}
else {
$callback->({error=>["Open table 'nodetype' failed"],errorcode=>1});
return;
}
my $switchestab = xCAT::Table->new('switches', -create=>0);
if ($switchestab) {
$swhash = $switchestab->getNodesAttribs($noderange, ['switch']);
if (!defined($swhash)) {
$callback->({error=>["Get attributes from table 'switches' failed"],errorcode=>1});
return;
}
}
else {
$callback->({error=>["Open table 'switches' failed"],errorcode=>1});
return;
}
my @switchnode = ();
my @errswnode = ();
my @errornode = ();
foreach my $node (@$noderange) {
if (!defined($nthash->{$node}) or $nthash->{$node}->[0]->{nodetype} ne 'switch') {
push @errornode, $node;
}
elsif (!defined($swhash->{$node})) {
push @errswnode, $node;
}
else {
push @switchnode, $node;
}
}
if (@errornode) {
$callback->({error=>["The nodetype is not 'switch' for nodes: ". join(",",@errornode)],errorcode=>1});
}
if (@errswnode) {
$callback->({error=>["No switch configuration info find for ". join(",",@errswnode)],errorcode=>1});
}
if (@switchnode) {
@{$request->{node}} = @switchnode;
return [$request];
}
return;
}
}
return [$request];
}
@ -119,6 +204,69 @@ sub process_request {
return;
} elsif ($req->{command}->[0] eq 'rspconfig') {
return process_switch_config($req, $cb, $doreq);
} elsif ($req->{command}->[0] eq 'switchprobe') {
my $macinfo = $macmap->dump_mac_info($req, $cb);
if ($macinfo and ref($macinfo) eq 'HASH') {
my $switch_name_length = 0;
my $port_name_length = 0;
foreach my $switch (keys %$macinfo) {
if (length($switch) > $switch_name_length) {
$switch_name_length = length($switch);
}
if (defined($macinfo->{$switch}->{ErrorStr})) {
next;
}
foreach my $portname (keys %{$macinfo->{$switch}}) {
if (length($portname) > $port_name_length) {
$port_name_length = length($portname);
}
}
}
my $format = "%-".$switch_name_length."s %-".$port_name_length."s %-18s %s";
my %failed_switches = ();
my $header = sprintf($format, "Switch", "Port", "MAC address", "Node");
if (!defined($req->{opt}->{check}) and $port_name_length) {
$cb->({data=>$header});
$cb->({data=>"------------------------------------------------------------------"})
};
foreach my $switch (keys %$macinfo) {
if (defined($macinfo->{$switch}->{ErrorStr})) {
if (defined($req->{opt}->{check})) {
$cb->({node=>[{name=>$switch, error=>[$macinfo->{$switch}->{ErrorStr}], errorcode=>1}]});
}
else {
$failed_switches{$switch} = "$macinfo->{$switch}->{ErrorStr}";
}
next;
}
elsif (defined($req->{opt}->{check})) {
$cb->({node=>[{name=>$switch, data=>["PASS"]}]});
next;
}
foreach my $port (sort keys %{$macinfo->{$switch}}) {
my $node = '';
if (defined($macinfo->{$switch}->{$port}->{Node})) {
$node = $macinfo->{$switch}->{$port}->{Node};
}
my @macarrary = ();
if (defined($macinfo->{$switch}->{$port}->{MACaddress})) {
@macarray = @{$macinfo->{$switch}->{$port}->{MACaddress}};
foreach (@macarray) {
my $data = sprintf($format, $switch, $port, ($_ ne '') ? $_ : ' N/A', $node);
$cb->({data=>$data});
#$cb->({node=>[{name=>$switch,data=>$data}]});
}
}
}
}
if (!defined($req->{opt}->{check}) and $port_name_length) {
$cb->({data=>"------------------------------------------------------------------"})
}
foreach (keys %failed_switches) {
$cb->({node=>[{name=>$_, error=>[$failed_switches{$_}], errorcode=>1}]});
}
}
return;
} elsif ($req->{command}->[0] eq 'findme') {
my $ip = $req->{'_xcat_clientip'};
if (defined $req->{nodetype} and $req->{nodetype}->[0] eq 'virtual') {