a4c0b58fe8
-Do not warn in the now preferred case of no explicit tftp server -Fix PXE and yaboot to clean stale links -PXE preprocess now specifies nodeset should be sent to all servers -PXE and DHCP only initiate meaningful work if specified node is on the same network -Utils now has nodeonmynet and thisisnothost functions which are commonly needed in scope detirmination git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@379 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
469 lines
14 KiB
Perl
469 lines
14 KiB
Perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
|
|
package xCAT_plugin::dhcp;
|
|
use xCAT::Table;
|
|
use Data::Dumper;
|
|
use MIME::Base64;
|
|
use Getopt::Long;
|
|
Getopt::Long::Configure("bundling");
|
|
Getopt::Long::Configure("pass_through");
|
|
use Socket;
|
|
use Sys::Syslog;
|
|
use IPC::Open2;
|
|
|
|
my @dhcpconf; #Hold DHCP config file contents to be written back.
|
|
my @nrn; # To hold output of netstat -rn to be consulted throughout process
|
|
my $domain;
|
|
my $omshell;
|
|
my $statements; #Hold custom statements to be slipped into host declarations
|
|
my $callback;
|
|
|
|
|
|
sub handled_commands {
|
|
return {
|
|
makedhcp => "dhcp",
|
|
}
|
|
}
|
|
|
|
sub delnode {
|
|
my $node = shift;
|
|
my $inetn = inet_aton($node);
|
|
|
|
my $mactab = xCAT::Table->new('mac');
|
|
if ($mactab) { $ent = $mactab->getNodeAttribs($node,[qw(mac)]); }
|
|
if ($ent and $ent->{mac}) {
|
|
my @macs = split(/\|/,$ent->{mac});
|
|
my $mace;
|
|
foreach $mace (@macs) {
|
|
my $mac;
|
|
my $hname;
|
|
($mac,$hname) = split (/!/,$mace);
|
|
unless ($hname) { $hname = $node; }
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$hname\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
if ($inetn) {
|
|
my $ip = inet_ntoa(inet_aton($hname));;
|
|
unless ($ip) { return; }
|
|
print $omshell "new host\n";
|
|
print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
}
|
|
}
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$node\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
if ($inetn) {
|
|
my $ip = inet_ntoa(inet_aton($node));;
|
|
unless ($ip) { return; }
|
|
print $omshell "new host\n";
|
|
print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
}
|
|
}
|
|
|
|
|
|
sub addnode {
|
|
#Use omshell to add the node.
|
|
#the process used is blind typing commands that should work
|
|
#it tries to delet any conflicting entries matched by name and
|
|
#hardware address and ip address before creating a brand now one
|
|
#unfortunate side effect: dhcpd.leases can look ugly over time, when
|
|
#doing updates would keep it cleaner, good news, dhcpd restart cleans
|
|
#up the lease file the way we would want anyway.
|
|
my $node = shift;
|
|
my $ent;
|
|
my $nrtab = xCAT::Table->new('noderes');
|
|
if ($nrtab) {
|
|
my $ent;
|
|
$ent = $nrtab->getNodeAttribs($node,['tftpserver']);
|
|
if ($ent and $ent->{tftpserver}) {
|
|
$statements = 'next-server = \"'.inet_ntoa(inet_aton($ent->{tftpserver})).'\";'.$statements;
|
|
} #else {
|
|
# $ent = $nrtab->getNodeAttribs($node,['servicenode']);
|
|
# if ($ent and $ent->{servicenode}) {
|
|
# $statements = 'next-server = \"'.inet_ntoa(inet_aton($ent->{servicenode})).'\";'.$statements;
|
|
# }
|
|
#}
|
|
}
|
|
my $mactab = xCAT::Table->new('mac');
|
|
unless ($mactab) {
|
|
$callback->({error=>["Unable to open mac table, it may not exist yet"],errorcode=>[1]});
|
|
return;
|
|
}
|
|
$ent = $mactab->getNodeAttribs($node,[qw(mac)]);
|
|
unless ($ent and $ent->{mac}) {
|
|
$callback->({error=>["Unable to find mac address for $node"],errorcode=>[1]});
|
|
return;
|
|
}
|
|
my @macs = split(/\|/,$ent->{mac});
|
|
my $mace;
|
|
foreach $mace (@macs) {
|
|
my $mac;
|
|
my $hname;
|
|
($mac,$hname) = split (/!/,$mace);
|
|
unless ($hname) { $hname = $node; } #Default to hostname equal to nodename
|
|
unless ($mac) { next; } #Skip corrupt format
|
|
my $inetn = inet_aton($hname);
|
|
unless ($inetn) {
|
|
syslog("local1|err","xCAT DHCP plugin unable to resolve IP for $hname (for $node)");
|
|
return;
|
|
}
|
|
my $ip = inet_ntoa(inet_aton($hname));;
|
|
syslog("local4|err","Setting $node ($hname|$ip) to ".$mac);
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$hname\"\n"; #Find and destroy conflict name
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
print $omshell "new host\n";
|
|
print $omshell "set ip-address = $ip\n"; #find and destroy ip conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
print $omshell "new host\n";
|
|
print $omshell "set hardware-address = ".$mac."\n"; #find and destroy mac conflict
|
|
print $omshell "open\n";
|
|
print $omshell "remove\n";
|
|
print $omshell "close\n";
|
|
print $omshell "new host\n";
|
|
print $omshell "set name = \"$hname\"\n";
|
|
print $omshell "set hardware-address = ".$mac."\n";
|
|
print $omshell "set hardware-type = 1\n";
|
|
print $omshell "set ip-address = $ip\n";
|
|
if ($statements) {
|
|
print $omshell "set statements = \"$statements\"\n";
|
|
}
|
|
|
|
print $omshell "create\n";
|
|
print $omshell "close\n";
|
|
unless (grep /#definition for host $node aka host $hname/,@dhcpconf) {
|
|
push @dhcpconf,"#definition for host $node aka host $hname can be found in the dhcpd.leases file\n";
|
|
}
|
|
}
|
|
}
|
|
sub thishostisnot {
|
|
my $comparison = shift;
|
|
my @ips = split /\n/,`/sbin/ip addr`;
|
|
my $comp=inet_aton($comparison);
|
|
foreach (@ips) {
|
|
if (/^\s*inet/) {
|
|
my @ents = split(/\s+/);
|
|
my $ip=$ents[2];
|
|
$ip =~ s/\/.*//;
|
|
if (inet_aton($ip) eq $comp) {
|
|
return 0;
|
|
}
|
|
#print Dumper(inet_aton($ip));
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
sub preprocess_request {
|
|
my $req = shift;
|
|
$callback = shift;
|
|
if ($req->{_xcatdest}) { return [$req]; } #Exit if the packet has been preprocessed in its history
|
|
my @requests = ({%$req}); #Start with a straight copy to reflect local instance
|
|
my $sitetab = xCAT::Table->new('site');
|
|
(my $ent) = $sitetab->getAttribs({key=>'xcatservers'},'value');
|
|
$sitetab->close;
|
|
if ($ent and $ent->{value}) {
|
|
foreach (split /,/,$ent->{value}) {
|
|
if (thishostisnot($_)) {
|
|
my $reqcopy = {%$req};
|
|
$reqcopy->{'_xcatdest'} = $_;
|
|
push @requests,$reqcopy;
|
|
}
|
|
}
|
|
}
|
|
if (scalar(@requests) > 1) { #hierarchy detected, enforce more rigorous sanity
|
|
my $ntab = xCAT::Table->new('networks');
|
|
if ($ntab) {
|
|
foreach (@{$ntab->getAllEntries()}) {
|
|
if ($_->{dynamicrange} and not $_->{dhcpserver}) {
|
|
$callback->({error=>["In a hierachy, network entries with a dynamic range must be explicit in the dhcpserver field"],errorcode=>[1]});
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return \@requests;
|
|
}
|
|
|
|
sub process_request {
|
|
my $req = shift;
|
|
$callback = shift;
|
|
my $sitetab = xCAT::Table->new('site');
|
|
my %activenics;
|
|
my $querynics=1;
|
|
if ($sitetab) {
|
|
my $href;
|
|
($href) = $sitetab->getAttribs({key=>'dhcpinterfaces'},'value');
|
|
unless ($href and $href->{value}) { #LEGACY: singular keyname for old style site value
|
|
($href) = $sitetab->getAttribs({key=>'dhcpinterface'},'value');
|
|
}
|
|
if ($href and $href->{value}) {
|
|
foreach (split /[,\s]+/,$href->{value}) {
|
|
$activenics{$_} = 1;
|
|
$querynics=0;
|
|
}
|
|
}
|
|
($href) = $sitetab->getAttribs({key=>'domain'},'value');
|
|
unless ($href and $href->{value}) {
|
|
$callback->({error=>["No domain defined in site tabe"],errorcode=>[1]});
|
|
return;
|
|
}
|
|
$domain = $href->{value};
|
|
}
|
|
|
|
@dhcpconf = ();
|
|
unless ($req->{arg} or $req->{node}) {
|
|
$callback->({data=>["Usage: makedhcp <-n> <noderange>"]});
|
|
return;
|
|
}
|
|
if (grep /-n/,@{$req->{arg}}) {
|
|
if (-e "/etc/dhcpd.conf") {
|
|
my $bakname = "/etc/dhcpd.conf.xcatbak";
|
|
while (-e $bakname) { #look for unused backup name..
|
|
$bakname .= "~";
|
|
}
|
|
rename("/etc/dhcpd.conf",$bakname);
|
|
}
|
|
} else {
|
|
open($rconf,"/etc/dhcpd.conf"); # Read file into memory
|
|
if ($rconf) {
|
|
while (<$rconf>) {
|
|
push @dhcpconf,$_;
|
|
}
|
|
close($rconf);
|
|
}
|
|
unless ($dhcpconf[0] =~ /^#xCAT/) { #Discard file if not xCAT originated, like 1.x did
|
|
@dhcpconf = ();
|
|
}
|
|
}
|
|
@nrn = split /\n/,`/bin/netstat -rn`;
|
|
splice @nrn,0,2; #get rid of header
|
|
if ($querynics) { #Use netstat to determine activenics only when no site ent.
|
|
foreach (@nrn) {
|
|
my @ent = split /\s+/;
|
|
if ($ent[7] =~ m/(ipoib|ib|vlan|bond|eth|myri|man|wlan)/) { #Mask out many types of interfaces, like xCAT 1.x
|
|
$activenics{$ent[7]} = 1;
|
|
}
|
|
}
|
|
}
|
|
unless ($dhcpconf[0]) { #populate an empty config with some starter data...
|
|
newconfig();
|
|
}
|
|
foreach (keys %activenics) {
|
|
addnic($_);
|
|
}
|
|
if ($req->{node}) {
|
|
@ARGV = @{$req->{arg}};
|
|
$statements="";
|
|
GetOptions(
|
|
's|statements=s' => \$statements
|
|
);
|
|
|
|
|
|
|
|
my $passtab = xCAT::Table->new('passwd');
|
|
my $ent;
|
|
($ent) = $passtab->getAttribs({key=>"omapi"},qw(username password));
|
|
unless ($ent->{username} and $ent->{password}) { return; } # TODO sane err
|
|
#Have nodes to update
|
|
#open2($omshellout,$omshell,"/usr/bin/omshell");
|
|
open($omshell,"|/usr/bin/omshell > /dev/null");
|
|
|
|
print $omshell "key ".$ent->{username}." \"".$ent->{password}."\"\n";
|
|
print $omshell "connect\n";
|
|
foreach(@{$req->{node}}) {
|
|
if (grep /^-d$/,@{$req->{arg}}) {
|
|
delnode $_;
|
|
} else {
|
|
addnode $_;
|
|
}
|
|
}
|
|
close($omshell);
|
|
}
|
|
foreach (@nrn) {
|
|
my @line = split /\s+/;
|
|
if ($activenics{$line[7]} and $line[3] !~ /G/) {
|
|
addnet($line[0],$line[2]);
|
|
}
|
|
}
|
|
writeout();
|
|
}
|
|
|
|
sub addnet {
|
|
my $net = shift;
|
|
my $mask = shift;
|
|
my $nic;
|
|
unless (grep /\} # $net\/$mask subnet_end/,@dhcpconf) {
|
|
foreach (@nrn) { # search for relevant NIC
|
|
my @ent = split /\s+/;
|
|
if ($ent[0] eq $net and $ent[2] eq $mask) {
|
|
$nic=$ent[7];
|
|
}
|
|
}
|
|
print "Need to add $net $mask under $nic\n";
|
|
my $idx=0;
|
|
while ($idx <= $#dhcpconf) {
|
|
if ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/) {
|
|
last;
|
|
}
|
|
$idx++;
|
|
}
|
|
unless ($dhcpconf[$idx] =~ /\} # $nic nic_end\n/) {
|
|
return 1; #TODO: this is an error condition
|
|
}
|
|
# if here, means we found the idx before which to insert
|
|
my $nettab = xCAT::Table->new("networks");
|
|
my $nameservers;
|
|
my $gateway;
|
|
my $tftp;
|
|
my $range;
|
|
if ($nettab) {
|
|
my ($ent) = $nettab->getAttribs({net=>$net,mask=>$mask},qw(tftpserver nameservers gateway dynamicrange dhcpserver));
|
|
if ($ent and $ent->{nameservers}) {
|
|
$nameservers = $ent->{nameservers};
|
|
} else {
|
|
$callback->({warning=>["No $net specific entry for nameservers, and dhcp plugin not sourcing from site yet (TODO)"]});
|
|
}
|
|
if ($ent and $ent->{tftpserver}) {
|
|
$tftp = $ent->{tftpserver};
|
|
}
|
|
if ($ent and $ent->{gateway}) {
|
|
$gateway = $ent->{gateway};
|
|
}
|
|
if ($ent and $ent->{dynamicrange}) {
|
|
unless ($ent->{dhcpserver} and thishostisnot($ent->{dhcpserver})) { #If specific, only one dhcp server gets a dynamic range
|
|
$range = $ent->{dynamicrange};
|
|
$range =~ s/[,-]/ /g;
|
|
}
|
|
} else {
|
|
$callback->({warning=>["No dynamic range specified for $net, unknown systems on this network will not receive an address"]});
|
|
}
|
|
} else {
|
|
$callback->({error=>["Unable to open networks table, please run makenetworks"],errorcode=>[1]});
|
|
return 1;
|
|
}
|
|
|
|
my @netent;
|
|
@netent = (
|
|
" subnet $net netmask $mask {\n",
|
|
" max-lease-time 43200;\n",
|
|
" min-lease-time 43200;\n",
|
|
" default-lease-time 43200;\n"
|
|
);
|
|
if ($gateway) {
|
|
push @netent," option routers $gateway;\n";
|
|
}
|
|
if ($tftp) {
|
|
push @netent," next-server $tftp;\n";
|
|
}
|
|
push @netent," option domain-name \"$domain\";\n";
|
|
if ($nameservers) {
|
|
push @netent," option domain-name-servers $nameservers;\n";
|
|
}
|
|
push @netent," if option client-architecture = 00:00 { #x86\n";
|
|
push @netent," filename \"pxelinux.0\";\n";
|
|
push @netent," } else if option client-architecture = 00:02 { #ia64\n ";
|
|
push @netent," filename \"elilo.efi\";\n";
|
|
push @netent," } else if substring(filename,0,1) = null { #otherwise, provide yaboot if the client isn't specific\n ";
|
|
push @netent," filename \"/yaboot\";\n";
|
|
push @netent," }\n";
|
|
if ($range) { push @netent," range dynamic-bootp $range;\n" };
|
|
push @netent," } # $net\/$mask subnet_end\n";
|
|
splice(@dhcpconf,$idx,0,@netent);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
sub addnic {
|
|
my $nic = shift;
|
|
my $firstindex=0;
|
|
my $lastindex=0;
|
|
unless (grep /} # $nic nic_end/,@dhcpconf) { #add a section if not there
|
|
print "Adding NIC $nic\n";
|
|
push @dhcpconf,"shared-network $nic {\n";
|
|
push @dhcpconf,"\} # $nic nic_end\n";
|
|
}
|
|
#return; #Don't touch it, it should already be fine..
|
|
#my $idx=0;
|
|
#while ($idx <= $#dhcpconf) {
|
|
# if ($dhcpconf[$idx] =~ /^shared-network $nic {/) {
|
|
# $firstindex = $idx; # found the first place to chop...
|
|
# } elsif ($dhcpconf[$idx] =~ /} # $nic network_end/) {
|
|
# $lastindex=$idx;
|
|
# }
|
|
# $idx++;
|
|
#}
|
|
#print Dumper(\@dhcpconf);
|
|
#if ($firstindex and $lastindex) {
|
|
# splice @dhcpconf,$firstindex,($lastindex-$firstindex+1);
|
|
#}
|
|
#print Dumper(\@dhcpconf);
|
|
}
|
|
|
|
|
|
sub writeout {
|
|
my $targ;
|
|
open($targ,'>',"/etc/dhcpd.conf");
|
|
foreach (@dhcpconf) {
|
|
print $targ $_;
|
|
}
|
|
close($targ)
|
|
}
|
|
|
|
sub newconfig {
|
|
# This function puts a standard header in and enough to make omapi work.
|
|
my $passtab = xCAT::Table->new('passwd',-create=>1);
|
|
push @dhcpconf,"#xCAT generated dhcp configuration\n";
|
|
push @dhcpconf,"\n";
|
|
push @dhcpconf,"authoritative;\n";
|
|
push @dhcpconf,"ddns-update-style none;\n";
|
|
push @dhcpconf,"option client-architecture code 93 = unsigned integer 16;\n";
|
|
push @dhcpconf,"\n";
|
|
push @dhcpconf,"omapi-port 7911;\n"; #Enable omapi...
|
|
push @dhcpconf,"key xcat_key {\n";
|
|
push @dhcpconf," algorithm hmac-md5;\n";
|
|
(my $passent) = $passtab->getAttribs({key=>omapi,username=>'xcat_key'},'password');
|
|
my $secret = encode_base64(genpassword(32)); #Random from set of 62^32
|
|
chomp $secret;
|
|
if ($passent->{password}) { $secret = $passent->{password}; } else {
|
|
$callback->({data=>["The dhcp server must be restarted for OMAPI function to work"]});
|
|
$passtab->setAttribs({key=>omapi},{username=>'xcat_key',password=>$secret});
|
|
}
|
|
|
|
|
|
push @dhcpconf," secret \"".$secret."\";\n";
|
|
push @dhcpconf,"};\n";
|
|
push @dhcpconf,"omapi-key xcat_key;\n";
|
|
}
|
|
|
|
sub genpassword {
|
|
#Generate a pseudo-random password of specified length
|
|
my $length = shift;
|
|
my $password='';
|
|
my $characters= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890';
|
|
srand; #have to reseed, rand is not rand otherwise
|
|
while (length($password) < $length) {
|
|
$password .= substr($characters,int(rand 63),1);
|
|
}
|
|
return $password;
|
|
}
|
|
|
|
|
|
1;
|