Hi all,

Ferrol has been working on the extension of SystemImager for a diskless 
support. He did a pretty good job, the implementation being not intrusive.

Please find a description of the modification, a patch for si_prepareclient and 
a new Perl module for SystemImager that actually implements the diskless 
support. It does not aim at being perfect but it is a small extension that 
enables a basic diskless support, and we think the community may be interested 
by such a capability.

Is it something that could be included directly into SystemImager? 

Of course, if you have any questions or remarks, feel free to contact us.

Thanks,

Description of changes made:
----------------------------

1. The user needs only modify a configuration file. This file is 
   /etc/systemimager/diskless.conf.

   <ip> <path_to_kernel> <method_of_boot>

   The <method_of_boot> section can define three methods:
   nfs-root, ramfs-rsync, ramfs-http. 

2. /usr/sbin/si_prepareclient was modified in order to include a flag that can
   be used to signify whether or not modifications should be done. 
   The proper call is:

   /usr/sbin/si_prepareclient --diskless

   It looks to see if that flag is there and then calls a function named 
   diskless(). This function makes use of a perl module, 
   /usr/lib/systemimager/perl/SystemImager/Diskless.pm, that contains
   most of the code that was used in si_mkdiskless.
   After the function, diskless(), returns si_prepareclient exits. This
   can be changed, it was just seen as a possible annoyance to have some
   parts of si_prepareclient to run if the user only wanted the diskless
   portion to run.
--- /usr/sbin/si_prepareclient	2007-12-06 06:18:34.000000000 -0500
+++ ./si_prepareclient	2008-04-22 11:19:07.000000000 -0400
@@ -6,6 +6,10 @@
 #   Copyright (C) 2002 Internation Business Machines
 #                      Sean Dague <[EMAIL PROTECTED]>
 #
+#   Copyright (c) Oak Ridge National Labratory
+#               Ferrol Aderholdt <[EMAIL PROTECTED]>
+#               All rights reserved
+#
 #   $Id: si_prepareclient 4336 2007-11-12 15:01:04Z arighi $
 #    vi:set filetype=perl: 
 #
@@ -83,6 +87,7 @@
 use vars qw($VERSION);
 use SystemImager::Common;
 use SystemImager::UseYourOwnKernel;
+use SystemImager::Diskless;
 
 # set version
 $VERSION = "4.0.2";
@@ -220,6 +225,11 @@
     /usr/share/doc/systemimager-server-4.0.2/local.cfg
     for a well commented example.
 
+ --diskless
+    Indicates that diskless nodes are present in 
+    /etc/systemimager/diskless.conf file. This will then do what is needed
+    for those nodes to boot.
+
 Download, report bugs, and make suggestions at:
 http://systemimager.org/
 
@@ -241,6 +251,7 @@
     "image=s"              => \my $image,
     "filesystem=s"         => \my $filesystem,
     "version"              => \my $version,
+    "diskless"             => \my $diskless,
     "server=s"             => \my $server,
     "yes"                  => \my $yes,
     "ssh-key=s"            => \my $ssh_key,
@@ -268,6 +279,11 @@
     exit 0;
 }
 
+if($diskless) {
+    &diskless();
+    exit 0;
+}
+
 # bail if not root
 if ($> != 0) {
     print "Must be run as root!\n";
@@ -1253,6 +1269,50 @@
         return @ips;
 }
 
+##############################################################################
+#
+# Description:
+# Does what is necessary so that any diskless nodes will boot properly.
+#
+# Added in on April 15, 2008 by Ferrol Aderholdt
+#
+# usage:
+# diskless();
+sub diskless {
+        my $diskless_file = "/etc/systemimager/diskless.conf";
+        my ($is_nfs, $is_http, $is_rsync) = SystemImager::Diskless::computenode_client_info($diskless_file);
+        SystemImager::Diskless::fix_pxe();
+
+        if($is_nfs) {
+                print "Starting NFS-Root modifications...\n";
+                my $distro = "fedora" if -e "/etc/fedora-release";
+                $distro = "debian" if -e "/etc/debian_version";
+                SystemImager::Diskless::fix_dhcpd();
+                SystemImager::Diskless::fix_exports();
+                SystemImager::Diskless::move_kernels();
+                SystemImager::Diskless::create_skeleton_directory_structure();
+                SystemImager::Diskless::fix_initrc($distro);
+                SystemImager::Diskless::fix_network($distro);
+                SystemImager::Diskless::modify_node_fstab();
+                SystemImager::Diskless::setup_node_swap();
+                SystemImager::Diskless::si_report();
+                print "Finished NFS-Root modifications...\n";
+        }
+        if($is_rsync) {
+                print "Starting ramfs-rsync modifications...\n";
+                SystemImager::Diskless::modify_rsyncd();
+                SystemImager::Diskless::create_initrd();
+                print "Finished ramfs-rsync modifications...\n";
+        }
+        if($is_http) {
+                print "Starting ramfs-http modifications...\n";
+                SystemImager::Diskless::create_tarballs();
+                SystemImager::Diskless::create_initrd();
+                print "Finished ramfs-http modifications...\n";
+        }
+        
+        print "Diskless modifications complete.\n";
+}
 
 ### END functions
 
package SystemImager::Diskless;

#
#	Copyright (c) Oak Ridge National Laboratory
#			  Ferrol Aderholdt <[EMAIL PROTECTED]>
#			  All rights reserved
#
#

use lib "/usr/lib/systemimager/perl","/usr/lib/systeminstaller";
use Carp;
use Cwd;
use File::Copy;
use File::Path;
use File::stat;
use SIS::DB;
use SIS::Client;
use SIS::Adapter;
use SIS::Image;
use SystemImager::Config;
use SystemImager::Server;
use strict;

our $config = $SystemImager::Config::config;

