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