2007-10-26 22:44:33 +00:00
#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT_plugin::nodediscover ;
use xCAT::Table ;
use IO::Socket ;
use strict ;
use XML::Simple ;
2009-07-30 19:42:29 +00:00
$ XML:: Simple:: PREFERRED_PARSER = 'XML::Parser' ;
2007-10-26 22:44:33 +00:00
use Data::Dumper ;
use POSIX "WNOHANG" ;
use Storable qw( freeze thaw ) ;
use IO::Select ;
use IO::Handle ;
2008-03-21 20:00:07 +00:00
use xCAT::Utils ;
2007-10-26 22:44:33 +00:00
use Sys::Syslog ;
2010-03-17 23:26:14 +00:00
use Text::Balanced qw( extract_bracketed ) ;
2007-10-26 22:44:33 +00:00
2008-01-14 19:13:43 +00:00
sub gethosttag {
#This function tries to return a good hostname for a node based on the
#network to which it is connected (by $netn or maybe $ifname)
#heuristic:
#if the client had a valid IP address from a dhcp server, that is used as key
#once the matching network is found, and an explicit mapping defined, try that
#next, try to see if the ip for the case where hostname==nodename is on this net, if so, return that
#next, try to do nodename-ifname, return that if successful
#next, repeat process for all networks that have the common mgtifname field
#return undef for now if none of the above worked
my $ node = shift ;
my $ netn = shift ;
my $ ifname = shift ;
2008-03-21 20:00:07 +00:00
my $ usednames = shift ;
2008-03-24 19:32:02 +00:00
my % netmap = % { xCAT::Utils:: my_if_netmap ( ) } ;
2008-03-21 20:00:07 +00:00
my $ mgtifname = $ netmap { $ netn } ;
2008-01-14 19:13:43 +00:00
my $ secondpass = 0 ;
my $ name = "" ;
my $ defhost = inet_aton ( $ node ) ;
my $ nettab = xCAT::Table - > new ( 'networks' ) ;
my $ defn = "" ;
my @ netents = @ { $ nettab - > getAllEntries ( ) } ;
my $ pass ;
2008-01-26 19:06:21 +00:00
#TODO: mgtifname field will get trounced in hierarchical setup, use a live check to match accurately
2008-03-21 20:00:07 +00:00
foreach ( @ netents ) {
if ( $ _ - > { net } eq $ netn or ( $ mgtifname and $ mgtifname eq $ netmap { $ _ - > { net } } ) ) { #either is the network or shares physical interface
if ( $ _ - > { nodehostname } ) { #Check for a nodehostname rule in the table
2010-03-17 23:26:14 +00:00
$ name = $ node ;
if ( $ _ - > { nodehostname } =~ /^\/[^\/]*\/[^\/]*\/$/ )
{
my $ exp = substr ( $ _ - > { nodehostname } , 1 ) ;
chop $ exp ;
my @ parts = split ( '/' , $ exp , 2 ) ;
$ name =~ s/$parts[0]/$parts[1]/ ;
}
elsif ( $ _ - > { nodehostname } =~ /^\|.*\|.*\|$/ )
{
#Perform arithmetic and only arithmetic operations in bracketed issues on the right.
#Tricky part: don't allow potentially dangerous code, only eval if
#to-be-evaled expression is only made up of ()\d+-/%$
#Futher paranoia? use Safe module to make sure I'm good
my $ exp = substr ( $ _ - > { nodehostname } , 1 ) ;
chop $ exp ;
my @ parts = split ( '\|' , $ exp , 2 ) ;
my $ curr ;
my $ next ;
my $ prev ;
my $ retval = $ parts [ 1 ] ;
( $ curr , $ next , $ prev ) =
extract_bracketed ( $ retval , '()' , qr/[^()]*/ ) ;
unless ( $ curr ) { #If there were no paramaters to save, treat this one like a plain regex
$ name =~ s/$parts[0]/$parts[1]/ ;
}
while ( $ curr )
{
#my $next = $comps[0];
if ( $ curr =~ /^[\{\}()\-\+\/\%\*\$\d]+$/ or $ curr =~ /^\(sprintf\(["'%\dcsduoxefg]+,\s*[\{\}()\-\+\/\%\*\$\d]+\)\)$/ )
{
use integer ;
#We only allow integer operations, they are the ones that make sense for the application
my $ value = $ name ;
$ value =~ s/$parts[0]/$curr/ee ;
$ retval = $ prev . $ value . $ next ;
}
else
{
print "$curr is bad\n" ;
}
( $ curr , $ next , $ prev ) =
extract_bracketed ( $ retval , '()' , qr/[^()]*/ ) ;
}
#At this point, $retval is the expression after being arithmetically contemplated, a generated regex, and therefore
#must be applied in total
$ name =~ s/$parts[0]/$retval/ ;
#print Data::Dumper::Dumper(extract_bracketed($parts[1],'()',qr/[^()]*/));
#use text::balanced extract_bracketed to parse earch atom, make sure nothing but arith operators, parans, and numbers are in it to guard against code execution
}
print "Name: $name\n" ;
#$name =~ s/$left/$right/;
2008-03-21 20:00:07 +00:00
if ( $ name and inet_aton ( $ name ) ) {
if ( $ netn eq $ _ - > { net } and not $ usednames - > { $ name } ) { return $ name ; }
#At this point, it could still be valid if block was entered due to mgtifname
my $ nnetn = inet_ntoa ( pack ( "N" , unpack ( "N" , inet_aton ( $ name ) ) & unpack ( "N" , inet_aton ( $ _ - > { mask } ) ) ) ) ;
if ( $ nnetn eq $ _ - > { net } and not $ usednames - > { $ name } ) { return $ name ; }
}
$ name = "" ; #Still here, this branch failed
}
$ defn = "" ;
if ( $ defhost ) {
$ defn = inet_ntoa ( pack ( "N" , unpack ( "N" , $ defhost ) & unpack ( "N" , inet_aton ( $ _ - > { mask } ) ) ) ) ;
}
if ( $ defn eq $ _ - > { net } and not $ usednames - > { $ node } ) { #the default nodename is on this network
return $ node ;
}
my $ tentativehost = $ node . "-" . $ ifname ;
my $ tnh = inet_aton ( $ tentativehost ) ;
if ( $ tnh ) {
my $ nnetn = inet_ntoa ( pack ( "N" , unpack ( "N" , $ tnh ) & unpack ( "N" , inet_aton ( $ _ - > { mask } ) ) ) ) ;
if ( $ nnetn eq $ _ - > { net } and not $ usednames - > { $ tentativehost } ) {
return $ tentativehost ;
}
}
2008-01-14 19:13:43 +00:00
}
2008-03-21 20:00:07 +00:00
}
2008-01-14 19:13:43 +00:00
}
2007-10-26 22:44:33 +00:00
sub handled_commands {
return {
2008-02-16 01:42:15 +00:00
#discovered => 'chain:ondiscover',
discovered = > 'nodediscover' ,
2007-10-26 22:44:33 +00:00
} ;
}
sub process_request {
my $ request = shift ;
my $ callback = shift ;
2008-01-14 22:19:17 +00:00
my $ doreq = shift ;
2007-10-26 22:44:33 +00:00
my $ node = $ request - > { node } - > [ 0 ] ;
2008-01-25 15:07:53 +00:00
my $ ip = $ request - > { '_xcat_clientip' } ;
2007-10-26 22:44:33 +00:00
openlog ( "xCAT node discovery" , '' , 'local0' ) ;
2009-12-09 19:15:16 +00:00
2007-10-26 22:44:33 +00:00
#First, fill in tables with data fields..
if ( defined ( $ request - > { mtm } ) or defined ( $ request - > { serial } ) ) {
my $ vpdtab = xCAT::Table - > new ( "vpd" , - create = > 1 ) ;
if ( $ request - > { mtm } - > [ 0 ] ) {
$ vpdtab - > setNodeAttribs ( $ node , { mtm = > $ request - > { mtm } - > [ 0 ] } ) ;
}
if ( $ request - > { serial } ) {
$ vpdtab - > setNodeAttribs ( $ node , { serial = > $ request - > { serial } - > [ 0 ] } ) ;
}
}
2008-05-07 21:15:13 +00:00
my $ nrtab ;
2009-12-09 19:15:16 +00:00
my @ discoverynics ;
2010-11-05 13:29:34 +00:00
my @ forcenics ; #list of 'eth' style interface names to require to come up on post-discovery client dhcp restart
2007-10-26 22:44:33 +00:00
if ( defined ( $ request - > { arch } ) ) {
2008-05-07 13:24:50 +00:00
#Set the architecture in nodetype. If 32-bit only x86 or ppc detected, overwrite. If x86_64, only set if either not set or not an x86 family
2007-10-26 22:44:33 +00:00
my $ typetab = xCAT::Table - > new ( "nodetype" , - create = > 1 ) ;
2009-09-14 11:38:16 +00:00
( my $ nent ) = $ typetab - > getNodeAttribs ( $ node , [ 'arch' , 'supportedarchs' ] ) ;
2008-05-07 13:24:50 +00:00
if ( $ request - > { arch } - > [ 0 ] =~ /x86_64/ ) {
2009-09-14 11:02:38 +00:00
if ( $ nent and ( $ nent - > { arch } =~ /x86/ ) ) { #If already an x86 variant, do not change
unless ( $ nent and $ nent - > { supportedarchs } =~ /x86_64/ ) {
$ typetab - > setNodeAttribs ( $ node , { supportedarchs = > "x86,x86_64" } ) ;
}
} else {
$ typetab - > setNodeAttribs ( $ node , { arch = > $ request - > { arch } - > [ 0 ] , supportedarchs = > "x86,x86_64" } ) ;
2008-05-07 13:24:50 +00:00
#this check is so that if an admin explicitly declares a node 'x86', the 64 bit capability is ignored
}
} else {
2009-09-14 11:02:38 +00:00
unless ( $ nent and $ nent - > { supportedarchs } eq $ request - > { arch } - > [ 0 ] and $ nent - > { arch } eq $ request - > { arch } - > [ 0 ] ) {
$ typetab - > setNodeAttribs ( $ node , { arch = > $ request - > { arch } - > [ 0 ] , supportedarchs = > $ request - > { arch } - > [ 0 ] } ) ;
}
2008-05-07 13:24:50 +00:00
}
2008-05-07 20:30:05 +00:00
my $ currboot = '' ;
2008-05-07 21:15:13 +00:00
$ nrtab = xCAT::Table - > new ( 'noderes' ) ; #Attempt to check and set if wrong the netboot method on discovery, if admin omitted
2009-12-09 19:15:16 +00:00
( my $ rent ) = $ nrtab - > getNodeAttribs ( $ node , [ 'netboot' , 'discoverynics' ] ) ;
if ( $ rent and defined $ rent - > { discoverynics } ) {
@ discoverynics = split /,/ , $ rent - > { discoverynics } ;
}
2008-05-07 20:30:05 +00:00
if ( $ rent and $ rent - > { 'netboot' } ) {
$ currboot = $ rent - > { 'netboot' } ;
}
2009-09-25 21:19:38 +00:00
if ( $ request - > { arch } - > [ 0 ] =~ /x86/ and $ currboot !~ /pxe/ and $ currboot !~ /xnba/ ) {
$ nrtab - > setNodeAttribs ( $ node , { netboot = > 'xnba' } ) ;
2008-05-07 20:30:05 +00:00
} elsif ( $ request - > { arch } - > [ 0 ] =~ /ppc/ and $ currboot !~ /yaboot/ ) {
$ nrtab - > setNodeAttribs ( $ node , { netboot = > 'yaboot' } ) ;
}
2007-10-26 22:44:33 +00:00
}
2008-01-14 19:13:43 +00:00
if ( defined ( $ request - > { mac } ) ) {
my $ mactab = xCAT::Table - > new ( "mac" , - create = > 1 ) ;
my @ ifinfo ;
my $ macstring = "" ;
2008-03-21 20:00:07 +00:00
my % usednames ;
2009-12-09 19:15:16 +00:00
my % bydriverindex ;
my $ forcenic = 0 ; #-1 is force skip, 0 is use default behavior, 1 is force to be declared even if hosttag is skipped to do so
2008-01-14 19:13:43 +00:00
foreach ( @ { $ request - > { mac } } ) {
2009-12-09 19:15:16 +00:00
@ ifinfo = split /\|/ ;
$ bydriverindex { $ ifinfo [ 0 ] } += 1 ;
if ( scalar @ discoverynics ) {
$ forcenic = - 1 ; #$forcenic defaults to explicitly skip nic
foreach my $ nic ( @ discoverynics ) {
if ( $ nic =~ /:/ ) { #syntax like 'bnx2:0' to say the first bnx2 managed interface
( my $ driver , my $ index ) = split /:/ , $ nic ;
if ( $ driver eq $ ifinfo [ 0 ] and $ index == ( $ bydriverindex { $ driver } - 1 ) ) {
$ forcenic = 1 ; #force nic to be put into database
2010-11-05 13:29:34 +00:00
push @ forcenics , $ ifinfo [ 1 ] ;
2009-12-09 19:15:16 +00:00
last ;
}
} else { #simple 'eth2' sort of argument
if ( $ nic eq $ ifinfo [ 1 ] ) {
2010-11-05 13:29:34 +00:00
push @ forcenics , $ ifinfo [ 1 ] ;
2009-12-09 19:15:16 +00:00
$ forcenic = 1 ;
last ;
}
}
}
}
if ( $ forcenic == - 1 ) { #if force to skip, go to next nic
next ;
}
2010-01-11 19:12:37 +00:00
my $ currmac = lc ( $ ifinfo [ 2 ] ) ;
2009-12-09 19:15:16 +00:00
if ( $ ifinfo [ 3 ] ) {
2008-01-14 19:13:43 +00:00
( my $ ip , my $ netbits ) = split /\// , $ ifinfo [ 3 ] ;
2009-12-09 19:15:16 +00:00
if ( $ ip =~ /\d+\.\d+\.\d+\.\d+/ ) {
my $ ipn = unpack ( "N" , inet_aton ( $ ip ) ) ;
my $ mask = 2 ** $ netbits - 1 << ( 32 - $ netbits ) ;
my $ netn = inet_ntoa ( pack ( "N" , $ ipn & $ mask ) ) ;
my $ hosttag = gethosttag ( $ node , $ netn , @ ifinfo [ 1 ] , \ % usednames ) ;
2010-03-17 23:26:14 +00:00
print Dumper ( $ hosttag ) . "\n" ;
2009-12-09 19:15:16 +00:00
if ( $ hosttag ) {
2010-09-17 14:41:32 +00:00
#(my $rent) = $nrtab->getNodeAttribs($node,['primarynic','nfsserver']);
( my $ rent ) = $ nrtab - > getNodeAttribs ( $ node , [ 'nfsserver' ] ) ;
#unless ($rent and $rent->{primarynic}) { #if primarynic not set, set it to this nic
# $nrtab->setNodeAttribs($node,{primarynic=>@ifinfo[1]});
#}
2009-12-09 19:15:16 +00:00
unless ( $ rent and $ rent - > { nfsserver } ) {
$ nrtab - > setNodeAttribs ( $ node , { nfsserver = > xCAT::Utils - > my_ip_facing ( $ hosttag ) } ) ;
}
$ usednames { $ hosttag } = 1 ;
2010-11-05 13:29:34 +00:00
if ( $ hosttag eq $ node ) {
$ macstring . = $ currmac . "|" ;
} else {
2010-02-06 19:05:23 +00:00
$ macstring . = $ currmac . "!" . $ hosttag . "|" ;
2010-11-05 13:29:34 +00:00
}
2009-12-09 19:15:16 +00:00
} else {
2010-01-11 19:12:37 +00:00
if ( $ forcenic == 1 ) { $ macstring . = $ currmac . "|" ; } else { $ macstring . = $ currmac . "!*NOIP*|" ; }
2009-12-09 19:15:16 +00:00
}
2010-01-11 19:12:37 +00:00
}
2009-12-09 19:15:16 +00:00
} else {
2010-01-11 19:12:37 +00:00
if ( $ forcenic == 1 ) { $ macstring . = $ currmac . "|" ; }
2008-03-21 20:00:07 +00:00
}
2008-01-14 19:13:43 +00:00
}
2009-12-09 19:15:16 +00:00
$ macstring =~ s/\|\z// ;
2008-01-14 19:13:43 +00:00
$ mactab - > setNodeAttribs ( $ node , { mac = > $ macstring } ) ;
2008-01-14 22:19:17 +00:00
my % request = (
command = > [ 'makedhcp' ] ,
node = > [ $ node ]
) ;
$ doreq - > ( \ % request ) ;
2008-01-14 19:13:43 +00:00
}
2007-10-26 22:44:33 +00:00
#TODO: mac table? on the one hand, 'the' definitive interface was determined earlier...
#Delete the state it was in to make it traverse destiny once agoin
my $ chaintab = xCAT::Table - > new ( 'chain' ) ;
if ( $ chaintab ) {
$ chaintab - > setNodeAttribs ( $ node , { currstate = > '' , currchain = > '' } ) ;
$ chaintab - > close ( ) ;
}
2010-11-05 13:29:34 +00:00
my $ restartstring = "restart" ;
if ( scalar @ forcenics > 0 ) {
$ restartstring . = " (" . join ( "|" , @ forcenics ) . ")" ;
}
2007-10-26 22:44:33 +00:00
#now, notify the node to continue life
my $ sock = new IO::Socket:: INET (
PeerAddr = > $ ip ,
2008-01-11 20:55:28 +00:00
PeerPort = > '3001' ,
Timeout = > '1' ,
Proto = > 'tcp'
) ;
2007-10-26 22:44:33 +00:00
unless ( $ sock ) { syslog ( "err" , "Failed to notify $ip that it's actually $node." ) ; return ; } #Give up if the node won't hear of it.
2010-11-05 13:29:34 +00:00
print $ sock $ restartstring ;
2007-10-26 22:44:33 +00:00
close ( $ sock ) ;
syslog ( "info" , "$node has been discovered" ) ;
}
1 ;