Home | History | Annotate | Line # | Download | only in system
      1 #!/usr/bin/perl -w
      2 
      3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      4 #
      5 # SPDX-License-Identifier: MPL-2.0
      6 #
      7 # This Source Code Form is subject to the terms of the Mozilla Public
      8 # License, v. 2.0.  If a copy of the MPL was not distributed with this
      9 # file, you can obtain one at https://mozilla.org/MPL/2.0/.
     10 #
     11 # See the COPYRIGHT file distributed with this work for additional
     12 # information regarding copyright ownership.
     13 
     14 # Framework for starting test servers.
     15 # Based on the type of server specified, check for port availability, remove
     16 # temporary files, start the server, and verify that the server is running.
     17 # If a server is specified, start it. Otherwise, start all servers for test.
     18 
     19 use strict;
     20 use warnings;
     21 
     22 use Cwd ':DEFAULT', 'abs_path';
     23 use English '-no_match_vars';
     24 use Getopt::Long;
     25 use Time::HiRes 'sleep'; # allows sleeping fractional seconds
     26 
     27 # Usage:
     28 #   perl start.pl [--noclean] [--restart] [--port port] [--taskset cpus] test [server [options]]
     29 #
     30 #   --noclean       Do not cleanup files in server directory.
     31 #
     32 #   --restart       Indicate that the server is being restarted, so get the
     33 #                   server to append output to an existing log file instead of
     34 #                   starting a new one.
     35 #
     36 #   --port port     Specify the default port being used by the server to answer
     37 #                   queries (default 5300).  This script will interrogate the
     38 #                   server on this port to see if it is running. (Note: for
     39 #                   "named" nameservers, this can be overridden by the presence
     40 #                   of the file "named.port" in the server directory containing
     41 #                   the number of the query port.)
     42 #
     43 #   --taskset cpus  Use taskset to signal which cpus can be used. For example
     44 #                   cpus=fff0 means all cpus aexcept for 0, 1, 2, and 3 are
     45 #                   eligible.
     46 #
     47 #   test            Name of the test directory.
     48 #
     49 #   server          Name of the server directory.  This will be of the form
     50 #                   "nsN" or "ansN", where "N" is an integer between 1 and 8.
     51 #                   If not given, the script will start all the servers in the
     52 #                   test directory.
     53 #
     54 #   options         Alternate options for the server.
     55 #
     56 #                   NOTE: options must be specified with '-- "<option list>"',
     57 #                   for instance: start.pl . ns1 -- "-c n.conf -d 43"
     58 #
     59 #                   ALSO NOTE: this variable will be filled with the contents
     60 #                   of the first non-commented/non-blank line of args in a file
     61 #                   called "named.args" in an ns*/ subdirectory. Only the FIRST
     62 #                   non-commented/non-blank line is used (everything else in
     63 #                   the file is ignored). If "options" is already set, then
     64 #                   "named.args" is ignored.
     65 
     66 my $usage = "usage: $0 [--noclean] [--restart] [--port <port>] [--taskset <cpus>] test-directory [server-directory [server-options]]";
     67 my $clean = 1;
     68 my $restart = 0;
     69 my $queryport = 5300;
     70 my $taskset = "";
     71 
     72 GetOptions(
     73 	'clean!'    => \$clean,
     74 	'restart!'  => \$restart,
     75 	'port=i'    => \$queryport,
     76 	'taskset=s' => \$taskset,
     77 ) or die "$usage\n";
     78 
     79 my( $test, $server_arg, $options_arg ) = @ARGV;
     80 
     81 if (!$test) {
     82 	die "$usage\n";
     83 }
     84 
     85 # Global variables
     86 my $builddir = $ENV{'builddir'};
     87 my $srcdir = $ENV{'srcdir'};
     88 my $testdir = "$builddir/$test";
     89 
     90 if (! -d $testdir) {
     91 	die "No test directory: \"$testdir\"\n";
     92 }
     93 
     94 if ($server_arg && ! -d "$testdir/$server_arg") {
     95 	die "No server directory: \"$testdir/$server_arg\"\n";
     96 }
     97 
     98 my $NAMED = $ENV{'NAMED'};
     99 my $DIG = $ENV{'DIG'};
    100 my $PERL = $ENV{'PERL'};
    101 my $PYTHON = $ENV{'PYTHON'};
    102 
    103 # Start the server(s)
    104 
    105 my @ns;
    106 my @ans;
    107 
    108 if ($server_arg) {
    109 	if ($server_arg =~ /^ns/) {
    110 		push(@ns, $server_arg);
    111 	} elsif ($server_arg =~ /^ans/) {
    112 		push(@ans, $server_arg);
    113 	} else {
    114 		print "$0: ns or ans directory expected";
    115 		print "I:$test:failed";
    116 	}
    117 } else {
    118 	# Determine which servers need to be started for this test.
    119 	opendir DIR, $testdir or die "unable to read test directory: \"$test\" ($OS_ERROR)\n";
    120 	my @files = sort readdir DIR;
    121 	closedir DIR;
    122 
    123 	@ns = grep /^ns[0-9]*$/, @files;
    124 	@ans = grep /^ans[0-9]*$/, @files;
    125 }
    126 
    127 # Start the servers we found.
    128 
    129 foreach my $name(@ns) {
    130 	my $instances_so_far = count_running_lines($name);
    131 	&check_ns_port($name);
    132 	&start_ns_server($name, $options_arg);
    133 	&verify_ns_server($name, $instances_so_far);
    134 }
    135 
    136 foreach my $name(@ans) {
    137 	&start_ans_server($name);
    138 }
    139 
    140 # Subroutines
    141 
    142 sub read_ns_port {
    143 	my ( $server ) = @_;
    144 	my $port = $queryport;
    145 	my $options = "";
    146 
    147 	if ($server) {
    148 		my $file = $testdir . "/" . $server . "/named.port";
    149 
    150 		if (-e $file) {
    151 			open(my $fh, "<", $file) or die "unable to read ports file \"$file\" ($OS_ERROR)";
    152 
    153 			my $line = <$fh>;
    154 
    155 			if ($line) {
    156 				chomp $line;
    157 				$port = $line;
    158 			}
    159 		}
    160 	}
    161 	return ($port);
    162 }
    163 
    164 sub check_ns_port {
    165 	my ( $server ) = @_;
    166 	my $options = "";
    167 	my $port = read_ns_port($server);
    168 
    169 	if ($server =~ /(\d+)$/) {
    170 		$options = "-i $1";
    171 	}
    172 
    173 	my $tries = 0;
    174 
    175 	while (1) {
    176 		my $return = system("$PERL $srcdir/testsock.pl -p $port $options");
    177 
    178 		if ($return == 0) {
    179 			last;
    180 		}
    181 
    182 		$tries++;
    183 
    184 		if ($tries > 4) {
    185 			print "$0: could not bind to server addresses, still running?\n";
    186 			print "I:$test:server sockets not available\n";
    187 			print "I:$test:failed\n";
    188 
    189 			system("$PERL $srcdir/stop.pl $test"); # Is this the correct behavior?
    190 
    191 			exit 1;
    192 		}
    193 
    194 		print "I:$test:Couldn't bind to socket (yet)\n";
    195 		sleep 2;
    196 	}
    197 }
    198 
    199 sub start_server {
    200 	my ( $server, $command, $pid_file ) = @_;
    201 
    202 	chdir "$testdir/$server" or die "unable to chdir \"$testdir/$server\" ($OS_ERROR)\n";
    203 
    204 	# start the server
    205 	my $child = `$command`;
    206 	chomp($child);
    207 
    208 	# wait up to 90 seconds for the server to start and to write the
    209 	# pid file otherwise kill this server and any others that have
    210 	# already been started
    211 	my $tries = 0;
    212 	while (!-s $pid_file) {
    213 		if (++$tries > 900) {
    214 			print "I:$test:Couldn't start server $command (pid=$child)\n";
    215 			print "I:$test:failed\n";
    216 			kill "ABRT", $child if ("$child" ne "");
    217 			chdir "$testdir";
    218 			system "$PERL $srcdir/stop.pl $test";
    219 			exit 1;
    220 		}
    221 		sleep 0.1;
    222 	}
    223 
    224 	# go back to the top level directory
    225 	chdir $builddir;
    226 }
    227 
    228 sub construct_ns_command {
    229 	my ( $server, $options ) = @_;
    230 
    231 	my $command;
    232 
    233 	if ($taskset) {
    234 		$command = "taskset $taskset $NAMED ";
    235 	} elsif ($ENV{'USE_RR'}) {
    236 		$ENV{'_RR_TRACE_DIR'} = ".";
    237 		$command = "$ENV{'TOP_BUILDDIR'}/libtool --mode=execute rr record --chaos $NAMED ";
    238 	} else {
    239 		$command = "$NAMED ";
    240 	}
    241 
    242 	my $args_file = $testdir . "/" . $server . "/" . "named.args";
    243 
    244 	if ($options) {
    245 		$command .= $options;
    246 	} elsif (-e $args_file) {
    247 		open(my $fh, "<", $args_file) or die "unable to read args_file \"$args_file\" ($OS_ERROR)\n";
    248 
    249 		while(my $line=<$fh>) {
    250 			next if ($line =~ /^\s*$/); #discard blank lines
    251 			next if ($line =~ /^\s*#/); #discard comment lines
    252 
    253 			chomp $line;
    254 
    255 			$line =~ s/#.*$//;
    256 
    257 			$command .= $line;
    258 
    259 			last;
    260 		}
    261 	} else {
    262 		$command .= "-D $test-$server ";
    263 		$command .= "-m record ";
    264 
    265 		foreach my $t_option(
    266 			"dropedns", "ednsformerr", "ednsnotimp", "ednsrefused",
    267 			"cookiealwaysvalid", "noaa", "noedns", "nosoa",
    268 			"maxudp512", "maxudp1460",
    269 		    ) {
    270 			if (-e "$testdir/$server/named.$t_option") {
    271 				$command .= "-T $t_option "
    272 			}
    273 		}
    274 
    275 		$command .= "-c named.conf -d 99 -g -T maxcachesize=2097152";
    276 	}
    277 
    278 	if (-e "$testdir/$server/named.notcp") {
    279 		$command .= " -T notcp"
    280 	}
    281 
    282 	if ($restart) {
    283 		$command .= " >>named.run 2>&1 &";
    284 	} else {
    285 		$command .= " >named.run 2>&1 &";
    286 	}
    287 
    288 	# get the shell to report the pid of the server ($!)
    289 	$command .= " echo \$!";
    290 
    291 	return $command;
    292 }
    293 
    294 sub start_ns_server {
    295 	my ( $server, $options ) = @_;
    296 
    297 	my $cleanup_files;
    298 	my $command;
    299 	my $pid_file;
    300 
    301 	$cleanup_files = "{./*.jnl,./*.bk,./*.st,./named.run}";
    302 
    303 	$command = construct_ns_command($server, $options);
    304 
    305 	$pid_file = "named.pid";
    306 
    307 	if ($clean) {
    308 		unlink glob $cleanup_files;
    309 	}
    310 
    311 	start_server($server, $command, $pid_file);
    312 }
    313 
    314 sub construct_ans_command {
    315 	my ( $server, $options ) = @_;
    316 
    317 	my $command;
    318 	my $n;
    319 
    320 	if ($server =~ /^ans(\d+)/) {
    321 		$n = $1;
    322 	} else {
    323 		die "unable to parse server number from name \"$server\"\n";
    324 	}
    325 
    326 	if (-e "$testdir/$server/ans.py") {
    327 		$ENV{'PYTHONPATH'} = $testdir . ":" . $builddir;
    328 		$command = "$PYTHON -u ans.py 10.53.0.$n $queryport";
    329 	} elsif (-e "$testdir/$server/ans.pl") {
    330 		$command = "$PERL ans.pl";
    331 	} else {
    332 		$command = "$PERL $srcdir/ans.pl 10.53.0.$n";
    333 	}
    334 
    335 	if ($options) {
    336 		$command .= $options;
    337 	}
    338 
    339 	if ($restart) {
    340 		$command .= " >>ans.run 2>&1 &";
    341 	} else {
    342 		$command .= " >ans.run 2>&1 &";
    343 	}
    344 
    345 	# get the shell to report the pid of the server ($!)
    346 	$command .= " echo \$!";
    347 
    348 	return $command;
    349 }
    350 
    351 sub start_ans_server {
    352 	my ( $server, $options ) = @_;
    353 
    354 	my $cleanup_files;
    355 	my $command;
    356 	my $pid_file;
    357 
    358 	$cleanup_files = "{./ans.run}";
    359 	$command = construct_ans_command($server, $options);
    360 	$pid_file = "ans.pid";
    361 
    362 	if ($clean) {
    363 		unlink glob $cleanup_files;
    364 	}
    365 
    366 	start_server($server, $command, $pid_file);
    367 }
    368 
    369 sub count_running_lines {
    370 	my ( $server ) = @_;
    371 
    372 	my $runfile = "$testdir/$server/named.run";
    373 
    374 	# the shell *ought* to have created the file immediately, but this
    375 	# logic allows the creation to be delayed without issues
    376 	if (open(my $fh, "<", $runfile)) {
    377 		# the two non-whitespace blobs should be the date and time
    378 		# but we don't care about them really, only that they are there
    379 		return scalar(grep /^\S+ \S+ running\R/, <$fh>);
    380 	} else {
    381 		return 0;
    382 	}
    383 }
    384 
    385 sub verify_ns_server {
    386 	my ( $server, $instances_so_far ) = @_;
    387 
    388 	my $tries = 0;
    389 
    390 	while (count_running_lines($server) < $instances_so_far + 1) {
    391 		$tries++;
    392 
    393 		if ($tries >= 30) {
    394 			print "I:$test:server $server seems to have not started\n";
    395 			print "I:$test:failed\n";
    396 
    397 			system("$PERL $srcdir/stop.pl $test");
    398 
    399 			exit 1;
    400 		}
    401 
    402 		sleep 2;
    403 	}
    404 
    405 	$tries = 0;
    406 
    407 	my $port = read_ns_port($server);
    408 	my $tcp = "+tcp";
    409 	my $n;
    410 
    411 	if ($server =~ /^ns(\d+)/) {
    412 		$n = $1;
    413 	} else {
    414 		die "unable to parse server number from name \"$server\"\n";
    415 	}
    416 
    417 	if (-e "$testdir/$server/named.notcp") {
    418 		$tcp = "";
    419 	}
    420 
    421 	my $ip = "10.53.0.$n";
    422 	if (-e "$testdir/$server/named.ipv6-only") {
    423 		$ip = "fd92:7065:b8e:ffff::$n";
    424 	}
    425 
    426 	while (1) {
    427 		my $return = system("$DIG $tcp +noadd +nosea +nostat +noquest +nocomm +nocmd +noedns -p $port version.bind. chaos txt \@$ip > /dev/null");
    428 
    429 		last if ($return == 0);
    430 
    431 		$tries++;
    432 
    433 		if ($tries >= 30) {
    434 			print "I:$test:no response from $server\n";
    435 			print "I:$test:failed\n";
    436 
    437 			system("$PERL $srcdir/stop.pl $test");
    438 
    439 			exit 1;
    440 		}
    441 
    442 		sleep 2;
    443 	}
    444 }
    445