############################################################
#
#	Subroutines in this module include:
#
#	computenode_client_info
#	create_skeleton_directory_structure
#	find_ip
#	find_image_path
#	fix_dhcpd
#	fix_exports
#	fix_initrc
#	fix_network
#	fix_pxe
#	ip2subnet
#	ip2hex 
#	modify_node_fstab
#	move_kernels
#	setup_node_swap
#	si_report
#	create_tarballs
#	create_initrd
#	generate_linuxrc
#	modify_rsyncd
#
###########################################################

my @computenode_ips;
my @ramfs_http_ips;
my @ramfs_rsync_ips;
my %ip_to_kernel = ();
my %ip_to_initrd = ();
my %ip_to_image = ();
my %ip_to_host = ();
my %paths = ();
my @images_used;

###########################
# computenode_client_info
#
# reads a diskless config file that is passed in to discover
# all diskless nodes and fills in the proper hashes 
# and arrays to reflect this. returns a tuple of values
# that are either 0, for false, or 1, for true, to 
# indicate if there are any nodes with a diskless
# boot using NFS-Root, a ramfs diskless boot using tarballs
# and httpd server, or ramfs diskless boot using rsync.
#
# Usage:
# ($nfs, $ramfs_http, $ramfs_rsync) = computenode_client_info($filename) 
###########################
sub computenode_client_info {
	my $file = shift;
	my @computenodes;
	my @ramfs_computenodes;
	my %ip_to_type = ();
	my @hosts = &list_client();
	my @computenode_temp = &list_adapter();
	my ($is_nfs, $is_http, $is_rsync) = (0, 0, 0);

	my $line_count = 1;
	
	open(FILE, "<$file") or croak("could not open $file for reading $!");
	while(<FILE>) {
		chomp($_);
		my $temp_ip = "";
		my $temp_kernel = "";
		my $temp_type = "";

		#remove comments
		$_ =~ s/(.*)\s*#.*/$1/;
		unless($_ =~ /.+/) {
			next;
		}
	
		#made this section more strict to the user
		#so that there aren't major problems later 
		if( /(.*) (.*) (.*)/ ) {
			$temp_ip = $1;
			$temp_kernel = $2;
			$temp_type = $3;
		#} elsif( /(.*) (.*)/ ) {
		#	$temp_ip = $1;
		#	$temp_kernel = $2;
		} else {
			croak("Config file $file is not written correctly");
		}

		#do some error checking
		if($temp_ip =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) {
			push(@computenodes, $temp_ip);
		} else {
			croak("Invalid IP used on line $line_count of $file");
		}

		#no longer guessing kernel since that is not a good
		#fix for user laziness

		#map ips for computenodes to kernels
		#also map ips for computenodes to initrds if needed
		$ip_to_kernel{$temp_ip} = $temp_kernel;
		$ip_to_type{$temp_ip} = $temp_type;
		$line_count++;
	}
	
	close(FILE);

	#no work needs to be done, leave...
	unless(@computenodes) {
		print "No work to be done. Leaving...\n";
		exit 0;
	}

	#more error checking
	foreach my $ips (@computenodes) {
		foreach my $temp_ip (@computenode_temp) { 
			if($temp_ip->ip =~ /$ips/) {
				#this is loosely checked...
				if($ip_to_type{$ips} =~ /ramfs-rsync/) {
					$is_rsync = 1;
					push(@ramfs_rsync_ips, $ips);
				} elsif($ip_to_type{$ips} =~ /ramfs-http/) {
					$is_http = 1;
					push(@ramfs_http_ips, $ips);
				} else {
					$is_nfs = 1;
					push(@computenode_ips, $ips);
				}
#				if($ip_to_kernel{$ips} =~ /ramfs/) {
#					if($ip_to_kernel{$ips} =~ /http/) {
#						$is_http = 1;
#						push(@ramfs_http_ips, $ips);
#					} else {
#						$is_rsync = 1;
#						push(@ramfs_rsync_ips, $ips);
#					}
#				} else {
#					$is_nfs = 1;
#					push(@computenode_ips, $ips);
#				}
				
				foreach my $temp_hosts (@hosts) {
					if($temp_hosts->hostname =~ $temp_ip->client) {
						my $hostname = $temp_hosts->hostname;
						
						#strip out domain information
						$hostname =~ s/(.*)\..*\..*/$1/; 
						
						$ip_to_image{$ips} = $temp_hosts->imagename;
						$ip_to_host{$ips} = $hostname;
						push(@images_used, $temp_hosts->imagename);
					}
				}
			}
		}
	}
	return ($is_nfs, $is_http, $is_rsync);
}

###########################################################
# create_skeleton_directory_structure
#
# creates a directory structure for the nodes using 
# NFS-Root. This requires that computenode_client_info 
# be called before this or else it will not work properly.
#
# Usage:
# create_skeleton_directory_structure();
###########################################################

sub create_skeleton_directory_structure {
	my @shared_directories = qw/media mnt scratch selinux srv usr/;
	my @unshared_directories = qw/bin dev etc home lib opt proc root sbin sys var/;
	my $tftpdir = "/tftpboot";
	
	foreach my $image (@images_used) {
		my $imagedir = &find_image_path($image);
		
		#example: create the /tftpboot/192.168.0.2 
		foreach my $ips (@computenode_ips) {
			unless(-d "$tftpdir/$ips") {
				mkpath("$tftpdir/$ips");
			}
			
			foreach my $dir (@shared_directories, @unshared_directories) {
				unless(-d "$tftpdir/$ips/$dir") {
					mkpath("$tftpdir/$ips/$dir");
				}
			}

			#/lib won't mount correctly through NFS-Root.
			#copy over necessary files then mount it with nfs
			foreach my $dir (@unshared_directories) {
				if($dir =~ /lib/) {
					system("cp -u $imagedir/$dir/* $tftpdir/$ips/$dir 2>/dev/null");
				} else {
					!system("cp -ur --remove-destination $imagedir/$dir $tftpdir/$ips")
					or croak("Could not copy $imagedir/$dir to $tftpdir/$ips");
				}
			}
		}
	}
}


