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 stopping test servers
     15 # Based on the type of server specified, signal the server to stop, wait
     16 # briefly for it to die, and then kill it if it is still alive.
     17 # If a server is specified, stop it. Otherwise, stop 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 
     26 # Usage:
     27 #   perl stop.pl [--use-rndc [--port port]] test [server]
     28 #
     29 #   --use-rndc      Attempt to stop the server via the "rndc stop" command.
     30 #
     31 #   --port port     Only relevant if --use-rndc is specified, this sets the
     32 #                   command port over which the attempt should be made.  If
     33 #                   not specified, port 9953 is used.
     34 #
     35 #   test            Name of the test directory.
     36 #
     37 #   server          Name of the server directory.
     38 
     39 my $usage = "usage: $0 [--use-rndc [--halt] [--port port]] test-directory [server-directory]";
     40 
     41 my $use_rndc = 0;
     42 my $halt = 0;
     43 my $rndc_port = 9953;
     44 my $errors = 0;
     45 
     46 GetOptions(
     47 	'use-rndc!' => \$use_rndc,
     48 	'halt!' => \$halt,
     49 	'port=i' => \$rndc_port
     50     ) or die "$usage\n";
     51 
     52 my ( $test, $server_arg ) = @ARGV;
     53 
     54 if (!$test) {
     55 	die "$usage\n";
     56 }
     57 
     58 # Global variables
     59 my $builddir = $ENV{'builddir'};
     60 my $srcdir = $ENV{'srcdir'};
     61 my $testdir = "$builddir/$test";
     62 
     63 if (! -d $testdir) {
     64 	die "No test directory: \"$testdir\"\n";
     65 }
     66 
     67 if ($server_arg && ! -d "$testdir/$server_arg") {
     68 	die "No server directory: \"$testdir/$server_arg\"\n";
     69 }
     70 
     71 my $RNDC = $ENV{RNDC};
     72 
     73 my @ns;
     74 my @ans;
     75 
     76 if ($server_arg) {
     77 	if ($server_arg =~ /^ns/) {
     78 		push(@ns, $server_arg);
     79 	} elsif ($server_arg =~ /^ans/) {
     80 		push(@ans, $server_arg);
     81 	} else {
     82 		print "$0: ns or ans directory expected";
     83 		print "I:$test:failed";
     84 	}
     85 } else {
     86 	# Determine which servers need to be stopped for this test.
     87 	opendir DIR, $testdir or die "unable to read test directory: \"$test\" ($OS_ERROR)\n";
     88 	my @files = sort readdir DIR;
     89 	closedir DIR;
     90 
     91 	@ns = grep /^ns[0-9]*$/, @files;
     92 	@ans = grep /^ans[0-9]*$/, @files;
     93 }
     94 
     95 # Stop the server(s), pass 1: rndc.
     96 if ($use_rndc) {
     97 	foreach my $name(@ns) {
     98 		stop_rndc($name, $rndc_port);
     99 	}
    100 
    101 	@ns = wait_for_servers(120, @ns);
    102 }
    103 
    104 # Pass 2: SIGTERM
    105 foreach my $name (@ns) {
    106 	stop_signal($name, "TERM");
    107 }
    108 
    109 @ns = wait_for_servers(300, @ns);
    110 
    111 foreach my $name(@ans) {
    112 	stop_signal($name, "TERM", 1);
    113 }
    114 
    115 @ans = wait_for_servers(300, @ans);
    116 
    117 # Pass 3: SIGABRT
    118 foreach my $name (@ns) {
    119 	print "I:$test:$name didn't die when sent a SIGTERM\n";
    120 	stop_signal($name, "ABRT");
    121 	$errors = 1;
    122 }
    123 foreach my $name (@ans) {
    124 	print "I:$test:$name didn't die when sent a SIGTERM\n";
    125 	stop_signal($name, "ABRT", 1);
    126 	$errors = 1;
    127 }
    128 
    129 exit($errors);
    130 
    131 # Subroutines
    132 
    133 # Return the full path to a given server's PID file.
    134 sub server_pid_file {
    135 	my ( $server ) = @_;
    136 
    137 	return $testdir . "/" . $server . "/named.pid" if ($server =~ /^ns/);
    138 	return $testdir . "/" . $server . "/ans.pid" if ($server =~ /^ans/);
    139 
    140 	die "Unknown server type $server\n";
    141 }
    142 
    143 # Read a PID.
    144 sub read_pid {
    145 	my ( $pid_file ) = @_;
    146 
    147 	return unless -f $pid_file;
    148 	# we don't really care about the race condition here
    149 	my $result = open(my $fh, "<", $pid_file);
    150 	if (!defined($result)) {
    151 		print "I:$test:$pid_file: $!\n";
    152 		unlink $pid_file;
    153 		return;
    154 	}
    155 
    156 	my $pid = <$fh>;
    157 	return unless defined($pid);
    158 
    159 	chomp($pid);
    160 	return $pid;
    161 }
    162 
    163 # Stop a named process with rndc.
    164 sub stop_rndc {
    165 	my ( $server, $port ) = @_;
    166 	my $n;
    167 
    168 	if ($server =~ /^ns(\d+)/) {
    169 		$n = $1;
    170 	} else {
    171 		die "unable to parse server number from name \"$server\"\n";
    172 	}
    173 
    174 	my $ip = "10.53.0.$n";
    175 	if (-e "$testdir/$server/named.ipv6-only") {
    176 		$ip = "fd92:7065:b8e:ffff::$n";
    177 	}
    178 
    179 	my $how = $halt ? "halt" : "stop";
    180 
    181 	# Ugly, but should work.
    182 	system("$RNDC -c ../_common/rndc.conf -s $ip -p $port $how | sed 's/^/I:$test:$server /'");
    183 	return;
    184 }
    185 
    186 sub server_died {
    187 	my ( $server, $signal ) = @_;
    188 
    189 	print "I:$test:$server died before a SIG$signal was sent\n";
    190 	$errors = 1;
    191 
    192 	my $pid_file = server_pid_file($server);
    193 	unlink($pid_file);
    194 
    195 	return;
    196 }
    197 
    198 sub send_signal {
    199 	my ( $signal, $pid, $ans ) = @_;
    200 
    201 	if (! defined $ans) {
    202 		$ans = 0;
    203 	}
    204 
    205 	my $result = 0;
    206 
    207 	$result = kill $signal, $pid;
    208 	return $result;
    209 }
    210 
    211 # Stop a server by sending a signal to it.
    212 sub stop_signal {
    213 	my ( $server, $signal, $ans ) = @_;
    214 	if (! defined $ans) {
    215 		$ans = 0;
    216 	}
    217 
    218 	my $pid_file = server_pid_file($server);
    219 	my $pid = read_pid($pid_file);
    220 
    221 	return unless defined($pid);
    222 
    223 	# Send signal to the server, and bail out if signal can't be sent
    224 	if (send_signal($signal, $pid, $ans) != 1) {
    225 		server_died($server, $signal);
    226 		return;
    227 	}
    228 
    229 	return;
    230 }
    231 
    232 sub pid_file_exists {
    233 	my ( $server ) = @_;
    234 
    235 	my $pid_file = server_pid_file($server);
    236 	my $pid = read_pid($pid_file);
    237 
    238 	return unless defined($pid);
    239 
    240 	# If we're here, the PID file hasn't been cleaned up yet
    241 	if (send_signal(0, $pid) == 0) {
    242 		print "I:$test:$server crashed on shutdown\n";
    243 		$errors = 1;
    244 		return;
    245 	}
    246 
    247 	return $server;
    248 }
    249 
    250 sub wait_for_servers {
    251 	my ( $timeout, @servers ) = @_;
    252 
    253 	while ($timeout > 0 && @servers > 0) {
    254 		sleep 1 if (@servers > 0);
    255 		@servers =
    256 			grep { defined($_) }
    257 			map  { pid_file_exists($_) } @servers;
    258 		$timeout--;
    259 	}
    260 
    261 	return @servers;
    262 }
    263