# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html package xCAT_plugin::lsslp; use strict; use Getopt::Long; use Socket; use POSIX "WNOHANG"; use Storable qw(freeze thaw); use Time::HiRes qw(gettimeofday); use IO::Select; use XML::Simple; if ($^O =~ /^linux/i) { $XML::Simple::PREFERRED_PARSER='XML::Parser'; } use xCAT::PPCdb; ####################################### # Perl::SNMP not working on AIX yet ####################################### if ($^O =~ /^linux/i) { eval { require xCAT::MacMap }; eval { require xCAT_plugin::blade }; } ####################################### # Constants ####################################### use constant { HARDWARE_SERVICE => "service:management-hardware.IBM", SOFTWARE_SERVICE => "service:management-software.IBM", WILDCARD_SERVICE => "service:management-*.IBM:", SERVICE_FSP => "cec-service-processor", SERVICE_BPA => "bulk-power-controller", SERVICE_HMC => "hardware-management-console", SERVICE_IVM => "integrated-virtualization-manager", SERVICE_MM => "management-module", SERVICE_RSA => "remote-supervisor-adapter", TYPE_MM => "MM", TYPE_RSA => "RSA", TYPE_BPA => "BPA", TYPE_HMC => "HMC", TYPE_IVM => "IVM", TYPE_FSP => "IVM", IP_ADDRESSES => 3, TEXT => 0, FORMAT => 1, SUCCESS => 0, RC_ERROR => 1 }; ####################################### # Globals ####################################### my %service_slp = ( @{[ SERVICE_FSP ]} => TYPE_FSP, @{[ SERVICE_BPA ]} => TYPE_BPA, @{[ SERVICE_HMC ]} => TYPE_HMC, @{[ SERVICE_IVM ]} => TYPE_IVM, @{[ SERVICE_MM ]} => TYPE_MM, @{[ SERVICE_RSA ]} => TYPE_RSA ); ####################################### # Basic SLP attributes ####################################### my @header = ( ["device", "%-8s" ], ["type-model", "%-12s"], ["serial-number", "%-15s"], ["ip-addresses", "placeholder"], ["hostname", "%s"] ); ####################################### # Hardware specific SLP attributes ####################################### my %exattr = ( @{[ SERVICE_FSP ]} => [ "bpc-machinetype-model", "bpc-serial-number", "cage-number" ], @{[ SERVICE_BPA ]} => [ "frame-number" ] ); ####################################### # Power methods ####################################### my %mgt = ( lc(TYPE_FSP) => "hmc", lc(TYPE_HMC) => "hmc", lc(TYPE_MM) => "blade", lc(TYPE_HMC) => "hmc", lc(TYPE_IVM) => "ivm", lc(TYPE_RSA) => "blade" ); my @attribs = qw(nodetype model serial groups node mgt mpa id); my $verbose = 0; my %ip_addr = (); my %slp_result = (); my %rsp_result = (); my %opt = (); my $macmap; ########################################################################## # Command handler method from tables ########################################################################## sub handled_commands { if ($^O =~ /^linux/i) { $macmap = xCAT::MacMap->new(); } return( {lsslp=>"lsslp"} ); } ########################################################################## # Invokes the callback with the specified message ########################################################################## sub send_msg { my $request = shift; my $ecode = 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} = \@_; $callback->( \%output ); } } ########################################################################## # Parse the command line options and operands ########################################################################## sub parse_args { my $request = shift; my $args = $request->{arg}; my $cmd = $request->{command}; my %services = ( HMC => SOFTWARE_SERVICE.":".SERVICE_HMC.":", IVM => SOFTWARE_SERVICE.":".SERVICE_IVM.":", BPA => HARDWARE_SERVICE.":".SERVICE_BPA, FSP => HARDWARE_SERVICE.":".SERVICE_FSP, RSA => HARDWARE_SERVICE.":".SERVICE_RSA.":", MM => HARDWARE_SERVICE.":".SERVICE_MM.":" ); ############################################# # 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 b=s x z w r s=s))) { return( usage() ); } ############################################# # Check for switch "-" with no option ############################################# if ( grep(/^-$/, @ARGV )) { return(usage( "Missing option: -" )); } ############################################# # Check for an argument ############################################# if ( defined( $ARGV[0] )) { return(usage( "Invalid Argument: $ARGV[0]" )); } ############################################# # Option -V for verbose output ############################################# if ( exists( $opt{V} )) { $verbose = 1; } ############################################# # Check for mutually-exclusive formatting ############################################# if ( (exists($opt{r}) + exists($opt{x}) + exists($opt{z})) > 1 ) { return( usage() ); } ############################################# # Check for unsupported service type ############################################# if ( exists( $opt{s} )) { if ( !exists( $services{$opt{s}} )) { return(usage( "Invalid service: $opt{s}" )); } $request->{service} = $services{$opt{s}}; } return(0); } ########################################################################## # Validate comma-seperated list of IPs ########################################################################## sub validate_ip { my $request = shift; ########################################### # Option -b specified - validate entries ########################################### if ( exists( $opt{b} )) { foreach ( split /,/, $opt{b} ) { my $ip = $_; ################################### # Length for IPv4 addresses ################################### my (@octets) = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/; if ( scalar(@octets) != 4 ) { return( [1,"Invalid IP address: $ip"] ); } foreach my $octet ( @octets ) { if (( $octet < 0 ) or ( $octet > 255 )) { return( [1,"Invalid IP address: $ip"] ); } } $ip_addr{$ip} = 1; } } ########################################### # Option -b not specified - determine IPs ########################################### else { my $result = ifconfig( $request ); ########################### # Command failed ########################### if ( @$result[0] ) { return( $result ); } if ( (keys %ip_addr) == 0 ) { return( [1,"No adapters configured for broadcast"] ); } } return( [0] ); } ########################################################################## # Determine adapters available for broadcast ########################################################################## sub ifconfig { my $request = shift; my $cmd = "ifconfig -a"; my $result = `$cmd`; ###################################### # Error running command ###################################### if ( !$result ) { return( [1, "Error running '$cmd': $!"] ); } if ( $verbose ) { trace( $request, $cmd ); trace( $request, "Broadcast Interfaces:" ); } if (xCAT::Utils->isAIX()) { ############################################################## # Should look like this for AIX: # en0: flags=4e080863,80<UP,BROADCAST,NOTRAILERS,RUNNING, # SIMPLEX,MULTICAST,GROUPRT,64BIT,PSEG,CHAIN> # inet 30.0.0.1 netmask 0xffffff00 broadcast 30.0.0.255 # inet 192.168.2.1 netmask 0xffffff00 broadcast 192.168.2.255 # en1: ... # ############################################################## my @adapter = split /\w+\d+:\s+flags=/, $result; foreach ( @adapter ) { if ( !($_ =~ /LOOPBACK/ ) and $_ =~ /UP(,|>)/ and $_ =~ /BROADCAST/ ) { my @ip = split /\n/; foreach ( @ip ){ if ( $_ =~ /^\s*inet\s+/ and $_ =~ /broadcast\s+(\d+\.\d+\.\d+\.\d+)/ ) { $ip_addr{$1} = 1; } } } } } else { ############################################################## # Should look like this for Linux: # eth0 Link encap:Ethernet HWaddr 00:02:55:7B:06:30 # inet addr:9.114.154.193 Bcast:9.114.154.223 # inet6 addr: fe80::202:55ff:fe7b:630/64 Scope:Link # UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 # RX packets:1280982 errors:0 dropped:0 overruns:0 frame:0 # TX packets:3535776 errors:0 dropped:0 overruns:0 carrier:0 # collisions:0 txqueuelen:1000 # RX bytes:343489371 (327.5 MiB) TX bytes:870969610 (830.6 MiB) # Base address:0x2600 Memory:fbfe0000-fc0000080 # # eth1 ... # ############################################################## my @adapter= split /\n{2,}/, $result; foreach ( @adapter ) { if ( !($_ =~ /LOOPBACK / ) and $_ =~ /UP / and $_ =~ /BROADCAST / ) { my @ip = split /\n/; foreach ( @ip ) { if ( $_ =~ /^\s*inet addr:/ and $_ =~ /Bcast:(\d+\.\d+\.\d+\.\d+)/ ) { $ip_addr{$1} = 1; } } } } } if ( $verbose ) { foreach ( keys %ip_addr ) { trace( $request, "\t\t$_\tUP,BROADCAST" ); } if ( (keys %ip_addr) == 0 ) { trace( $request, "$cmd\n$result" ); } } return([0]); } ########################################################################## # Verbose mode (-V) ########################################################################## sub trace { my $request = shift; my $msg = shift; if ( $verbose ) { my ($sec,$min,$hour,$mday,$mon,$yr,$wday,$yday,$dst) = localtime(time); my $msg = sprintf "%02d:%02d:%02d %5d %s", $hour,$min,$sec,$$,$msg; send_msg( $request, 0, $msg ); } } ########################################################################## # Forks a process to run the slp command (1 per broadcast adapter) ########################################################################## sub fork_cmd { my $request = shift; my $ip = shift; my $arg = shift; my $services = shift; ####################################### # Pipe childs output back to parent ####################################### my $parent; my $child; pipe $parent, $child; my $pid = fork; if ( !defined($pid) ) { ################################### # Fork error ################################### send_msg( $request, 1, "Fork error: $!" ); return undef; } elsif ( $pid == 0 ) { ################################### # Child process ################################### close( $parent ); $request->{pipe} = $child; invoke_cmd( $request, $ip, $arg, $services ); exit(0); } else { ################################### # Parent process ################################### close( $child ); return( $parent ); } return(0); } ########################################################################## # Run the forked command and send reply to parent ########################################################################## sub invoke_cmd { my $request = shift; my $ip = shift; my $args = shift; my $services = shift; my $converge = 1; my $tries = 5; ######################################## # Telnet (rspconfig) command ######################################## if ( !defined( $services )) { my $mm = $args->{$ip}; my @cmds = ( "snmpcfg=enable", "sshcfg=enable", "network_reset=$mm->{args}" ); if ( $verbose ) { trace( $request, "Forked: ($ip)->($mm->{args})" ); } my $result = xCAT_plugin::blade::telnetcmds( $ip, $mm->{username}, $mm->{password}, 0, @cmds ); #################################### # Pass result array back to parent #################################### my @data = ("RSPCONFIG6sK4ci", $ip, @$result[0], @$result[2]); my $out = $request->{pipe}; print $out freeze( \@data ); print $out "\nENDOFFREEZE6sK4ci\n"; return; } ######################################## # SLP broadcast command ######################################## my $result = runslp($args, $ip, $services, $request, $tries, $converge); my $unicast = @$result[0]; my $values = @$result[1]; ######################################## # May have to send additional unicasts ######################################## if ( keys (%$unicast) ) { foreach my $url ( keys %$unicast ) { my ($service,$addr) = split "://", $url; my $sockaddr = inet_aton( $addr ); #################################### # Make sure can resolve if hostname #################################### if ( !defined( $sockaddr )) { if ( $verbose ) { trace( $request, "Cannot convert '$addr' to dot-notation" ); } next; } $addr = inet_ntoa( $sockaddr ); $result = runslp( $args, $addr, [$service], $request, 1 ); my $data = @$result[1]; my ($key) = keys %$data; if ( defined($key) ) { $values->{"URL: $url\n$data->{$key}\n"} = 1; } } } ######################################## # No valid responses received ######################################## if (( keys (%$values )) == 0 ) { return; } ######################################## # Pass result array back to parent ######################################## my @results = ("FORMATDATA6sK4ci", $values ); my $out = $request->{pipe}; print $out freeze( \@results ); print $out "\nENDOFFREEZE6sK4ci\n"; } ########################################################################## # Run the SLP command, process the response, and send to parent ########################################################################## sub runslp { my $slpcmd = shift; my $ip = shift; my $services = shift; my $request = shift; my $max = shift; my $converge = shift; my %result = (); my %unicast = (); foreach my $type ( @$services ) { my $try = 0; my $cmd = "$slpcmd --address=$ip --type=$type"; ############################################### # If --converge is specified, slp_query will # broadcast a service-request to the broadcast # address specified by --address. If not # specified, slp_query will unicast an attribute # request to the URL specified by --type to # the remote target specified by --address. ############################################### if ( defined($converge) ) { $cmd .= " --converge=$converge"; } while ( $try++ < $max ) { if ( $verbose ) { trace( $request, $cmd ); trace( $request, "Attempt $try of $max\t( $ip\t$type )" ); } ########################################### # Serialize broadcasts out each adapter ########################################### if ( !open( OUTPUT, "$cmd 2>&1 |")) { send_msg( $request, 1, "Fork error: $!" ); return undef; } ############################### # Get command output ############################### my $rsp; while ( <OUTPUT> ) { $rsp.=$_; } close OUTPUT; ############################### # No replies ############################### if ( !$rsp ) { if ( $verbose ) { trace( $request, ">>>>>> No Response" ); } next; } ########################################### # split into array of individual responses: # 0 # 1 # 75 # URL: service:management-software.IBM... # ATTR: (type=hardware-management-cons... # (serial-number=KPHHK24),(name=c76v2h... # 1ab1dd89ca8e0763e),(ip-address=192.1... # 0CR3*KPHHK24),(web-management-interf... # 2.ppd.pok.ibm.com:8443),(cimom-port=... # # 0 # 1 # 69 # URL: # ATTR: # ... # # For IVM, running AIX 53J (6/07) release, # there is an AIX SLP bug where IVM will # respond to SLP broadcasts with its URL # only and not its attributes. An SLP # unicast to the URL address is necessary # to acquire the attributes. This was fixed # in AIX 53L (11/07). # ########################################### foreach ( split /\n{2,}/,$rsp ) { if ( $_ =~ s/(\d+)\n(\d+)\n(\d+)\n// ) { if ( $verbose ) { trace( $request, "SrvRqst Response ($1)($2)($3)" ); trace( $request, "$_\n" ); } ################################### # Response has "ATTR" field ################################### if ( /ATTR: / ) { $result{$_} = 1; } ################################### # No "ATTR" - have to unicast ################################### elsif ( /.*URL: (.*)/ ) { $unicast{$1} = $1; } } elsif ( $verbose ) { trace( $request, "DISCARDING: $_" ); } } } } return( [\%unicast,\%result] ); } ########################################################################## # Formats slp responses ########################################################################## sub format_output { my $request = shift; my $values = shift; my $rsp = shift; my $length = length( $header[IP_ADDRESSES][TEXT] ); my $result; ########################################### # Query switch ports ########################################### my $mm = switch_cmd( $request, $values ); ########################################### # Parse responses and add to hash ########################################### my $outhash = parse_responses( $request, $values, $mm, \$length ); ########################################### # No responses ########################################### if (( keys %$outhash ) == 0 ){ send_msg( $request, 0, "No responses" ); return; } ########################################### # -w flag for write to xCat database ########################################### if ( exists( $opt{w} )) { xCATdB( $outhash ); } ########################################### # -r flag for raw response format ########################################### if ( exists( $opt{r} )) { foreach ( keys %$outhash ) { $result .= "@{ $outhash->{$_}}[5]\n"; } send_msg( $request, 0, $result ); return; } ########################################### # -x flag for xml format ########################################### if ( exists( $opt{x} )) { send_msg( $request, 0, format_xml( $outhash )); return; } ########################################### # -z flag for stanza format ########################################### if ( exists( $opt{z} )) { send_msg( $request, 0, format_stanza( $outhash )); return; } ########################################### # Get longest IP for formatting purposes ########################################### my $format = sprintf "%%-%ds", ( $length + 2 ); $header[IP_ADDRESSES][FORMAT] = $format; ########################################### # Display header ########################################### foreach ( @header ) { $result .= sprintf @$_[1], @$_[0]; } $result .= "\n"; ########################################### # Display response attributes ########################################### foreach ( sort keys %$outhash ) { my $data = $outhash->{$_}; my $i = 0; foreach ( @header ) { $result .= sprintf @$_[1], @$data[$i++]; } $result .= "\n"; } send_msg( $request, 0, $result ); } ########################################################################## # Get IP from SLP URL response ########################################################################## sub getip_from_url { my $request = shift; my $url = shift; ###################################################################### # Extract the IP from the URL. Generally, the URL # should be in the following format (the ":0" port number # may or may not be present): # service:management-hardware.IBM:management-module://9.114.113.78:0 # service:management-software.IBM:integrated-virtualization-manager://zd21p1.rchland.ibm.com ###################################################################### if (($url !~ /service:.*:\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*/ )) { return undef; } return( $1 ); } ########################################################################## # Get hostname from SLP URL response ########################################################################## sub gethost_from_url { my $request = shift; my $url = shift; ####################################### # Extract IP from URL ####################################### my $ip = getip_from_url( $request, $url ); if ( !defined( $ip )) { return undef; } ####################################### # Check if valid IP ####################################### my $packed = inet_aton( $ip ); if ( length( $packed ) != 4 ) { if ( $verbose ) { trace( $request, "Invalid IP address in URL: $ip" ); } return undef; } ####################################### # Convert IP to hostname ####################################### my $host = gethostbyaddr( $packed, AF_INET ); if ( !$host or $! ) { return( $ip ); } ####################################### # Convert hostname to short-hostname ####################################### if ( $host =~ /([^\.]+)\./ ) { $host = $1; } return( $host ); ########################################### # Otherwise, URL is not in IP format ########################################### if ( !($url =~ /service:.*:\/\/(.*)/ )) { if ( $verbose ) { trace( $request, "Invalid URL: $_[0]" ); } return undef; } return( $1 ); } ########################################################################## # Example slp_query command "service-request" output. The following # attributes can be returned in any order within an SLP response. # Note: The leading 3 numbers preceeding the URL: and ATTR: fields # represent: # error code, # URL count, # URL length, respectively. # 0 # 1 # 75 # URL: service:management-software.IBM:hardware-management-console://192.168.1.110 # ATTR: (type=hardware-management-console),(level=3),(machinetype-model=7310CR3), # (serial-number=KPHHK24),(name=c76v2hmc02.ppd.pok.ibm.com),(uuid=de335adf051eb21 # 1ab1dd89ca8e0763e),(ip-address=192.168.1.110,9.114.47.154),(web-url=),(mtms=731 # 0CR3*KPHHK24),(web-management-interface=true),(secure-web-url=https://c76v2hmc0 # 2.ppd.pok.ibm.com:8443),(cimom-port=),(secure-cimom-port=5989) # # 0 # 1 # 69 # ... # # Example slp_query command "attribute-request" output. The following # attributes can be returned in any order within an SLP response. # Note: The leading 3 numbers preceeding the URL: and ATTR: fields # represent: # error code, # 0, (hardcoded) # ATTR length, respectively. # 0 # 0 # 354 # ATTR: (type=integrated-virtualization-manager),(level=3),(machinetype-model=911051A), # (serial-number=1075ECF),(name=p705ivm.clusters.com),(ip-address=192.168.1.103), # (web-url=http://p705ivm.clusters.com/),(mtms=911051A*1075ECF),(web-management- # interface=TRUE),(secure-web-url=https://p705ivm.clusters.com/),(cimom-port=5988), # (secure-cimom-port=5989),(lparid=1) # ######################################################################### sub parse_responses { my $request = shift; my $values = shift; my $mm = shift; my $length = shift; my %outhash = (); my @attr = ( "type", "machinetype-model", "serial-number", "ip-address" ); foreach my $rsp ( @$values ) { ########################################### # Get service-type from response ########################################### my @result = (); my $host; ########################################### # service-type attribute not found ########################################### if ( $rsp !~ /\(type=([\w\-\.,]+)\)/ ) { if ( $verbose ) { trace( $request, "(type) attribute not found: $rsp" ); } next; } ########################################### # Valid service-type attribute ########################################### my $type = $1; ########################################### # Unsupported service-type ########################################### if ( !exists($service_slp{$type} )) { if ( $verbose ) { trace( $request, "Discarding unsupported type: $type" ); } next; } ########################################### # RSA/MM - slightly different attributes ########################################### if (( $type eq SERVICE_RSA ) or ( $type eq SERVICE_MM )) { $attr[1] = "enclosure-machinetype-model"; $attr[2] = "enclosure-serial-number"; } ########################################### # Extract the attributes ########################################### foreach ( @attr ) { $rsp =~ /\($_=([\w\-\.,]+)\)/; push @result, $1; } ########################################### # Use the IP/Hostname contained in the URL # not the (ip-address) field since for FSPs # it may contain default IPs which could # all be the same. If the response contains # a "name" attribute as the HMC does, use # that instead of the URL. # ########################################### if (( $type eq SERVICE_HMC ) or ( $type eq SERVICE_IVM )) { if ( $rsp =~ /\(name=([\w\-\.,]+)\)/ ) { $host = $1; ################################### # Convert to short-hostname ################################### if ( $host =~ /([^\.]+)\./ ) { $host = $1; } } } ########################################### # Seperate ATTR and URL portions: # 0 # 1 # 75 # URL: service:management-software.IBM... # ATTR: (type=hardware-management-cons... # (serial-number=KPHHK24),(name=c76v2h... # 1ab1dd89ca8e0763e),(ip-address=192.1... # 0CR3*KPHHK24),(web-management-interf... # 2.ppd.pok.ibm.com:8443),(cimom-port=... # ########################################### $rsp =~ /.*URL: (.*)\nATTR: +(.*)/; ########################################### # If MM, use the discovered host ########################################### if (( $type eq SERVICE_MM ) and ( defined( $mm ))) { my $ip = getip_from_url( $request, $1 ); if ( defined( $ip )) { if ( exists( $mm->{$ip}->{args} )) { $mm->{$ip}->{args} =~ /^.*,(.*)$/; $host = $1; } } } ########################################### # Get host directly from URL ########################################### if ( !defined($host) ) { $host = gethost_from_url( $request, $1 ); if ( !defined( $host )) { next; } } push @result, $host; push @result, $2; ########################################### # Strip commas from IP list ########################################### $result[3] =~ s/,/ /g; my $ip = $result[3]; ########################################### # Process any extra attributes ########################################### foreach ( @{$exattr{$type}} ) { push @result, ($rsp =~ /\($_=([\w\-\.,]+)\)/) ? $1 : "0"; } ########################################### # Save longest IP for formatting purposes ########################################### if ( length( $ip ) > $$length ) { $$length = length( $ip ); } $result[0] = $service_slp{$type}; $outhash{$host} = \@result; } return( \%outhash ); } ########################################################################## # Write result to xCat database ########################################################################## sub xCATdB { my $outhash = shift; my %keyhash = (); my %updates = (); foreach ( keys %$outhash ) { my $data = $outhash->{$_}; my $type = @$data[0]; if ( $type =~ /^BPA$/ ) { my $model = @$data[1]; my $serial = @$data[2]; my $ips = @$data[3]; my $name = @$data[4]; my $id = @$data[6]; #################################### # N/A Values #################################### my $prof = ""; my $frame = ""; my $values = join( ",", lc($type),$name,$id,$model,$serial,$name,$prof,$frame,$ips ); xCAT::PPCdb::add_ppc( $type, [$values] ); } elsif ( $type =~ /^(HMC|IVM)$/ ) { xCAT::PPCdb::add_ppchcp( $type, $data ); } elsif ( $type =~ /^FSP$/ ) { ######################################## # BPA frame this CEC is in ######################################## my $frame = ""; my $model = @$data[1]; my $serial = @$data[2]; my $ips = @$data[3]; my $name = @$data[4]; my $bpc_model = @$data[6]; my $bpc_serial = @$data[7]; my $cageid = @$data[8]; ######################################## # May be no Frame with this FSP ######################################## if (( $bpc_model ne "0" ) and ( $bpc_serial ne "0" )) { $frame = "$bpc_model*$bpc_serial"; } ######################################## # "Factory-default" FSP name format: # Server-<type>-<model>-<serialnumber> # ie. Server-9117-MMA-SN10F6F3D # # If the IP address cannot be converted # to a shirt-hostname use the following: # # Note that this may not be the name # that the user (or the HMC) knows this # CEC as. This is the "factory-default" # CEC name. SLP does not return the # user- or system-defined CEC name and # FSPs are assigned dynamic hostnames # by DHCP so there is no point in using # the short-hostname as the name. ######################################## if ( $name =~ /^[\d]{1}/ ) { $name = "Server-$model-$serial"; } ######################################## # N/A Values ######################################## my $prof = ""; my $server = ""; my $values = join( ",", $type,$name,$cageid,$model,$serial,$server,$prof,$frame,$ips ); xCAT::PPCdb::add_ppc( "fsp", [$values] ); } elsif ( $type =~ /^(RSA|MM)$/ ) { xCAT::PPCdb::add_systemX( $type, $data ); } } } ########################################################################## # Stanza formatting ########################################################################## sub format_stanza { my $outhash = shift; my $result; ##################################### # Write attributes ##################################### foreach ( keys %$outhash ) { my @data = @{$outhash->{$_}}; my $type = lc($data[0]); my $name = $data[4]; my $i = 0; ################################# # Node attributes ################################# $result .= "$name:\n\tobjtype=node\n"; ################################# # Add each attribute ################################# foreach ( @attribs ) { my $d = $data[$i++]; if ( /^node$/ ) { next; } elsif ( /^nodetype$/ ) { $d = $type; } elsif ( /^groups$/ ) { $d = "$type,all"; } elsif ( /^mgt$/ ) { $d = $mgt{$type}; } elsif ( /^(id|mpa)$/ ) { if ( $type =~ /^(mm|rsa)$/ ) { $d = (/^id$/) ? "0" : $name; } else { next; } } $result .= "\t$_=$d\n"; } } return( $result ); } ########################################################################## # XML formatting ########################################################################## sub format_xml { my $outhash = shift; my $xml; ##################################### # Create XML formatted attributes ##################################### foreach ( keys %$outhash ) { my @data = @{ $outhash->{$_}}; my $type = lc($data[0]); my $name = $data[4]; my $i = 0; ################################# # Initialize hash reference ################################# my $href = { Node => { } }; ################################# # Add each attribute ################################# foreach ( @attribs ) { my $d = $data[$i++]; if ( /^nodetype$/ ) { $d = $type; } elsif ( /^groups$/ ) { $d = "$type,all"; } elsif ( /^mgt$/ ) { $d = $mgt{$type}; } elsif ( /^(id|mpa)$/ ) { if ( $type =~ /^(mm|rsa)$/ ) { $d = (/^id$/) ? "0" : $name; } else { next; } } $href->{Node}->{$_} = $d; } ################################# # XML encoding ################################# $xml.= XMLout($href, NoAttr => 1, KeyAttr => [], RootName => undef ); } return( $xml ); } ########################################################################## # OpenSLP is running on: # p6 FSP # p6 BPA # IBM SLP is running on: # p5 FSP # p5 BPA # HMC # MM # RSA # AIX SLP # IVM # # OpenSLP v. IBM SLP # (1) OpenSLP does not support wildcards (i.e. service:management-*.IBM: ) # (2) OpenSLP does not support ':' at the end of services # (i.e. service:management-hardware.IBM:). Unfortunately, IBM SLP # requires it. # # Given the above, to collect all the above service types, it is # necesary to broadcast: # (1) service:management-*.IBM: for all IBM SLP hardware # (2) service:management-hardware.IBM for OpenSLP hardware (p6 FSP/BPA) # (IBM SLP hardware will not respond since there is no trailing ":") # (3) IBM SLP does not require a trailing ':' with "cec-service-processor" # concrete type only. # ########################################################################## sub slp_query { my $request = shift; my $callback = $request->{callback}; my $slpcmd = "/usr/sbin/slp_query"; my $start; my @services = ( HARDWARE_SERVICE, WILDCARD_SERVICE ); ############################################# # slp_query not installed ############################################# if ( !-x $slpcmd ) { send_msg( $request, 1, "Command not installed: $slpcmd" ); return( [RC_ERROR] ); } ############################################# # slp_query runnable - dependent on libstdc++ # Test for usage statement. ############################################# my $output = `$slpcmd 2>&1`; if ( $output !~ /slp_query --type=service-type-string/ ) { send_msg( $request, 1, $output ); return( [RC_ERROR] ); } ############################################# # Query specific service; otherwise, # query all hardware/software services ############################################# if ( exists( $opt{s} )) { @services = $request->{service}; } if ( $verbose ) { ######################################### # Write header for trace ######################################### my $tm = localtime( time ); my $msg = "\n-------- $tm\nTime PID"; trace( $request, $msg ); } ############################################# # Get/validate broadcast IPs ############################################# my $result = validate_ip( $request ); my $Rc = shift(@$result); if ( $Rc ) { send_msg( $request, 1, @$result[0] ); return( [RC_ERROR] ); } if ( $verbose ) { $start = Time::HiRes::gettimeofday(); } ############################################# # Fork one process per broadcast adapter ############################################# my $children = 0; $SIG{CHLD} = sub { while (waitpid(-1, WNOHANG) > 0) { $children--; } }; my $fds = new IO::Select; foreach ( keys %ip_addr ) { my $pipe = fork_cmd( $request, $_, $slpcmd, \@services ); if ( $pipe ) { $fds->add( $pipe ); $children++; } } ############################################# # Process slp responses from children ############################################# while ( $children > 0 ) { child_response( $callback, $fds ); } while (child_response($callback,$fds)) {} if ( $verbose ) { my $elapsed = Time::HiRes::gettimeofday() - $start; my $msg = sprintf( "Total SLP Time: %.3f sec\n", $elapsed ); trace( $request, $msg ); } ############################################# # Combined responses from all children ############################################# my @all_results = keys %slp_result; format_output( $request, \@all_results ); return( [SUCCESS,\@all_results] ); } ########################################################################## # Collect output from the child processes ########################################################################## sub child_response { my $callback = shift; my $fds = shift; my @ready_fds = $fds->can_read(1); foreach my $rfh (@ready_fds) { my $data = <$rfh>; ################################# # Read from child process ################################# if ( defined( $data )) { while ($data !~ /ENDOFFREEZE6sK4ci/) { $data .= <$rfh>; } my $responses = thaw($data); ############################# # Formatted SLP results ############################# if ( @$responses[0] =~ /^FORMATDATA6sK4ci$/ ) { shift @$responses; foreach ( keys %$responses ) { $slp_result{$_} = 1; } next; } ############################# # rspconfig results ############################# if ( @$responses[0] =~ /^RSPCONFIG6sK4ci$/ ) { shift @$responses; my $ip = shift(@$responses); $rsp_result{$ip} = $responses; next; } ############################# # Message or verbose trace ############################# foreach ( @$responses ) { $callback->( $_ ); } next; } ################################# # Done - close handle ################################# $fds->remove($rfh); close($rfh); } } ############################################################################# # Preprocess request from xCat daemon and send request to service nodes ############################################################################# sub preprocess_request { my $req = shift; if ($req->{_xcatdest}) { return [$req]; } #exit if preprocessed my $callback=shift; my @requests; #################################### # Prompt for usage if needed #################################### my $noderange = $req->{node}; #Should be arrayref 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; } ########################################### # find all the service nodes for xCAT cluster # build an individual request for each service node ########################################### my $nrtab=xCAT::Table->new("noderes", -create =>0); my @all=$nrtab->getAllNodeAttribs(['servicenode']); my %sv_hash=(); foreach (@all) { if ($_->{servicenode}) {$sv_hash{$_->{servicenode}}=1;} } ########################################### # build each request for each service node ########################################### my @requests=(); foreach my $sn (keys (%sv_hash)) { my $reqcopy = {%$req}; $reqcopy->{_xcatdest} = $sn; push @requests, $reqcopy; } return \@requests; } ########################################################################## # Match SLP IP/ARP MAC/Switch table port to actual switch data ########################################################################## sub switch_cmd { my $req = shift; my $slp = shift; my %mm; my %slp; my %hosts; my @entries; my $hosttab = xCAT::Table->new( 'hosts' ); my $swtab = xCAT::Table->new( 'switch' ); ########################################### # No tables ########################################### if ( !defined($swtab) or !defined($hosttab) ) { return; } ########################################### # Any MMs in SLP response ########################################### foreach ( @$slp ) { if ( /\(type=management-module\)/ and /\(ip-address=([^\),]+)/) { $slp{$1} = undef; } } ########################################### # No MMs in response ########################################### if ( !%slp ) { return; } ########################################### # Any entries in switch table ########################################### foreach ( $swtab->getAllNodeAttribs([qw(node)]) ) { push @entries, $_->{node}; } ########################################### # Any entries in hosts table ########################################### if ( $verbose ) { trace( $req, "SWITCH/HOSTS TABLE:" ); } foreach ( @entries ) { my $ent = $hosttab->getNodeAttribs($_,[qw(ip)]); if ( !$ent ) { next; } $hosts{$_} = $ent->{ip}; if ( $verbose ) { trace( $req, "\t\t($_)->($ent->{ip})" ); } } ########################################### # No MMs in hosts/switch table ########################################### if ( !%hosts ) { return; } ########################################### # Ping each MM to update arp table ########################################### foreach my $ip ( keys %slp ) { my $cmd = `ping -c 1 -w 0 $ip`; } ########################################### # Match discovered IP to MAC in arp table ########################################### my $arp = `/sbin/arp -n`; my @arpents = split /\n/, $arp; if ( $verbose ) { trace( $req, "ARP TABLE:" ); } foreach ( @arpents ) { /^(\S+)+\s+\S+\s+(\S+)\s/; if ( exists( $slp{$1} )) { if ( $verbose ) { trace( $req, "\t\t($1)->($2)" ); } $slp{$1} = $2; } } ########################################### # No discovered IP - MAC matches ########################################### if ( !grep( defined($_), values %slp )) { return; } if ( $verbose ) { trace( $req, "getting switch information...." ); } foreach my $ip ( keys %slp ) { ####################################### # Not in SLP response ####################################### if ( !defined( $slp{$ip} ) or !defined( $macmap )) { next; } ####################################### # Get node from switch ####################################### my $name = $macmap->find_mac( $slp{$ip} ); if ( !defined( $name )) { if ( $verbose ) { trace( $req, "\t\t($slp{$ip})-> NOT FOUND" ); } next; } if ( $verbose ) { trace( $req, "\t\t($slp{$ip})-> $name" ); } ####################################### # In hosts table ####################################### if ( defined( $hosts{$name} )) { if ( $ip eq $hosts{$name} ) { if ( $verbose ) { trace( $req, "MM already set '$ip' - skipping" ); } next; } $mm{$ip}->{args} = "$hosts{$name},$name"; } } ########################################### # No MMs ########################################### if ( !%mm ) { if ( $verbose ) { trace( $req, "No ARP-Switch-SLP matches found" ); } return; } ########################################### # Update MM hardware w/discovery info ########################################### my $result = rspconfig( $req, \%mm ); return( $result ); } ########################################################################## # Run rspconfig against MMs ########################################################################## sub rspconfig { my $request = shift; my $mm = shift; my $callback = $request->{callback}; my $bladeuser = 'USERID'; my $bladepass = 'PASSW0RD'; my $start; if ( $verbose ) { trace( $request, "telneting to management-modules....." ); $start = Time::HiRes::gettimeofday(); } ############################################# # Check passwd table for userid/password ############################################# my $passtab = xCAT::Table->new('passwd'); if ( $passtab ) { my ($ent) = $passtab->getAttribs({key=>'blade'},'username','password'); if ( defined( $ent )) { $bladeuser = $ent->{username}; $bladepass = $ent->{password}; } } ############################################# # Get MM userid/password ############################################# my $mpatab = xCAT::Table->new('mpa'); foreach ( keys %$mm ) { my $user = $bladeuser; my $pass = $bladepass; if ( defined( $mpatab )) { my ($ent) = $mpatab->getAttribs({mpa=>$_},'username','password'); if ( defined( $ent->{password} )) { $pass = $ent->{password}; } if ( defined( $ent->{username} )) { $user = $ent->{username}; } } $mm->{$_}->{username} = $user; $mm->{$_}->{password} = $pass; } ############################################# # Fork one process per MM ############################################# my $children = 0; $SIG{CHLD} = sub { while (waitpid(-1, WNOHANG) > 0) { $children--; } }; my $fds = new IO::Select; foreach my $ip ( keys %$mm ) { my $pipe = fork_cmd( $request, $ip, $mm ); if ( $pipe ) { $fds->add( $pipe ); $children++; } } ############################################# # Process responses from children ############################################# while ( $children > 0 ) { child_response( $callback, $fds ); } while (child_response($callback,$fds)) {} if ( $verbose ) { my $elapsed = Time::HiRes::gettimeofday() - $start; my $msg = sprintf( "Total rspconfig Time: %.3f sec\n", $elapsed ); trace( $request, $msg ); } foreach my $ip ( keys %rsp_result ) { ################################# # Error logging on to MM ################################# my $result = $rsp_result{$ip}; my $Rc = shift(@$result); if ( $Rc != SUCCESS ) { ############################# # MM connect error ############################# if ( ref(@$result[0]) ne 'ARRAY' ) { if ( $verbose ) { trace( $request, "$ip: @$result[0]" ); } delete $mm->{$ip}; next; } } ################################## # Process each response ################################## foreach ( @{@$result[0]} ) { if ( $verbose ) { trace( $request, "$ip: $_" ); } /^(\S+)\s+(\d+)/; my $cmd = $1; $Rc = $2; if ( $cmd =~ /^network_reset/ ) { if ( $Rc != SUCCESS ) { delete $mm->{$ip}; next; } if ( $verbose ) { trace( $request,"Resetting management-module ($ip)...." ); } } } } ###################################### # Update etc/hosts ###################################### my $fname = "/etc/hosts"; if ( $verbose ) { trace( $request, "updating /etc/hosts...." ); } unless ( open( HOSTS,"<$fname" )) { if ( $verbose ) { trace( $request, "Error opening '$fname'" ); } return( $mm ); } my @rawdata = <HOSTS>; close( HOSTS ); ###################################### # Remove old entry ###################################### foreach ( keys %$mm) { my ($ip,$host) = split /,/,$mm->{$_}->{args}; foreach ( @rawdata ) { if ( /^#/ or /^\s*\n$/ ) { next; } elsif ( /\s+$host\s+$/ ) { s/$_//; } } push @rawdata,"$ip\t$host\n"; } ###################################### # Rewrite file ###################################### unless ( open( HOSTS,">$fname" )) { if ( $verbose ) { trace( $request, "Error opening '$fname'" ); } return( $mm ); } print HOSTS @rawdata; close( HOSTS ); return( $mm ); } ########################################################################## # Process request from xCat daemon ########################################################################## sub process_request { my $req = shift; my $callback = shift; my $doreq = 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); } ########################################### # Broadcast SLP ########################################### $result = slp_query( \%request ); my $Rc = shift(@$result); return( $Rc ); } 1;