###########################################################
# find_ip
#
# find_ip opens /etc/hosts and looks for the ip containing
# oscar_server. it then returns both the headnode ip and 
# netmask. 
#
# Usage:
# ($ip, $netmask) = find_ip();
# my ($ip, $netmask) = find_ip();
###########################################################

sub find_ip {
	my ($headnode_ip, $headnode_netmask) = ("","");
	my $test_ip;

	#look for line in /etc/hosts containing oscar_server
	open(IN, "</etc/hosts");
	while(<IN>) {
		if(/^(\d+.\d+.\d+.\d+).*oscar_server.*/) {
			$test_ip = $1;
			last; 
		}
	}
	close(IN);

	#checking for use
	open(IN, "/sbin/ifconfig |") or croak("Couldn't run /sbin/ifconfig");
	while(<IN>) {
		if(/^.*:(\d+.\d+.\d+.\d+).*:.*:(\d+.\d+.\d+.\d+)\s*$/) {
			($headnode_ip, $headnode_netmask) = ($1, $2);
			if($test_ip =~ m/$headnode_ip/) {
				last;
			}
		}
	}
	close(IN);

	#just one last error check
	unless($test_ip =~ /$headnode_ip/) {
		croak("$test_ip is not actually in use. Fix this and run again.");
	}

	return ($headnode_ip, $headnode_netmask);
}

###########################################################
# find_image_path
#
# this finds the path to the image passed in 
# and then returns it.
#
# Usage:
# my $path = find_image_path($image_name);
###########################################################
sub find_image_path {
	my $image = shift;
	my @list_of_images = &list_image();
	my $path;

	#find matching path
	foreach my $test_image (@list_of_images) {
		if($test_image->name =~ /$image/) {
			$path = $test_image->location;
			last;
		}
	}
	return $path;
}

###########################################################
# fix_dhcpd
#
# this looks for and adds in, if not already there, the
# line "authoritative" to the dhpcd.conf file wherever it 
# may be. this is so computenodes using NFS-Root will boot
# properly.
#
# Usage:
# fix_dhcpd();
###########################################################
sub fix_dhcpd {
	my ($ip, $netmask) = &find_ip();
	my $dhcpd = "/etc/dhcpd.conf"; #for fedora
	$dhcpd = "/etc/dhcp3/dhcpd.conf" if -d "/etc/dhcp3"; #for debian
	my $authoritative_line = "authoritative";
	my $cmd = "/etc/init.d/dhcpd restart";
	
	#make sure not authoritative is not in the dhcpd.conf
	if(system("grep -qG \"^[a-z]*[[:space:]]$authoritative_line;\" $dhcpd")) {
		copy($dhcpd, "$dhcpd.bak");
		
		open(IN, "<$dhcpd.bak") or croak("Couldnt open $dhcpd.bak for $!");
		open(OUT, ">$dhcpd") or croak("Couldnt open $dhcpd for $!"); 
		
		my $flag = 0;
		my $subnet = &ip2subnet($ip, $netmask);
		
		while(<IN>) {
			if(/$subnet/) {
				$flag = 1;
			}
			
			print OUT "$_";
			
			if($flag) {
				print OUT "\t$authoritative_line; #added in for nfs-root\n";
				$flag = 0;
			}
		}
		close(OUT);
		close(IN);
	}

	#restart dhcp server to make 
	if($dhcpd =~ /.*3.*/) {
		$cmd = "/etc/init.d/dhcp3-server restart";
	}
	
	!system($cmd) or croak("Couldn't restart dhcpd server");
}

###########################################################
# fix_exports
#
# this guarantees that the NFS-Root computenodes will be
# able to mount. this requires that computenode_client_info
# be called before it is called. 
#
# Usage:
# fix_exports();
###########################################################

sub fix_exports {
	my $exports = "/etc/exports";
	my ($ip, $netmask) = &find_ip();
	my $tftpdir = "/tftpboot";

	my $nfs = "nfs"; #supports fedora/redhat
	$nfs = "nfsserver" if -x "/etc/init.d/nfsserver"; #suse
	$nfs = "nfs-kernel-server" if -x "/etc/init.d/nfs-kernel-server"; #debian
	$nfs = "nfs-user-server" if -x "/etc/init.d/nfs-user-server"; #also debian
	
	#if the line isn't there, add it
	if(system("grep -q /lib $exports")) {
		system("echo \"/lib $ip/$netmask(async,ro,no_root_squash)\" >> /etc/exports");
	}
	
	#check for lines for /usr /srv and /selinux
	foreach my $image (@images_used) {
		my $imagedir = &find_image_path($image);
		
		if(system("grep -q $imagedir/usr $exports")) {
			open (OUT, ">>$exports");
			print OUT "$imagedir/usr $ip/$netmask(async,ro,no_root_squash)\n";
			close (OUT);
		}

		if(system("grep -q $imagedir/srv $exports")) {
			open (OUT, ">>$exports");
			print OUT "$imagedir/srv $ip/$netmask(async,ro,no_root_squash)\n";
			close (OUT);
		}

		if(system("grep -q $imagedir/selinux $exports")) {
			open (OUT, ">>$exports");
			print OUT "$imagedir/selinux $ip/$netmask(async,ro,no_root_squash)\n";
			close (OUT);
		}
	}

	foreach my $ips (@computenode_ips) {
		my $flag = 0;
		
		open (IN, "<$exports");
		open (OUT, ">>$exports");
		
		while(<IN>) {
			if( m.$tftpdir/$ips .) {
				$flag = 1;
				last;
			}
		}

		#need to share $tftpdir/$ips for computenodes to
		#even boot.
		unless($flag) {
			print OUT "$tftpdir/$ips $ip/$netmask(async,rw,no_root_squash)\n";
		}
		close (OUT);
		close (IN);
	}

	!system("/etc/init.d/$nfs restart") or croak("Couldn't restart $nfs");
}

