package xCAT_plugin::mknb; use strict; use File::Temp qw(tempdir); use xCAT::Utils; use xCAT::TableUtils; use xCAT::NodeRange; use File::Path; use File::Copy; sub handled_commands { return { mknb => 'mknb', }; } sub process_request { my $request = shift; my $callback = shift; my $serialport; my $serialspeed; my $serialflow; my %nobootnicips = (); my $initrd_file = undef; my $xcatdport = 3001; my @entries = xCAT::TableUtils->get_site_attribute("defserialport"); my $t_entry = $entries[0]; if (defined($t_entry)) { $serialport = $t_entry; } @entries = xCAT::TableUtils->get_site_attribute("defserialspeed"); $t_entry = $entries[0]; if (defined($t_entry)) { $serialspeed = $t_entry; } @entries = xCAT::TableUtils->get_site_attribute("defserialflow"); $t_entry = $entries[0]; if (defined($t_entry)) { $serialflow = $t_entry; } @entries = xCAT::TableUtils->get_site_attribute("xcatdport"); $t_entry = $entries[0]; if (defined($t_entry)) { $xcatdport = $t_entry; } @entries = xCAT::TableUtils->get_site_attribute("dhcpinterfaces"); $t_entry = $entries[0]; if (defined($t_entry)) { my %nobootnics = (); foreach my $dhcpif (split /;/, $t_entry) { if ($dhcpif =~ /\|/) { my $isself = 0; (my $ngroup, $dhcpif) = split /\|/, $dhcpif; foreach my $host (noderange($ngroup)) { unless(xCAT::NetworkUtils->thishostisnot($host)) { $isself = 1; } } unless(xCAT::NetworkUtils->thishostisnot($ngroup)) { $isself = 1; } unless ($isself) { next; } } foreach (split /[,\s]+/, $dhcpif) { my ($nicname, $flag) = split /:/; if ($flag and $flag =~ /noboot/i) { $nobootnics{$nicname} = 1; } } } my $nicips = xCAT::NetworkUtils->get_nic_ip(); foreach (keys $nicips) { # To support tagged vlan, create entries in the hash for the # interface name removing the physical interface ending: # 'enP1p12s0f0.2@enP1p12s0f0' => 'enP1p12s0f0.2' if ($_ =~ "@") { my $newkey = $_; $newkey =~ s/\@.*//g; $$nicips{$newkey} = ${nicips}->{$_}; } } foreach (keys %nobootnics) { if (defined($nicips->{$_})) { $nobootnicips{$nicips->{$_}} = 1; } } } my $tftpdir = xCAT::TableUtils->getTftpDir(); my $arch = $request->{arg}->[0]; if (!$arch) { $callback->({ error => "Need to specify architecture (x86, x86_64 or ppc64)" }, { errorcode => [1] }); return; } elsif ($arch eq "ppc64le" or $arch eq "ppc64el") { $callback->({ data => "The arch:$arch is not supported at present, pls use \"ppc64\" instead" }); return; } unless (-d "$::XCATROOT/share/xcat/netboot/$arch" or -d "$::XCATROOT/share/xcat/netboot/genesis/$arch") { $callback->({ error => "Unable to find directory $::XCATROOT/share/xcat/netboot/$arch or $::XCATROOT/share/xcat/netboot/genesis/$arch", errorcode => [1] }); return; } my $configfileonly = $request->{arg}->[1]; if ($configfileonly and $configfileonly ne "-c" and $configfileonly ne "--configfileonly") { $callback->({ error => "The option $configfileonly is not supported", errorcode => [1] }); return; } elsif ($configfileonly) { goto CREAT_CONF_FILE; } unless (-r "/root/.ssh/id_rsa.pub") { if (-r "/root/.ssh/id_rsa") { $callback->({ data => ["Extracting ssh public key from private key"] }); my $rc = system('ssh-keygen -y -f /root/.ssh/id_rsa > /root/.ssh/id_rsa.pub'); if ($rc) { $callback->({ error => ["Failure executing ssh-keygen for root"], errorcode => [1] }); } } else { $callback->({ data => ["Generating ssh private key for root"] }); my $rc = system('ssh-keygen -t rsa -q -b 2048 -N "" -f /root/.ssh/id_rsa'); if ($rc) { $callback->({ error => ["Failure executing ssh-keygen for root"], errorcode => [1] }); } } } my $tempdir = tempdir("mknb.$$.XXXXXX", TMPDIR => 1); unless ($tempdir) { $callback->({ error => ["Failed to create a temporary directory"], errorcode => [1] }); return; } unless (-e "$tftpdir/xcat") { mkpath("$tftpdir/xcat"); } my $rc; my $invisibletouch = 0; if (-e "$::XCATROOT/share/xcat/netboot/genesis/$arch") { $rc = system("cp -a $::XCATROOT/share/xcat/netboot/genesis/$arch/fs/* $tempdir"); $rc = system("cp -a $::XCATROOT/share/xcat/netboot/genesis/$arch/kernel $tftpdir/xcat/genesis.kernel.$arch"); $invisibletouch = 1; } else { $rc = system("cp -a $::XCATROOT/share/xcat/netboot/$arch/nbroot/* $tempdir"); } if ($rc) { system("rm -rf $tempdir"); if ($invisibletouch) { $callback->({ error => ["Failed to copy $::XCATROOT/share/xcat/netboot/genesis/$arch/fs contents"], errorcode => [1] }); } else { $callback->({ error => ["Failed to copy $::XCATROOT/share/xcat/netboot/$arch/nbroot/ contents"], errorcode => [1] }); } return; } my $sshdir; if ($invisibletouch) { $sshdir = "/.ssh"; } else { $sshdir = "/root/.ssh"; } mkpath($tempdir . "$sshdir"); chmod(0700, $tempdir . "$sshdir"); copy("/root/.ssh/id_rsa.pub", "$tempdir$sshdir/authorized_keys"); chmod(0600, "$tempdir$sshdir/authorized_keys"); if (not $invisibletouch and -r "/etc/xcat/hostkeys/ssh_host_rsa_key") { copy("/etc/xcat/hostkeys/ssh_host_rsa_key", "$tempdir/etc/ssh_host_rsa_key"); copy("/etc/xcat/hostkeys/ssh_host_dsa_key", "$tempdir/etc/ssh_host_dsa_key"); chmod(0600, <$tempdir/etc/ssh_*>); } unless ($invisibletouch or -r "$tempdir/etc/ssh_host_rsa_key") { system("ssh-keygen -t rsa -f $tempdir/etc/ssh_host_rsa_key -C '' -N ''"); system("ssh-keygen -t dsa -f $tempdir/etc/ssh_host_dsa_key -C '' -N ''"); } my $lzma_exit_value = 1; if ($invisibletouch) { my $done = 0; if (-x "/usr/bin/lzma") { #let's reclaim some of that size... $callback->({ data => ["Creating genesis.fs.$arch.lzma in $tftpdir/xcat"] }); system("cd $tempdir; find . | cpio -o -H newc | lzma -C crc32 -9 > $tftpdir/xcat/genesis.fs.$arch.lzma"); $lzma_exit_value = $? >> 8; if ($lzma_exit_value) { $callback->({ data => ["Creating genesis.fs.$arch.lzma in $tftpdir/xcat failed, falling back to gzip"] }); unlink("$tftpdir/xcat/genesis.fs.$arch.lzma"); } else { $done = 1; $initrd_file = "$tftpdir/xcat/genesis.fs.$arch.lzma"; } } if (not $done) { $callback->({ data => ["Creating genesis.fs.$arch.gz in $tftpdir/xcat"] }); system("cd $tempdir; find . | cpio -o -H newc | gzip -9 > $tftpdir/xcat/genesis.fs.$arch.gz"); $initrd_file = "$tftpdir/xcat/genesis.fs.$arch.gz"; } } else { $callback->({ data => ["Creating nbfs.$arch.gz in $tftpdir/xcat"] }); system("cd $tempdir; find . | cpio -o -H newc | gzip -9 > $tftpdir/xcat/nbfs.$arch.gz"); $initrd_file = "$tftpdir/xcat/nbfs.$arch.gz"; } system("rm -rf $tempdir"); unless ($initrd_file) { $callback->({ data => ["Creating filesystem file in $tftpdir/xcat failed"] }); return; } CREAT_CONF_FILE: if ($configfileonly) { unless (-e "$tftpdir/xcat/genesis.kernel.$arch") { $callback->({ error => ["No kernel file found in $tftpdir/xcat, pls run \"mknb $arch\" instead."], errorcode => [1] }); return; } if (-e "$tftpdir/xcat/genesis.fs.$arch.lzma") { $initrd_file = "$tftpdir/xcat/genesis.fs.$arch.lzma"; } elsif (-e "$tftpdir/xcat/genesis.fs.$arch.gz") { $initrd_file = "$tftpdir/xcat/genesis.fs.$arch.gz"; } elsif (-e "$tftpdir/xcat/nbfs.$arch.gz") { $initrd_file = "$tftpdir/xcat/nbfs.$arch.gz"; } else { $callback->({ error => ["No filesystem file found in $tftpdir/xcat, pls run \"mknb $arch\" instead."], errorcode => [1] }); return; } } my $hexnets = xCAT::NetworkUtils->my_hexnets(); my $normnets = xCAT::NetworkUtils->my_nets(); my $consolecmdline; if (defined($serialport) and $serialspeed) { if ($arch =~ /ppc/) { $consolecmdline = "console=tty0 console=hvc$serialport,$serialspeed"; } else { $consolecmdline = "console=tty0 console=ttyS$serialport,$serialspeed"; } if ($serialflow =~ /cts/ or $serialflow =~ /hard/) { $consolecmdline .= "n8r"; } } my $cfgfile; if ($arch =~ /x86/) { mkpath("$tftpdir/xcat/xnba/nets"); chmod(0755, "$tftpdir/xcat/xnba"); chmod(0755, "$tftpdir/xcat/xnba/nets"); mkpath("$tftpdir/pxelinux.cfg"); chmod(0755, "$tftpdir/pxelinux.cfg"); if (-r "/usr/lib/syslinux/pxelinux.0") { copy("/usr/lib/syslinux/pxelinux.0", "$tftpdir/pxelinux.0"); } elsif (-r "/usr/share/syslinux/pxelinux.0") { copy("/usr/share/syslinux/pxelinux.0", "$tftpdir/pxelinux.0"); } if (-r "$tftpdir/pxelinux.0") { chmod(0644, "$tftpdir/pxelinux.0"); } } elsif ($arch =~ /ppc/) { mkpath("$tftpdir/pxelinux.cfg/p/"); } my $dopxe = 0; foreach (keys %{$normnets}) { my $net = $_; my $nicip = $normnets->{$net}; $net =~ s/\//_/; if (defined($nobootnicips{$nicip})) { if ($arch =~ /ppc/ and -r "$tftpdir/pxelinux.cfg/p/$net") { unlink("$tftpdir/pxelinux.cfg/p/$net"); } next; } $dopxe = 0; if ($arch =~ /x86/) { #only do pxe if just x86 or x86_64 and no x86 if ($arch =~ /x86_64/ and not $invisibletouch) { if (-r "$tftpdir/xcat/xnba/nets/$net") { my $cfg; my @contents; open($cfg, "<", "$tftpdir/xcat/xnba/nets/$net"); @contents = <$cfg>; close($cfg); if (grep (/x86_64/, @contents)) { $dopxe = 1; } } else { $dopxe = 1; } } else { $dopxe = 1; } } if ($dopxe) { my $cfg; open($cfg, ">", "$tftpdir/xcat/xnba/nets/$net"); print $cfg "#!gpxe\n"; if ($invisibletouch) { print $cfg 'imgfetch -n kernel http://${next-server}/tftpboot/xcat/genesis.kernel.' . "$arch quiet xcatd=" . $normnets->{$_} . ":$xcatdport $consolecmdline BOOTIF=01-" . '${netX/machyp}' . "\n"; if ($lzma_exit_value) { print $cfg 'imgfetch -n nbfs http://${next-server}/tftpboot/xcat/genesis.fs.' . "$arch.gz\n"; } else { print $cfg 'imgfetch -n nbfs http://${next-server}/tftpboot/xcat/genesis.fs.' . "$arch.lzma\n"; } } else { print $cfg 'imgfetch -n kernel http://${next-server}/tftpboot/xcat/nbk.' . "$arch quiet xcatd=" . $normnets->{$_} . ":$xcatdport $consolecmdline\n"; print $cfg 'imgfetch -n nbfs http://${next-server}/tftpboot/xcat/nbfs.' . "$arch.gz\n"; } print $cfg "imgload kernel\n"; print $cfg "imgexec kernel\n"; close($cfg); if ($invisibletouch and $arch =~ /x86_64/) { #UEFI time open($cfg, ">", "$tftpdir/xcat/xnba/nets/$net.elilo"); print $cfg "default=\"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; print $cfg " delay=5\n"; print $cfg ' image=/tftpboot/xcat/genesis.kernel.' . "$arch\n"; print $cfg " label=\"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; if ($lzma_exit_value) { print $cfg " initrd=/tftpboot/xcat/genesis.fs.$arch.gz\n"; } else { print $cfg " initrd=/tftpboot/xcat/genesis.fs.$arch.lzma\n"; } print $cfg " append=\"quiet xcatd=" . $normnets->{$_} . ":$xcatdport destiny=discover $consolecmdline BOOTIF=%B\"\n"; close($cfg); open($cfg, ">", "$tftpdir/xcat/xnba/nets/$net.uefi"); print $cfg "#!gpxe\n"; print $cfg 'chain http://${next-server}/tftpboot/xcat/elilo-x64.efi -C /tftpboot/xcat/xnba/nets/' . "$net.elilo\n"; close($cfg); } } elsif ($arch =~ /ppc/) { open($cfgfile, ">", "$tftpdir/pxelinux.cfg/p/$net"); print $cfgfile "default \"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; print $cfgfile " delay=10\n"; print $cfgfile " label \"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; print $cfgfile " kernel http://" . $normnets->{$_} . ":80/$tftpdir/xcat/genesis.kernel.$arch\n"; print $cfgfile " initrd http://" . $normnets->{$_} . ":80/$initrd_file\n"; print $cfgfile ' append "quiet xcatd=' . $normnets->{$_} . ":$xcatdport $consolecmdline\"\n"; close($cfgfile); } } $dopxe = 0; foreach (keys %{$hexnets}) { $dopxe = 0; if ($arch =~ /x86/) { #only do pxe if just x86 or x86_64 and no x86 if ($arch =~ /x86_64/) { if (-r "$tftpdir/pxelinux.cfg/" . uc($_)) { my $pcfg; open($pcfg, "<", "$tftpdir/pxelinux.cfg/" . uc($_)); my @pcfgcontents = <$pcfg>; close($pcfg); if (grep (/x86_64/, @pcfgcontents)) { $dopxe = 1; } } else { $dopxe = 1; } } else { $dopxe = 1; } } if ($dopxe) { open($cfgfile, ">", "$tftpdir/pxelinux.cfg/" . uc($_)); print $cfgfile "DEFAULT xCAT\n"; print $cfgfile " LABEL xCAT\n"; print $cfgfile " KERNEL xcat/nbk.$arch\n"; print $cfgfile " APPEND initrd=xcat/nbfs.$arch.gz quiet xcatd=" . $hexnets->{$_} . ":$xcatdport $consolecmdline\n"; close($cfgfile); } elsif ($arch =~ /ppc/) { open($cfgfile, ">", "$tftpdir/etc/" . lc($_)); print $cfgfile "default \"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; print $cfgfile " delay=10\n"; print $cfgfile " label \"xCAT Genesis (" . $normnets->{$_} . ")\"\n"; print $cfgfile " kernel http://" . $hexnets->{$_} . ":80/$tftpdir/xcat/genesis.kernel.$arch\n"; print $cfgfile " initrd http://" . $hexnets->{$_} . ":80/$initrd_file\n"; print $cfgfile ' append "quiet xcatd=' . $hexnets->{$_} . ":$xcatdport $consolecmdline\"\n"; close($cfgfile); } } if ($configfileonly) { $callback->({ data => ["Write netboot config file done"] }); } } 1;