| rev | line source | 
| devi@0 | 1 #!/usr/bin/perl | 
| devi@0 | 2 | 
| devi@0 | 3 | 
| devi@0 | 4 use strict; | 
| devi@0 | 5 use Data::Dumper; | 
| devi@0 | 6 use Switch; | 
| devi@0 | 7 use XML::Simple; | 
| devi@0 | 8 use Getopt::Long; | 
| devi@4 | 9 use utf8; | 
| devi@0 | 10 | 
| devi@40 | 11 use lib "/usr/local/bin"; | 
| devi@40 | 12 use l3config; | 
| devi@40 | 13 | 
| devi@0 | 14 our $XMLClass; | 
| devi@0 | 15 our $XMLCourse; | 
| devi@0 | 16 our @Labs; | 
| devi@0 | 17 | 
| devi@0 | 18 our %Machines;			# Machines list from class.xml | 
| devi@0 | 19 our @SelectedMachines;		# Machines list given as the command line argument | 
| devi@0 | 20 | 
| devi@0 | 21 our $Config_File = "labmaker.conf"; | 
| devi@40 | 22 our %Config_ = ( | 
| devi@0 | 23 	"show_host" 	=> "no", | 
| devi@0 | 24 | 
| devi@3 | 25 	# Вспомогательные программы | 
| devi@23 | 26 	#"l3-report"	=> "./lm-report", | 
| devi@23 | 27 	"l3-report"	=> "./l3-report", | 
| devi@3 | 28 | 
| devi@0 | 29 	# Каталоги | 
| devi@13 | 30 	"path_lilalo" => "/var/lilalo/", | 
| devi@10 | 31 	"path_classes"	=> "/var/lilalo/classes/", | 
| devi@10 | 32 	"path_lablogs"	=> "/var/lilalo/lablogs/", | 
| devi@10 | 33 	"courses_path"	=> "/var/lilalo/courses/", | 
| devi@10 | 34 	"outpath"	=> "/var/lilalo/out/", | 
| devi@3 | 35 	"path_web"	=> "/var/www/l3",		# Путь к web-отчётам | 
| devi@3 | 36 	"path_share"	=> "./share/",		# Путь к web-отчётам | 
| devi@0 | 37 | 
| devi@0 | 38 	# Файлы | 
| devi@0 | 39 	"runfile"	=> "lm.run", | 
| devi@0 | 40 	"logfile"	=> "lm.log", | 
| devi@0 | 41 | 
| devi@0 | 42 	"class" 	=> "class", 				# Имя файла класса | 
| devi@0 | 43 	"class_suffix" 	=> ".xml", 				# Cуффикс файла класса | 
| devi@5 | 44 	"classfile"	=> "", | 
| devi@0 | 45 | 
| devi@0 | 46 	"sshkey"	=> "$ENV{HOME}/.ssh/id_dsa.pub", | 
| devi@0 | 47 	"lmssh"		=> "./lm-ssh", | 
| devi@0 | 48 	"lminstall"	=> "./lm-install", | 
| devi@17 | 49 	"ssh_user"	=> "root", | 
| devi@0 | 50 ); | 
| devi@0 | 51 | 
| devi@0 | 52 our %Run = ( | 
| devi@0 | 53 	"lab" => "" | 
| devi@0 | 54 ); | 
| devi@0 | 55 | 
| devi@0 | 56 our %Scripts; | 
| devi@0 | 57 | 
| devi@0 | 58 sub load_class; | 
| devi@0 | 59 sub load_config; | 
| devi@0 | 60 sub load_course; | 
| devi@0 | 61 sub load_scripts; | 
| devi@0 | 62 | 
| devi@0 | 63 sub lm_next; | 
| devi@0 | 64 sub lm_prev; | 
| devi@0 | 65 sub lm_start; | 
| devi@0 | 66 sub lm_stop; | 
| devi@0 | 67 sub lm_set; | 
| devi@0 | 68 sub lm_do; | 
| devi@0 | 69 sub lm_report; | 
| devi@0 | 70 sub lm_show_hosts; | 
| devi@0 | 71 sub lm_show_labs; | 
| devi@0 | 72 | 
| devi@0 | 73 sub load_run; | 
| devi@0 | 74 sub save_run; | 
| devi@0 | 75 sub print_log; | 
| devi@0 | 76 sub print_usage_info; | 
| devi@0 | 77 sub main(); | 
| devi@0 | 78 | 
| devi@0 | 79 main(); | 
| devi@0 | 80 | 
| devi@0 | 81 sub main() | 
| devi@0 | 82 { | 
| devi@0 | 83 	binmode STDOUT, ":utf8"; | 
| devi@0 | 84 | 
| devi@0 | 85 	if (! @ARGV) { | 
| devi@0 | 86 		print_usage_info(); | 
| devi@0 | 87 		exit(0); | 
| devi@0 | 88 	} | 
| devi@0 | 89 | 
| devi@40 | 90 	init_config(); | 
| devi@40 | 91 	#load_config; | 
| devi@0 | 92 	load_run; | 
| devi@0 | 93 	load_scripts; | 
| devi@0 | 94 	load_class; | 
| devi@0 | 95 	load_course; | 
| devi@0 | 96 | 
| devi@0 | 97 	my $arg = join " ", @ARGV; | 
| devi@0 | 98 | 
| devi@0 | 99 	# Getting @SelectedMachines if any | 
| devi@0 | 100 	if ($arg =~ s/@(.*?)\s//) { | 
| devi@0 | 101 		my $machines = $1; | 
| devi@0 | 102 		my @list = split /,/, $machines; | 
| devi@0 | 103 		for my $interval (@list) { | 
| devi@0 | 104 			my ($first, $last) = split /-/, $interval; | 
| devi@0 | 105 | 
| devi@0 | 106 			push @SelectedMachines, $first; | 
| devi@0 | 107 			while ($first < $last) { | 
| devi@0 | 108 				push @SelectedMachines, ++$first; | 
| devi@0 | 109 			} | 
| devi@0 | 110 		} | 
| devi@0 | 111 	} | 
| devi@0 | 112 | 
| devi@0 | 113 	# Choose command to do | 
| devi@0 | 114 	switch ($arg) { | 
| devi@0 | 115 		case "next"	{ lm_next } | 
| devi@0 | 116 		case "prev"	{ lm_prev } | 
| devi@0 | 117 		case /set /	{ $arg =~ /set (.*)/; lm_set $1 } | 
| devi@0 | 118 		case "report"	{ lm_report } | 
| devi@0 | 119 		case "start"	{ lm_start } | 
| devi@0 | 120 		case "stop"	{ lm_stop } | 
| devi@0 | 121 		case "show hosts" { lm_show_hosts } | 
| devi@0 | 122 		case "show labs" { lm_show_labs } | 
| devi@0 | 123 		case /do /  { $arg =~ /do (.*)/;  lm_do "$1" } | 
| devi@0 | 124 		else 		{ print_usage_info() } | 
| devi@0 | 125 	} | 
| devi@0 | 126 	save_run; | 
| devi@0 | 127 	exit(0); | 
| devi@0 | 128 } | 
| devi@0 | 129 | 
| devi@0 | 130 sub load_scripts | 
| devi@0 | 131 { | 
| devi@40 | 132 	print join " ", %Config; | 
| devi@40 | 133 	print "\n"; | 
| devi@40 | 134 | 
| devi@40 | 135 	open (SCRIPTS, "$Config{l3scripts}") | 
| devi@40 | 136 		or die "Cant open l3scripts file: ".$Config{l3scripts}.": $!\n"; | 
| devi@40 | 137 	binmode SCRIPTS, ":utf8"; | 
| devi@0 | 138 	local $/; | 
| devi@0 | 139 	$_=<SCRIPTS>; | 
| devi@40 | 140 	close(SCRIPTS); | 
| devi@40 | 141 | 
| devi@0 | 142 	%Scripts = ("empty-element", split (/###(.*)\n/)); | 
| devi@0 | 143 	delete($Scripts{"empty-element"}); | 
| devi@40 | 144 | 
| devi@0 | 145 } | 
| devi@0 | 146 | 
| devi@0 | 147 sub load_config | 
| devi@0 | 148 { | 
| devi@0 | 149 	my %file_config; | 
| devi@0 | 150 	my %argv_config; | 
| devi@0 | 151 	#read_config_file(\%file_config, $Config_File); | 
| devi@0 | 152 	GetOptions(\%argv_config, map "$_=s", keys %Config); | 
| devi@0 | 153 	%Config = (%Config, %file_config, %argv_config); | 
| devi@0 | 154 } | 
| devi@0 | 155 | 
| devi@0 | 156 sub load_course | 
| devi@0 | 157 { | 
| devi@0 | 158 	$XMLCourse = XMLin($Config{"courses_path"}.$XMLClass->{"course"}.".xml", ForceArray => 1 ) | 
| devi@0 | 159 		or die "Can't open file of the course ",$XMLClass->{"course"}," [with .xml extension]\n"; | 
| devi@0 | 160 #	print Dumper($XMLCourse); | 
| devi@0 | 161 	for my $lab (@{$XMLCourse->{"module"}}) { | 
| devi@0 | 162 		push @Labs, $lab->{"code"}; | 
| devi@0 | 163 	} | 
| devi@0 | 164 } | 
| devi@0 | 165 | 
| devi@0 | 166 sub load_class | 
| devi@0 | 167 { | 
| devi@0 | 168 	my $classfile = | 
| devi@5 | 169 	$Config{"classfile"} || | 
| devi@0 | 170 	$Config{"path_classes"}."/".$Config{"class"}.$Config{"class_suffix"}; | 
| devi@0 | 171 	$XMLClass = XMLin($classfile , ForceArray => [ 'student' ] ) | 
| devi@0 | 172 		or die "Can't open file of the class ",$classfile,"\n"; | 
| devi@0 | 173 | 
| devi@0 | 174 	for my $student (@{$XMLClass->{"student"}}) { | 
| devi@0 | 175 		$Machines{$student->{"host"}} = { | 
| devi@0 | 176 			"name"	=> "$student->{firstname} $student->{surname}", | 
| devi@0 | 177 			"user"	=> "$student->{user}", | 
| devi@0 | 178 			"student" => $student, | 
| devi@0 | 179 		} | 
| devi@0 | 180 	} | 
| devi@0 | 181 #	print Dumper($XMLClass); | 
| devi@0 | 182 #	print Dumper(\%Machines); | 
| devi@0 | 183 } | 
| devi@0 | 184 | 
| devi@0 | 185 | 
| devi@0 | 186 sub lm_next | 
| devi@0 | 187 { | 
| devi@0 | 188 	for(my $i=0; $i<=$#Labs; $i++){ | 
| devi@0 | 189 		if ( $Labs[$i] eq $Run{"lab"} ) { | 
| devi@0 | 190 			if ($i < $#Labs) { | 
| devi@0 | 191 				lm_set($Labs[$i+1]); | 
| devi@0 | 192 				return ; | 
| devi@0 | 193 			} else { | 
| devi@0 | 194 				die "Lab ", $Run{"lab"}, " is the last. Switch to next lab is impossible" | 
| devi@0 | 195 			} | 
| devi@0 | 196 		} | 
| devi@0 | 197 | 
| devi@0 | 198 	} | 
| devi@0 | 199 	die "Lab ", $Run{"lab"}, " not found. Don't know which is next" | 
| devi@0 | 200 } | 
| devi@0 | 201 | 
| devi@0 | 202 sub lm_prev | 
| devi@0 | 203 # Switch to previous lab | 
| devi@0 | 204 { | 
| devi@0 | 205 	for(my $i=0; $i<=$#Labs; $i++){ | 
| devi@0 | 206 		if ( $Labs[$i] eq $Run{"lab"} ) { | 
| devi@0 | 207 			if ($i > 0) { | 
| devi@0 | 208 				lm_set($Labs[$i-1]); | 
| devi@0 | 209 				return ; | 
| devi@0 | 210 			} else { | 
| devi@0 | 211 				die "Lab ", $Run{"lab"}, " is the first. Switch to previous lab is impossible" | 
| devi@0 | 212 			} | 
| devi@0 | 213 		} | 
| devi@0 | 214 | 
| devi@0 | 215 	} | 
| devi@0 | 216 	die "Lab ", $Run{"lab"}, " not found. Don't know which is previous" | 
| devi@0 | 217 } | 
| devi@0 | 218 | 
| devi@0 | 219 sub lm_set | 
| devi@0 | 220 # Switch to $_[0] lab | 
| devi@0 | 221 # FIXME | 
| devi@0 | 222 { | 
| devi@0 | 223 	my $lab = shift; | 
| devi@0 | 224 	print "Current lab is $lab\n"; | 
| devi@0 | 225 	$Run{"lab"} = "$lab"; | 
| devi@0 | 226 	lm_do "setlab", $lab; | 
| devi@0 | 227 } | 
| devi@0 | 228 | 
| devi@0 | 229 | 
| devi@0 | 230 sub lm_start | 
| devi@0 | 231 # Start new training day | 
| devi@0 | 232 { | 
| devi@0 | 233 	print_log(`date`." STARTED\n"); | 
| devi@0 | 234 	if ($Run{"lab"}) { | 
| devi@0 | 235 		lm_next; | 
| devi@0 | 236 	} | 
| devi@0 | 237 	else | 
| devi@0 | 238 	{ | 
| devi@0 | 239 		# First lab in the course | 
| devi@0 | 240 		lm_set($Labs[0]); | 
| devi@0 | 241 	} | 
| devi@0 | 242 } | 
| devi@0 | 243 | 
| devi@0 | 244 sub lm_stop | 
| devi@0 | 245 # Stop this training day | 
| devi@0 | 246 { | 
| devi@0 | 247 	print_log(`date`." STOPPED\n"); | 
| devi@0 | 248 } | 
| devi@0 | 249 | 
| devi@0 | 250 | 
| devi@0 | 251 sub lm_show_hosts | 
| devi@0 | 252 # Show hosts used to run a commands | 
| devi@0 | 253 { | 
| devi@0 | 254 	my $i=1; | 
| devi@0 | 255 	for my $m (sort keys %Machines) { | 
| devi@0 | 256 		if (!@SelectedMachines || grep /^$i$/, @SelectedMachines) { | 
| devi@0 | 257 			print "($i)","\t",$m,"\t",$Machines{$m}->{"name"},"\n"; | 
| devi@0 | 258 		} | 
| devi@0 | 259 		$i++; | 
| devi@0 | 260 	} | 
| devi@0 | 261 } | 
| devi@0 | 262 | 
| devi@0 | 263 sub lm_show_labs | 
| devi@0 | 264 # Show hosts used to run a commands | 
| devi@0 | 265 { | 
| devi@0 | 266 	my $i=1; | 
| devi@0 | 267 	for my $lab (@Labs) { | 
| devi@0 | 268 		print $lab; | 
| devi@0 | 269 		print "*" if $lab eq $Run{"lab"}; | 
| devi@0 | 270 		print "\n"; | 
| devi@0 | 271 	} | 
| devi@0 | 272 } | 
| devi@0 | 273 | 
| devi@0 | 274 sub lm_do | 
| devi@0 | 275 # Do the $_[0] command on all of the hosts | 
| devi@0 | 276 { | 
| devi@0 | 277 	my $command = shift; | 
| devi@0 | 278 	my $arg = join " ", @_; | 
| devi@0 | 279 	my $i=1; | 
| devi@40 | 280 | 
| devi@40 | 281 	my %myenv = ( %Config, | 
| devi@40 | 282 				lab	=> 	$arg, | 
| devi@40 | 283 				center 	=>	$XMLClass->{"center"}, | 
| devi@40 | 284 				course 	=>	$XMLClass->{"course"}, | 
| devi@40 | 285 				date 	=>	$XMLClass->{"date"}, | 
| devi@40 | 286 				coursepath =>	$XMLCourse->{"path"}, | 
| devi@40 | 287 			); | 
| devi@40 | 288 | 
| devi@40 | 289 	if (grep { $_ eq "PRE-$command"} keys %Scripts) { | 
| devi@40 | 290 		$_=$Scripts{"PRE-$command"}; | 
| devi@40 | 291 		s/\$(\w+)/$myenv{$1}/ge; | 
| devi@40 | 292 		open(SHELL, "|/bin/sh -s"); | 
| devi@40 | 293 		binmode SHELL, ":utf8"; | 
| devi@40 | 294 		print SHELL $_; | 
| devi@40 | 295 		close (SHELL); | 
| devi@40 | 296 	} | 
| devi@40 | 297 | 
| devi@40 | 298 | 
| devi@0 | 299 	for my $m (sort keys %Machines) { | 
| devi@0 | 300 		if (!@SelectedMachines || grep $_ eq $i, @SelectedMachines) { | 
| devi@0 | 301 			print "$m:\n" if $Config{"show_host"} =~ /y/i; | 
| devi@0 | 302 | 
| devi@40 | 303 			%myenv = ( %myenv, | 
| devi@0 | 304 				host 	=>	$m, | 
| devi@0 | 305 				dirs	=>	"/root /home/".$Machines{$m}->{"user"}, | 
| devi@3 | 306 				lablogs =>	$Config{"path_lablogs"}."/". | 
| devi@0 | 307 						$XMLClass->{"course"}."/". | 
| devi@0 | 308 						$XMLClass->{"date"}."/". | 
| devi@0 | 309 						"$m", | 
| devi@0 | 310 				email	=>	$Machines{$m}->{"student"}->{"email"}, | 
| devi@0 | 311 				company	=>	$Machines{$m}->{"student"}->{"company"}, | 
| devi@0 | 312 				name	=> 	$Machines{$m}->{"name"}, | 
| devi@0 | 313 			); | 
| devi@0 | 314 			if (grep { $_ eq $command} keys %Scripts) { | 
| devi@0 | 315 				$_=$Scripts{"$command"}; | 
| devi@0 | 316 				s/\$(\w+)/$myenv{$1}/ge; | 
| devi@0 | 317 				open(SHELL, "|/bin/sh -s"); | 
| devi@0 | 318 				binmode SHELL, ":utf8"; | 
| devi@0 | 319 				print SHELL $_; | 
| devi@0 | 320 				close (SHELL); | 
| devi@0 | 321 			} | 
| devi@0 | 322 			else { | 
| devi@0 | 323 				my $res = `ssh $Config{"ssh_user"}\@$m $command`; | 
| devi@0 | 324 				if ($res) { | 
| devi@0 | 325 					my $count = ($res =~ s/(^)/$m: /mg); | 
| devi@0 | 326 					print $res; | 
| devi@0 | 327 					print "\n" if ($count > 1); | 
| devi@0 | 328 				} | 
| devi@0 | 329 			} | 
| devi@0 | 330 		} | 
| devi@0 | 331 		$i++; | 
| devi@0 | 332 	} | 
| devi@40 | 333 | 
| devi@40 | 334 	if (grep { $_ eq "POST-$command"} keys %Scripts) { | 
| devi@40 | 335 		$_=$Scripts{"POST-$command"}; | 
| devi@40 | 336 		s/\$(\w+)/$myenv{$1}/ge; | 
| devi@40 | 337 		open(SHELL, "|/bin/sh -s"); | 
| devi@40 | 338 		binmode SHELL, ":utf8"; | 
| devi@40 | 339 		print SHELL $_; | 
| devi@40 | 340 		close (SHELL); | 
| devi@40 | 341 	} | 
| devi@0 | 342 } | 
| devi@0 | 343 | 
| devi@0 | 344 | 
| devi@3 | 345 | 
| devi@3 | 346 =cut comment | 
| devi@3 | 347 | 
| devi@3 | 348 lm report | 
| devi@3 | 349 | 
| devi@3 | 350 Построить html представление для журналов текущего класса. | 
| devi@3 | 351 Для построения используется скрипт l3-report. | 
| devi@3 | 352 | 
| devi@3 | 353 =cut | 
| devi@3 | 354 | 
| devi@0 | 355 sub lm_report | 
| devi@0 | 356 { | 
| devi@0 | 357 | 
| devi@3 | 358 	my $webdir = $Config{"path_web"}; | 
| devi@3 | 359 	my $course=$XMLClass->{"course"}; | 
| devi@3 | 360 	my $date=$XMLClass->{"date"}; | 
| devi@3 | 361 	my $encoding=$XMLClass->{"charset"}; | 
| devi@0 | 362 | 
| devi@4 | 363 	my $center = $XMLClass->{"center"}; | 
| devi@4 | 364 	my $instructor = $XMLClass->{"instructor"}->{"firstname"}." ".$XMLClass->{"instructor"}->{"surname"}; | 
| devi@4 | 365 	my $course_name = $XMLCourse->{"fullname"}[0]; | 
| devi@4 | 366 | 
| devi@5 | 367 | 
| devi@5 | 368 	# Собственно журналы | 
| devi@5 | 369 | 
| devi@5 | 370 	for my $student (@{$XMLClass->{"student"}}) { | 
| devi@5 | 371 		my $user = $student->{"user"}; | 
| devi@5 | 372 		my $hostname = $student->{"host"}; | 
| devi@6 | 373 		my $encoding = $student->{"charset"}; | 
| devi@6 | 374 		my $student_name = $student->{"firstname"}." ".$student->{"surname"}; | 
| devi@5 | 375 | 
| devi@5 | 376 		system("mkdir -p $webdir/$date/$hostname"); | 
| devi@5 | 377 		system("cp ".$Config{"path_share"}."/*.{ico,css} $webdir/$date/$hostname"); | 
| devi@5 | 378 		system($Config{"l3-report"}. | 
| devi@5 | 379 			" --input ".$Config{"path_lablogs"}."/$course/$date/$hostname/$user". | 
| devi@5 | 380 			" --diffs ".$Config{"path_lablogs"}."/$course/$date/$hostname/$user ". | 
| devi@5 | 381 				   $Config{"path_lablogs"}."/$course/$date/$hostname/root". | 
| devi@5 | 382 			" --output $webdir/$date/$hostname/$user.html". | 
| devi@6 | 383 			" --course-name '$course_name'". | 
| devi@6 | 384 			" --course-code '$course'". | 
| devi@6 | 385 			" --course-date '$date'". | 
| devi@6 | 386 			" --course-center '$center'". | 
| devi@6 | 387 			" --course-student '$student_name'". | 
| devi@6 | 388 			" --course-trainer '$instructor'". | 
| devi@5 | 389 			" --encoding $encoding" | 
| devi@5 | 390 		); | 
| devi@5 | 391 		system($Config{"l3-report"}. | 
| devi@5 | 392 			" --input ".$Config{"path_lablogs"}."/$course/$date/$hostname/root". | 
| devi@5 | 393 			" --diffs ".$Config{"path_lablogs"}."/$course/$date/$hostname/root ". | 
| devi@5 | 394 			" --output $webdir/$date/$hostname/root.html". | 
| devi@6 | 395 			" --course-name '$course_name'". | 
| devi@6 | 396 			" --course-code '$course'". | 
| devi@6 | 397 			" --course-date '$date'". | 
| devi@6 | 398 			" --course-center '$center'". | 
| devi@6 | 399 			" --course-student '$student_name'". | 
| devi@6 | 400 			" --course-trainer '$instructor'". | 
| devi@5 | 401 			" --encoding $encoding" | 
| devi@5 | 402 		); | 
| devi@5 | 403 	} | 
| devi@5 | 404 | 
| devi@4 | 405 	# Индекс для данного класса | 
| devi@4 | 406 | 
| devi@4 | 407 	my $head; | 
| devi@4 | 408 | 
| devi@4 | 409 	$head="Журналы лабораторных работ"; | 
| devi@4 | 410 	open(HTML, ">$webdir/$date/index.html") | 
| devi@4 | 411 		or die "Can't open $webdir/$date/index.html for writing"; | 
| devi@4 | 412 	binmode HTML, ":utf8"; | 
| devi@4 | 413 	print HTML <<HEAD; | 
| devi@4 | 414 	<html> | 
| devi@4 | 415 	<head> | 
| devi@4 | 416 	<meta content='text/html; charset=utf-8' http-equiv='Content-Type' /> | 
| devi@4 | 417 	<title>$head</title> | 
| devi@4 | 418 	</head> | 
| devi@4 | 419 	<body> | 
| devi@4 | 420 	<h1>$head</h1> | 
| devi@4 | 421 	<p> | 
| devi@4 | 422 	Курс: $course_name ($course)<br/> | 
| devi@4 | 423 	Начало: $date<br/> | 
| devi@4 | 424 	Учебный центр: $center <br/> | 
| devi@4 | 425 	Инструктор: $instructor <br/> | 
| devi@4 | 426 	</p> | 
| devi@4 | 427 	<table> | 
| devi@4 | 428 HEAD | 
| devi@4 | 429 	for my $student (@{$XMLClass->{"student"}}) { | 
| devi@4 | 430 		my $user = $student->{"user"}; | 
| devi@4 | 431 		my $hostname = $student->{"host"}; | 
| devi@4 | 432 		print HTML "<tr>\n"; | 
| devi@4 | 433 		print HTML "<td>",$student->{"firstname"}," ",$student->{"surname"},"</td>\n"; | 
| devi@4 | 434 		print HTML "<td>",$hostname,"</td>\n"; | 
| devi@4 | 435 		print HTML "<td><a href=\"$hostname/$user.html\">",$user,"</td>\n"; | 
| devi@4 | 436 		print HTML "<td><a href=\"$hostname/root.html\">","root","</td>\n"; | 
| devi@4 | 437 		print HTML "</tr>\n"; | 
| devi@4 | 438 	} | 
| devi@4 | 439 	print HTML <<TAIL; | 
| devi@4 | 440 	</table> | 
| devi@4 | 441 	</html> | 
| devi@4 | 442 TAIL | 
| devi@4 | 443 	close (HTML); | 
| devi@4 | 444 | 
| devi@4 | 445 | 
| devi@4 | 446 | 
| devi@0 | 447 } | 
| devi@0 | 448 | 
| devi@0 | 449 sub load_run | 
| devi@0 | 450 { | 
| devi@0 | 451 	my $runfile = $Config{"path_labmaker"}."/".$Config{"path_runfile"}; | 
| devi@0 | 452 	open (RUN, $runfile) | 
| devi@0 | 453 		or return; | 
| devi@0 | 454 	while (<RUN>) { | 
| devi@0 | 455 		chomp; | 
| devi@0 | 456 		my ($var, $val) = split /\s+/,$_,2; | 
| devi@0 | 457 		$Run{$var}=$val; | 
| devi@0 | 458 	} | 
| devi@0 | 459 	close (RUN); | 
| devi@0 | 460 } | 
| devi@0 | 461 | 
| devi@0 | 462 sub save_run | 
| devi@0 | 463 { | 
| devi@0 | 464 	my $runfile = $Config{"path_labmaker"}."/".$Config{"path_runfile"}; | 
| devi@0 | 465 	open (RN, "$runfile") | 
| devi@0 | 466 		or die "Can't save running state to $runfile"; | 
| devi@0 | 467 	for my $var (keys %Run) { | 
| devi@0 | 468 		print RN $var,"\t",$Run{$var},"\n"; | 
| devi@0 | 469 	} | 
| devi@0 | 470 	close (RN); | 
| devi@0 | 471 } | 
| devi@0 | 472 | 
| devi@0 | 473 sub print_log | 
| devi@0 | 474 { | 
| devi@0 | 475 	my $logfile = $Config{"path_labmaker"}."/".$Config{"path_logfile"}; | 
| devi@0 | 476 	open (LOG, ">>$logfile") | 
| devi@0 | 477 		or die "Can't open logfile $logfile for writing"; | 
| devi@0 | 478 	print LOG  @_; | 
| devi@0 | 479 	close (LOG); | 
| devi@0 | 480 } | 
| devi@0 | 481 | 
| devi@0 | 482 | 
| devi@0 | 483 sub print_usage_info | 
| devi@0 | 484 { | 
| devi@0 | 485 	print "Usage:\n\n\t$0 [host-list] command\n"; | 
| devi@40 | 486 	print <<'USAGE'; | 
| devi@0 | 487 | 
| devi@0 | 488 Commands: | 
| devi@0 | 489 | 
| devi@0 | 490 	next		-- next lab | 
| devi@0 | 491 	prev		-- prev lab | 
| devi@0 | 492 	set LAB		-- set current lab to LAB | 
| devi@0 | 493 	start		-- start this day training | 
| devi@0 | 494 	stop		-- stop this day training | 
| devi@0 | 495 	show hosts	-- show available hosts in the class | 
| devi@0 | 496 	show labs	-- show available labs in the course | 
| devi@0 | 497 	do COMMAND	-- do specified command on the hosts of hostlist | 
| devi@0 | 498 	report		-- generate XML/HTML reports | 
| devi@0 | 499 | 
| devi@0 | 500 | 
| devi@0 | 501 do commands: | 
| devi@0 | 502 | 
| devi@0 | 503 	install [PROFILE] -- install profile | 
| devi@0 | 504 | 
| devi@0 | 505 Host list: | 
| devi@0 | 506 | 
| devi@0 | 507 	@N		-- machine N | 
| devi@0 | 508 	@N1-N2		-- all of the machines from N1 to N2 | 
| devi@0 | 509 	@N1,N2,N3	-- machine N1, N2 and N3 | 
| devi@0 | 510 | 
| devi@0 | 511 	N* is numbers or domain names of the machines. | 
| devi@0 | 512 | 
| devi@0 | 513 	If host list is not specified, | 
| devi@0 | 514 	command is executed on all of the machines | 
| devi@0 | 515 | 
| devi@40 | 516 USAGE | 
| devi@40 | 517 } | 
| devi@0 | 518 | 
| devi@0 | 519 |