###########################################################
# fix_initrc
# 
# fixes the boot of the NFS-Root computenodes so that 
# they will boot properly. this requires 
# computenode_client_info be called before hand.
#
# Usage:
# fix_initrc($distro_name); # either fedora or debian
###########################################################

sub fix_initrc {
	my $distro = shift;
	my $tftpdir = "/tftpboot";
	my ($ip, $netmask) = &find_ip();

	if($distro =~ /fedora/) {
		
		foreach my $ips (@computenode_ips) {
			my $file = "$tftpdir/$ips/etc/rc.sysinit";
			my $backup_file = $file.".bak";
			
			#fixed?
			if(system("grep -q \"/var/lib/systemimager/\" $tftpdir/$ips/etc/rc.sysinit")) {
				!system("cp $file $backup_file")
				or croak("Couldn't copy $file to $backup_file");
				
				open(IN, "<$backup_file") or croak("Couldn't open $backup_file");
				open(OUT, ">$file") or croak("Couldn't open $file");
				my $flag = 0; #tells when to place new lines in file
			
				while(<IN>) {
					if($flag) {
						my $image = $ip_to_image{$ips};
						
						print OUT "\n\trpc.lockd\n\tportmap\n";
						print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/usr /usr\n";
						print OUT "\tmount -n -t nfs $ip:/lib /lib\n";
						print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/selinux /selinux\n";
						print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/srv /srv\n";
						print OUT "\tmount -n -t tmpfs tmpfs /tmp -o size=4G\n";
						$flag = 0;
					} elsif(/mount.*sysfs/) {
						$flag = 1;
					}
					
					print OUT "$_";
				}
				close (IN);
				close (OUT);
			}
		}
	} else { #only also have debian.... should also support suse later
		#need to modify rc.local, then mv Swhatever rc.local in rc2.d to S05

		foreach my $ips (@computenode_ips) {
			my $file = "$tftpdir/$ips/etc/rc.local";
			my $backup_file = $file.".bak";
			
			copy($file, $backup_file);
	
			open(OUT, ">$file");
			my $image = $ip_to_image{$ips};
		
			print OUT "\n\trpc.lockd\n\tportmap\n";
			print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/usr /usr\n";
			print OUT "\tmount -n -t nfs $ip:/lib /lib\n";
			print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/selinux /selinux\n";
			print OUT "\tmount -n -t nfs $ip:/var/lib/systemimager/images/$image/srv /srv\n";
			print OUT "\tmount -n -t tmpfs tmpfs /tmp -o size=4G\n";
			print OUT "exit 0\n";
			close (OUT);

			open(IN, "ls $tftpdir/$ips/etc/rc2.d/ |")
			or croak("Couldn't run: ls $tftpdir/$ips/etc/rc2.d/");

			while(<IN>) {
				if(/rc\.local/) {
					!system("mv $_ S05rc.local")
					or croak("Couldn't rename $_");
				}
			}
			close(IN);
		}
	}
}

###########################################################
# fix_network
#
# during boot some services will not start unless the
# file /etc/sysconfig/network is present for fedora 
# machines. the distro is passed in and actions are taken 
# for a proper boot.
#
# Usage:
# fix_network($distro_name); #either fedora or debian
###########################################################
sub fix_network {
	my $distro = shift;
	my $tftpdir = "/tftpboot";

	foreach my $ips (@computenode_ips) {
		my $hostname = $ip_to_host{$ips};
		
		if($distro =~ /fedora/) {
			open(IN, "</etc/sysconfig/network") 
			or croak("Couldn't open /etc/sysconfig/network");
			open(OUT, ">$tftpdir/$ips/etc/sysconfig/network")
			or croak("Couldn't open $tftpdir/$ips/etc/sysconfig/network for writing");
	
			while(<IN>) {
				if(/HOSTNAME/) {
					#make sure compute nodes boot correctly
					print OUT "HOSTNAME=$hostname\n";
				} else {
					print OUT "$_";
				}
			}
		} else { 
			#something tells me this won't work...
			open(OUT, ">$tftpdir/$ips/etc/hostname");
		}

		close(OUT);
		close(IN);
	}
}

###########################################################
# fix_pxe
#
# fix_pxe writes pxe boot files so that diskless
# nodes will not use the systemimager generated
# default. 
#
# Usage:
# fix_pxe();
###########################################################
sub fix_pxe {
	my ($ip,$netmask) = &find_ip();
	my $tftpdir = "/tftpboot";

	my $pxe =<<"EOF";
DEFAULT systemimager

#
# Uncomment next line to send pxelinux boot prompt over serial port 0.
# NOTE: Be sure your serial port speed is appropriate (57600, 9600, etc.)
#
# SERIAL 0 57600
DISPLAY message.txt
PROMPT 1
TIMEOUT 50

# Add the following to the append line above to use your first serial port
# (ttyS0) as a console in addition to your monitor (tty0).  NOTE: Be sure
# your serial port speed is appropriate (57600, 9600, etc.)
#
# console=ttyS0,57600

# Add the following to the append line above to increase the size of your tmpfs
# filesystem.  About 100MB larger than your image size should suffice.
#
#
# tmpfs_size=800M
# ramdisk_size=80000
#
# Other tmpfs mount options are also supported.
#
# See http://wiki.systemimager.org/index.php/Troubleshooting for details.
#

EOF


	#for compute nodes using NFS-Root
	foreach my $ips (@computenode_ips) {
		my $append = "APPEND vga=normal root=/dev/nfs rw ";
		$append .= "nfsroot=$ip:$tftpdir/$ips/ ip=dhcp\n\n";
		my $temp_kernel = $ip_to_kernel{$ips};
		
		#remove the path and keep only the name for the kernel
#		unless($temp_kernel =~ m/ramfs/) {
		$temp_kernel =~ s,.*/(.*)$,$1,;
#		}
		
		open(OUT, ">$tftpdir/pxelinux.cfg/".ip2hex($ips))
		or croak("Couldn't open pxe file for writing");

		print OUT "$pxe";
		print OUT "LABEL systemimager\n";
		print OUT "KERNEL $temp_kernel\n";
		print OUT "$append";
		close(OUT);
	}

	#for compute nodes using ramfs via a tarball
	foreach my $ips (@ramfs_http_ips) {
		my $append = "APPEND vga=extended initrd=ramfs-http-initrd.img";
		$append .= " root=/dev/ram MONITOR_SERVER=$ip MONITOR_CONSOLE=yes";
	
		my $temp_kernel = $ip_to_kernel{$ips};

		open(OUT, ">$tftpdir/pxelinux.cfg/".ip2hex($ips))
		or croak("Couldnt open pxe file for writing");
		
		$temp_kernel =~ s,.*/(.*)$,$1,;

		print OUT "$pxe";
		print OUT "LABEL systemimager\n";
		print OUT "KERNEL $temp_kernel\n";
		print OUT "$append";
		close(OUT);
	}

	foreach my $ips (@ramfs_rsync_ips) {
		my $append = "APPEND vga=extended initrd=ramfs-rsync-initrd.img ";
		$append .= "root=/dev/ram MONITOR_SERVER=$ip MONITOR_CONSOLE=yes";
		
		my $temp_kernel = $ip_to_kernel{$ips};
		$temp_kernel =~ s,.*/(.*)$,$1,;

		print "$tftpdir/pxelinux.cfg/".ip2hex($ips);

		open(OUT, ">$tftpdir/pxelinux.cfg/".ip2hex($ips))
		or croak("Couldnt open pxe file");
	
		print OUT "$pxe";
		print OUT "LABEL systemimager\n";
		print OUT "KERNEL $temp_kernel\n";
		print OUT "$append";
		close(OUT);
	}
}

###########################################################
# ip2subnet
#
# ip2subnet takes an ip and netmask and returns the
# subnet.
#
# Usage:
# my $subnet = ip2subnet($ip, $netmask);
###########################################################
sub ip2subnet {
	my $temp_ip = shift;
	my $temp_net = shift;
	my ($ip_1, $ip_2, $ip_3, $ip_4);
	my ($net_1, $net_2, $net_3, $net_4);
	my ($final_1, $final_2, $final_3, $final_4);
	
	$ip_1 = $ip_2 = $ip_3 = $ip_4 = $temp_ip;
	$net_1 = $net_2 = $net_3 = $net_4 = $temp_net;

	#find which octet goes with which variable
	$ip_1 =~ s/([0-9]*)\..*/$1/;
	$ip_2 =~ s/[0-9]*\.([0-9]*)\..*/$1/;
    $ip_3 =~ s/[0-9]*\.[0-9]*\.([0-9]*)\..*/$1/;
    $ip_4 =~ s/[0-9]*\.[0-9]*\.[0-9]*\.(.*)/$1/;

    $net_1 =~ s/([0-9]*)\..*/$1/;
    $net_2 =~ s/[0-9]*\.([0-9]*)\..*/$1/;
    $net_3 =~ s/[0-9]*\.[0-9]*\.([0-9]*)\..*/$1/;
    $net_4 =~ s/[0-9]*\.[0-9]*\.[0-9]*\.(.*)/$1/;

	#simple ANDs
	$final_1 = int($ip_1) & int($net_1);
    $final_2 = int($ip_2) & int($net_2);
    $final_3 = int($ip_3) & int($net_3);
    $final_4 = int($ip_4) & int($net_4);


    my $string_ip = sprintf("%d.%d.%d.%d",$final_1,$final_2,$final_3,$final_4);
    return $string_ip;
}

#########################
# taken from HostRange.pm verbatim
#########################
sub ip2hex {
	my @bytes = split(/\./, $_[0]);

	return 0 unless @bytes == 4 && ! grep {!(/\d+$/ && ($_ <= 255) && ($_ >= 0))} @bytes;

	return sprintf("%02X%02X%02X%02X", @bytes);
}

###########################################################
# modify_node_fstab
#
# modifies the computenode's fstab file since it is 
# normally written by systemconfigurator. requires
# that computenode_client_info be called prior.
#
# modify_node_fstab();
###########################################################
sub modify_node_fstab {
	my ($ip, $netmask) = &find_ip();
	my $tftpdir = "/tftpboot";
	my $fstab =<<"EOF";
proc	/proc	proc	defaults	0 0
sysfs	/sys	sysfs	defaults	0 0
devpts	/dev/pts	devpts	defaults	0 0
$ip:/home	/home	nfs	defaults	1 1
EOF

	foreach my $ips (@computenode_ips) {
		open(OUT, ">$tftpdir/$ips/etc/fstab")
		or croak("Couldn't write fstab for computenode");
		print OUT $fstab;
	}
	close(OUT);
}

#######################
# move_kernels
#
# simply copies the kernel from location presented
# in conf file to /tftpboot
#
# Usage:
# move_kernels();
#######################
sub move_kernels {
	my $tftpdir = "/tftpboot";
	foreach my $ips (@computenode_ips, @ramfs_http_ips, @ramfs_rsync_ips) {
		if($ip_to_kernel{$ips}) {
			copy($ip_to_kernel{$ips}, $tftpdir) or 
			croak("Could not find $ip_to_kernel{$ips}");
		} else {
			croak("Cannot find kernels for use with node $ips\n");
		}
	}
}

###########################################################
# setup_node_swap
#
# this looks for any hds with a swap partition and then
# mounts it.
#
# Usage:
# setup_node_swap();
###########################################################
sub setup_node_swap {
	my $tftpdir = "/tftpboot";
	my $swaps=<<"EOF";
#!/bin/bash
cdroms=\'cat /proc/sys/dev/cdrom/info 2>/dev/null | grep "drive name:" | sed -e "s/^drive name:[[:space:]]*//"\'

if [ ! -z \${cdroms} ]
then
hd=\'cat /proc/diskstats | grep -e ".*[h,s]d[a-z]" | sed -e "s/.*\\([h,s]d[a-z]\\).*/\\1/" | sort -u | grep -v \${cdroms}\'

fi

if [ -z \${hd} ]
then
	echo "no hard drives found... bailing out"
else
	for test in \${hd}
	do
	swap=\'fdisk -l /dev/\${test} | grep "Linux swap" | sed -e "s/\\/dev/\\/\${test}\\([0-9]*\\)[[:space:]].*/\\1/"\'
	swapon /dev/\${test}\${swap}
	done

	for test in \${hd}
	do
	num=\'fdisk -l /dev/\${test} | grep " 83 " | sed -e "s/\\/dev/\\/\${test}\\([0-9]*\\).*/\\1/"\'
	mkdir -p /mnt/scratch-\${test}\${num}
	mount /dev/\${test}\${num} /mnt/scratch-\${test}\${num}
	done
fi
EOF

	foreach my $ips (@computenode_ips) {
		open(OUT, ">$tftpdir/$ips/etc/diskless_swaps");
		print OUT $swaps;
		close(OUT);
		
		system("chmod 755 $tftpdir/$ips/etc/diskless_swaps");
		if(system("grep -q diskless_swaps $tftpdir/$ips/etc/rc.local")) {
			system("grep -v \"exit 0\" $tftpdir/$ips/etc/rc.local > $tftpdir/$ips/etc/rc.local.temp");
			system("cat $tftpdir/$ips/etc/rc.local.temp > $tftpdir/$ips/etc/rc.local");
			system("chmod 755 $tftpdir/$ips/etc/rc.local");
			
			open(LOCAL, ">>$tftpdir/$ips/etc/rc.local");
			print LOCAL "/etc/diskless_swaps\nexit 0\n";
			close(LOCAL);
		}
	}
}

###########################################################
# si_report
#
# writes a script that is taken straight from systemimager
# that will report to the si_monitor on the headnode.
#
# Usage:
# si_report();
###########################################################
sub si_report {
	my ($ip, $netmask) = &find_ip();
	my $tftpdir = "/tftpboot";
	my $report=<<"EOF";
#!/bin/bash
#
# Description: a modded out version of 95all.monitord_rebooted from si
#

TIMEOUT=30

write_monitor_msg() {
	MONITOR_SERVER=$ip
	MONITOR_PORT=8181

	msg=`echo "\$\@"`

	if [ -z "\$mac" ]; then
		mac=`ifconfig eth0 2>/dev/null | sed -ne "s/.*HWaddr //p" | sed "s/ //g" | sed s/:/./g`
	fi

	#not an easy way to determine, accurately, the imagename
	#just send the distro name for now
	if [ -e /etc/redhat-release ]; then
		distro='redhat/fedora'
	elif [ -e /etc/SuSE-release ]; then
		distro='SuSE'
	elif [ -e /etc/debian_version ]; then
		distro='debian'
	fi
	

	kernel=`uname -r` #determine kernel version
	hostname=`uname -n`
	ipaddr=`hostname --ip-address`

	total_mem=`awk '{ print \$2 }' <(free -k | grep Mem:)`
	used_mem=`awk '{ print \$3 }' <(free -k | grep Mem:)`

	send_msg=`echo "mac=\$mac:ip=\$ipaddr:host=\$hostname:kernel=\$kernel:mem=\$total_mem:tmpfs=\$used_mem:os=\$distro:\$msg"`

	netcat=`(which netcet || which nc) 2>/dev/null`

	echo "\$send_msg" | \$netcat -w \$TIMEOUT \$MONITOR_SERVER \$MONITOR_PORT
}

write_monitor_msg "status=102:speed=0"

EOF
	
	#give the compute nodes the report to send back
	foreach my $ips (@computenode_ips) {
		open(OUT,">$tftpdir/$ips/etc/report_to_monitor");
		print OUT $report;
		close (OUT);
		
		system("chmod 755 $tftpdir/$ips/etc/report_to_monitor");
		if(system("grep -q report_to_monitor $tftpdir/$ips/etc/rc.local")) {
			system("grep -v \"exit 0\" $tftpdir/$ips/etc/rc.local > $tftpdir/$ips/etc/rc.local.temp"); #soo help me if this breaks something
			system("cat $tftpdir/$ips/etc/rc.local.temp > $tftpdir/$ips/etc/rc.local");
			system("chmod 755 $tftpdir/$ips/etc/rc.local"); #insurance

			open(LOCAL, ">>$tftpdir/$ips/etc/rc.local"); #only for fedora right now...need to change this
			print LOCAL "/etc/report_to_monitor\nexit 0\n";
			close (LOCAL);
		}
	}
}

###########################################################
# create_tarballs
#
# if there are any nodes that are using the tarball/http
# method, this will create the tarballs and place them
# in the proper place.
#
# Usage:
# create_tarballs();
###########################################################
sub create_tarballs {
	for my $test (@ramfs_http_ips) {
		my $image = $ip_to_image{$test};
		my $path = &find_image_path($image);

		my $pwd = getcwd(); #come back to this
		chdir("$path/");

		my $tarball = "/var/www/tar.$image.bz2";
		$tarball = "/var/www/html/tar.$image.bz2" if -d "/var/www/html";
	
		my $symlink = "/var/www/tar.$test.bz2";
		$symlink = "/var/www/html/tar.$test.bz2" if -d "/var/www/html";

		unless( -e "$tarball") {
			if(system("tar cjvf $tarball ./*")) {
				croak("cannot create tarball tar.$image.bz2");
			}
		}

		unless( -e "$symlink") {
			system("ln -s $tarball $symlink");
		}
		print "ip: $test\n image: $image\n";
		chdir("$pwd");
	}
}

###########################################################
# create_initrd
#
# this creates the initrd for the diskless boot. Only 
# creates an initrd with cramfs.
#
# Usage:
# create_initrd();
###########################################################
sub create_initrd {
	my @ramfs_dirs = qw/bin dev lib new_root proc sbin usr var/;
	my @bin = qw/bash cat grep gzip mkdir mount sed tar umount/;
	my @sbin = qw/dhclient ifconfig dhclient-script/; 
	my @usr_bin = qw/bzip2 rsync wget/;
	my $ramdir = "/tmp/initrd";
		
	my @required;

	print "creating initrd for diskless boot.\n";
	#create a new initrd
	if(-d "$ramdir") {
		system("rm -rf $ramdir");
	}

	#make template
	if(system("mkdir -p $ramdir")) {
		croak("could not create $ramdir");
	}

	for my $dir (@ramfs_dirs) {
		if(system("mkdir -p $ramdir/$dir")) { 
			croak("could not create $ramdir/$dir");
		}
	}

	#find path of each file
	for my $stuff (@bin,@sbin,@usr_bin) {
		#just covering all the bases
		my $cmd = "/usr/bin/whereis" if -x "/usr/bin/whereis";
		$cmd = "/bin/whereis" if -x "/bin/whereis";
		$cmd = "/sbin/whereis" if -x "/sbin/whereis";

		open(IN,"$cmd $stuff |") or croak("could not open whereis");
		while(<IN>) {
			if(/$stuff: (.*?) .*/) {
				push(@required,$1);
				$paths{$stuff} = $1;
			}
		}
		close(IN);
	}

	for my $stuff (@required) {
		my $tempdir;
		if($stuff =~ m/(.*\/).*/) {
			$tempdir = $1;
			if(system("mkdir -p $ramdir/$tempdir")) {
				croak("could not create $ramdir/$tempdir");
			}
		}
		if(system("cp $stuff $ramdir/$tempdir")) {
			croak("could not copy $stuff to $ramdir/$tempdir");
		}
	}

	#just do ldd part first
	for my $stuff (@required) {		
		if(-B $stuff) {
			open(IN,"/usr/bin/ldd $stuff |") or croak("could not find /usr/bin/ldd");
			while(<IN>) {
				if(/(.*) => (.*\/lib\/.*) (.*)$/) {
					my $file = $2;
					my $cmd = "cp ";
					my $libdir;
					if($file =~ /(.*\/).*$/) {
						$libdir = $1;
					}
					if(system("mkdir -p $ramdir$libdir")) {
						croak("something messed up");
					}
					$cmd .= " $file $ramdir$libdir"; #need to change  
					if(system($cmd)) {
						croak("couldn't copy");
					}
				}
			}
		}
	}
	close(IN);

	#the above method of collecting libraries misses ld-linux.so.2 (add it in)
	if(system("cp /lib/ld-linux.so.2 $ramdir/lib")) {
		croak("could not copy /lib/ld-linux.so.2");
	}

	#copy dev over from systemimager's initrd_template
	my $initrd_template = "/usr/share/systemimager/boot/i386/standard/initrd_template/dev";
	system("cp -r $initrd_template $ramdir"); #IT WILL NOT FAIL!

	if(@ramfs_http_ips) {
		&generate_linuxrc(1);
		if(system("mkcramfs $ramdir $ramdir/ramfs-http-initrd")) {
		croak("cannot run mkcramfs");
		}
		if(system("gzip -9 -f -S .img $ramdir/ramfs-http-initrd")) {
			croak("cannot run gzip");
		}
		if(system("mv $ramdir/ramfs-http-initrd.img /tftpboot/")) {
			croak("cannot move $ramdir/ramfs-http-initrd.img to /tftpboot");
		}
	}

	if(@ramfs_rsync_ips) {
		&generate_linuxrc(0);
		if(system("mkcramfs $ramdir $ramdir/ramfs-rsync-initrd")) {
		croak("cannot run mkcramfs");
		}	
		if(system("gzip -9 -f -S .img $ramdir/ramfs-rsync-initrd")) {
			croak("cannot run gzip");
		}
		if(system("mv $ramdir/ramfs-rsync-initrd.img /tftpboot/")) {
			croak("cannot move $ramdir/ramfs-rsync-initrd.img to /tftpboot");
		}
	}
}

###########################################################
# generate_linuxrc
#
# this generates the linuxrc file used by the initrd. the
# type of linuxrc file generated is based on a passed in
# value. 0 is for one using rsync and 1 is for one using
# http.
#
# Usage:
# generate_linuxrc(0);
###########################################################
sub generate_linuxrc {
	my ($ip,$netmask) = &find_ip(); #kind of pointless?
	my $choice = shift;
	my $ramdir = "/tmp/initrd";

########################
#HTTP VERSION
########################
	my $linuxrc_http =<<EOF;
#!/bin/bash

$paths{"mount"} -n -t proc none /proc

echo "Maximum amount of memory is being determined..."
#find memory
MAXMEM=\`$paths{"cat"} /proc/meminfo | $paths{"grep"} MemTotal | $paths{"sed"} -e "s/[a-z,A-Z]*:[[:space:]]*\\([0-9]*\\).*/\\1/"\`
echo "\${MAXMEM} KB detected..."

$paths{"ifconfig"} eth0 up 2>&1>/dev/null

echo "Mounting tmpfs partition to /new_root/"
$paths{"mount"} -n -t tmpfs -o size=\${meminfo}k tmpfs new_root/

$paths{"dhclient"} 2>&1>/dev/null

echo "determining ip address..."
IP=\`$paths{"ifconfig"} eth0 | $paths{"grep"} "inet addr" | $paths{"sed"} -e "s/.*inet addr:\\([0-9]*.[0-9]*.[0-9]*.[0-9]*\\) .*/\\1/"\`
echo "ip is \${IP}"

KERNEL=\`$paths{"cat"} /proc/sys/kernel/osrelease | $paths{"grep"} boel\`

#rsync
if [ ! -z \${KERNEL} ]
then
$paths{"rsync"} -aHS $ip\::boot/i386/standard/boel_binaries.tar.gz new_root/
$paths{"tar"} xzvf new_root/boel_binaries.tar.gz -C /new_root
fi

echo "retrieving tarball..."
$paths{"wget"} -O new_root/tar.test.bz2 http://$ip/tar.\${IP}.bz2 ./
echo "done...untarring."
$paths{"tar"} xjvf new_root/tar.test.bz2 -C /new_root

echo "changing root mount point..."
$paths{"umount"} /proc
cd /new_root/
$paths{"mkdir"} old_root/
./sbin/pivot_root . old_root/
cd /
/bin/mount -t proc none /proc
/bin/mount -t sysfs none /sys

if [ ! -z \${KERNEL} ]
then
/bin/rm -f /boel_binaries.tar.gz
fi

/bin/rm -f /tar.test.bz2
cat<<EOL > /etc/fstab
devpts	/dev/pts	devpts	defaults	0 0
$ip:/home	/home	nfs	defaults	1 1
EOL

HOSTNAME=\`cat /etc/hosts | grep "\${IP} " | sed -e "s/.*[[:space:]]//"\`

#for fedora
if [ -f /etc/fedora-release ]
then 
cat<<EOL > /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=\${HOSTNAME}
EOL
fi

/sbin/route add default gw $ip eth0

cd /

exec /usr/sbin/chroot . /bin/bash -c "/sbin/init -i"
EOF


##################
#NOW RSYNC's VERSION
##################
	my $linuxrc_rsync =<<EOF;
#!/bin/bash

$paths{"mount"} -n -t proc none /proc

#find memory
echo "Maximum amount of memory is being determined..."
MAXMEM=\`$paths{"cat"} /proc/meminfo | $paths{"grep"} MemTotal | $paths{"sed"} -e "s/[a-z,A-Z]*:[[:space:]]*\\([0-9]*\\).*/\\1/"\`
echo "\${MAXMEM} KB detected..."

$paths{"ifconfig"} eth0 up 2>&1>/dev/null

echo "Mounting tmpfs partition to /new_root/"
$paths{"mount"} -n -t tmpfs -o size=\${meminfo}k tmpfs new_root/

$paths{"dhclient"} 2>&1>/dev/null

echo "determining ip address..."
IP=\`$paths{"ifconfig"} eth0 | $paths{"grep"} "inet addr" | $paths{"sed"} -e "s/.*inet addr:\\([0-9]*.[0-9]*.[0-9]*.[0-9]*\\) .*/\\1/"\`
echo "ip is \${IP}"

KERNEL=\`cat /proc/sys/kernel/osrelease | grep boel\`

#rsync
if [ ! -z \${KERNEL} ]
then
$paths{"rsync"} -aHS $ip\::boot/i386/standard/boel_binaries.tar.gz new_root/
$paths{"tar"} xzvf new_root/boel_binaries.tar.gz -C /new_root
fi

echo "beginning rsync of image..."
$paths{"rsync"} -aHS $ip\::\${IP} new_root/
echo "...done!"

echo "changing mount point of root"
$paths{"umount"} /proc
cd /new_root/
$paths{"mkdir"} old_root/
./sbin/pivot_root . old_root/
cd /
/bin/mount -t proc none /proc
/bin/mount -t sysfs none /sys

if [ ! -z \${KERNEL} ]
then
/bin/rm -f /boel_binaries.tar.gz
fi

cat<<EOL > /etc/fstab
proc	/proc	proc	defaults	0 0
sysfs	/sys	sysfs	defaults	0 0
devpts	/dev/pts	devpts	defaults	0 0
$ip:/home	/home	nfs	defaults	1 1
EOL

HOSTNAME=\`cat /etc/hosts | grep "\${IP} " | sed -e "s/.*[[:space:]]//"\`

#for fedora
if [ -f /etc/fedora-release ]
then 
cat<<EOL > /etc/sysconfig/network
NETWORKING=yes
HOSTNAME=\${HOSTNAME}
EOL
fi

echo "transferring modules dir to computenode."
rsync -aHS $ip\::modules /lib/modules

/sbin/route add default gw $ip eth0

cd /

exec /usr/sbin/chroot . /bin/bash -c "/sbin/init -i"
EOF

	if($choice) {
		open(OUT,">$ramdir/linuxrc");
		print OUT $linuxrc_http;
		close(OUT);
	} else {
		open(OUT,">$ramdir/linuxrc");
		print OUT $linuxrc_rsync;
		close(OUT);
	}
	
	if(system("chmod 777 $ramdir/linuxrc")) {
		croak("cannot run: chmod 755 $ramdir/linuxrc");
	}

	#need to create symlink in /sbin to make this work
	unless(-e "$ramdir/sbin/init") {
		if(system("ln -s ../linuxrc $ramdir/sbin/init")) {
			croak("could not run \"ln -s ../linuxrc $ramdir/sbin/init\"");
		}
	}
}

###########################################################
# modify_rsyncd
#
# modifies the rsyncd.conf file to allow for the client
# to use rsync to retreive the image upon boot.
#
# Usage:
# modify_rsyncd();
###########################################################
sub modify_rsyncd {
	my $file = "/etc/systemimager/rsyncd.conf";

	if(@ramfs_rsync_ips) {
		copy("$file","$file.bak"); #make backup
		open(OUT,">>$file");

		foreach my $ips (@ramfs_rsync_ips) {
			#if it's not there, add it
			if(system("grep $ips] $file 2>&1>/dev/null")) { #not a typo with $ips]
				print OUT "[$ips]\n\tpath=/var/lib/systemimager/images/$ip_to_image{$ips}\n\n";
			}
		}

		close(OUT);
	}
}

1;
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
sisuite-devel mailing list
sisuite-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sisuite-devel

Reply via email to