summaryrefslogtreecommitdiffstats
path: root/t/030_errors.t
blob: 9d64785de34e76d8d5d9e20ae02d56135a81889c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# Check all kinds of error conditions.

use strict; 

require File::Temp;

use lib 't';
use TestLib;
use Test::More tests => 154;
use PgCommon;

my $version = $MAJORS[-1];

my $socketdir = '/tmp/postgresql-testsuite/';
my ($pg_uid, $pg_gid) = (getpwnam 'postgres')[2,3];

# create a pid file with content $1 and return its path
sub create_pidfile {
    my $fname = "/var/lib/postgresql/$version/main/postmaster.pid";
    open F, ">$fname" or die "open: $!";
    print F $_[0];
    close F;
    chown $pg_uid, $pg_gid, $fname or die "chown: $!";
    chmod 0700, $fname or die "chmod: $!";
    return $fname;
}

sub check_nonexisting_cluster_error {
    my $outref;
    my $result = exec_as 0, $_[0], $outref;
    is $result, 1, "'$_[0]' fails";
    like $$outref, qr/(invalid version|does not exist)/i, "$_[0] gives error message about nonexisting cluster";
    unlike $$outref, qr/invalid symbolic link/i, "$_[0] does not print 'invalid symbolic link' gibberish";
}

# check if pg_lsclusters shows a cluster without binaries
mkdir "/etc/postgresql/6.3";
mkdir "/etc/postgresql/6.3/main";
open F, ">/etc/postgresql/6.3/main/postgresql.conf";
close F;
is `pg_lsclusters -h`, "6.3 main <unknown> down,binaries_missing <unknown> <unknown> <unknown>\n",
    'pg_lscluster reports cluster without binaries';
program_ok 0, "pg_dropcluster 6.3 main";

# create cluster
ok ((system "pg_createcluster --socketdir '$socketdir' $version main >/dev/null") == 0,
    "pg_createcluster --socketdir");
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/$version\s*main.*5432.*down/, 'cluster was created';

is ((get_cluster_port $version, 'main'), 5432, 'Port of created cluster is 5432');

# creating cluster with the same name should fail
like_program_out 'root', "pg_createcluster --socketdir '$socketdir' $version main", 1, qr/already exists/,
    "pg_createcluster on existing cluster";
# and the original one still exists
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/$version\s*main.*5432.*down/, 'original cluster still exists';

# attempt to create clusters with an invalid port
like_program_out 0, "pg_createcluster $version test -p foo", 1,
    qr/invalid.*number expected/,
    'pg_createcluster -p checks that port option is numeric';
like_program_out 0, "pg_createcluster $version test -p 42", 1,
    qr/must be a positive integer between/,
    'pg_createcluster -p checks valid port range';

# chown cluster to an invalid user to test error
(system "chown -R 0 /var/lib/postgresql/$version/main") == 0 or die "chown failed: $!";
like_program_out 0, "pg_ctlcluster $version main start", 1, qr/must not be owned by root/,
    "pg_ctlcluster refuses to start root-owned cluster";
my $badid = 98;
(system "chown -R $badid /var/lib/postgresql/$version/main") == 0 or die "chown failed: $!";
like_program_out 0, "pg_ctlcluster $version main start", 1, qr/owned by user id 98 which does not exist/,
    'pg_ctlcluster fails on invalid cluster owner uid';
(system "chown -R postgres:$badid /var/lib/postgresql/$version/main") == 0 or die "chown failed: $!";
like_program_out 0, "pg_ctlcluster $version main start", 1, qr/owned by group id 98 which does not exist/,
    'pg_ctlcluster as root fails on invalid cluster owner gid';
like_program_out 'postgres', "pg_ctlcluster $version main start", 1, qr/owned by group id 98 which does not exist/,
    'pg_ctlcluster as postgres fails on invalid cluster owner gid';
(system "chown -R postgres:postgres /var/lib/postgresql/$version/main") == 0 or die "chown failed: $!";
program_ok 0, "pg_ctlcluster $version main start", 0,
    'pg_ctlcluster succeeds on valid cluster owner uid/gid';

# check socket
my @contents = ('.s.PGSQL.5432', '.s.PGSQL.5432.lock', "$version-main.pid", "$version-main.pg_stat_tmp");
pop @contents if ($version < 8.4); # remove pg_stat_tmp
ok_dir '/var/run/postgresql', [grep {/main/} @contents], 'No sockets in /var/run/postgresql';
ok_dir $socketdir, ['.s.PGSQL.5432', '.s.PGSQL.5432.lock'], "Socket is in $socketdir";

# stop cluster, check sockets
ok ((system "pg_ctlcluster $version main stop") == 0,
    'cluster stops with custom unix_socket_dir');
ok_dir $socketdir, [], "No sockets in $socketdir after stopping cluster";

# remove default socket dir and check that the socket defaults to
# /var/run/postgresql
open F, "+</etc/postgresql/$version/main/postgresql.conf" or
    die "could not open postgresql.conf for r/w: $!";
my @lines = <F>;
seek F, 0, 0 or die "seek: $!";
truncate F, 0;
@lines = grep !/^unix_socket_dir/, @lines; # <= 9.2: "_directory", >= 9.3: "_directories"
print F @lines;
close F;

ok ((system "pg_ctlcluster $version main start") == 0,
    'cluster starts after removing unix_socket_dir');
if ($PgCommon::rpm) {
    ok ((grep { $_ eq '.s.PGSQL.5432' } @{TestLib::dircontent('/tmp')}) == 1, 'Socket is in /tmp');
} else {
    ok_dir '/var/run/postgresql', [@contents],
        'Socket is in default dir /var/run/postgresql';
}
ok_dir $socketdir, [], "No sockets in $socketdir";

# server should not stop with corrupt file
rename "/var/lib/postgresql/$version/main/postmaster.pid",
    "/var/lib/postgresql/$version/main/postmaster.pid.orig" or die "rename: $!";
create_pidfile 'foo';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 1, 
    "Error: pid file is invalid, please manually kill the stale server process.\n",
    'pg_ctlcluster fails with corrupted PID file';
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/online/, 'cluster is still online';

# restore PID file
(system "cp /var/lib/postgresql/$version/main/postmaster.pid.orig /var/lib/postgresql/$version/main/postmaster.pid") == 0 or die "cp: $!";
is ((exec_as 'postgres', "pg_ctlcluster $version main stop"), 0, 
    'pg_ctlcluster succeeds with restored PID file');
mkdir $PgCommon::binroot . "foo"; # #940220: infinite recursion in get_program_path
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/down/, 'cluster is down';
rmdir $PgCommon::binroot . "foo";

# stop stopped server
is_program_out 'postgres', "pg_ctlcluster $version main stop", 2,
    "Cluster is not running.\n", 'pg_ctlcluster stop fails on stopped cluster';

# simulate crashed server
rename "/var/lib/postgresql/$version/main/postmaster.pid.orig",
    "/var/lib/postgresql/$version/main/postmaster.pid" or die "rename: $!";
is_program_out 'postgres', "pg_ctlcluster $version main start", 0, 
    "Removed stale pid file.\n", 'pg_ctlcluster succeeds with already existing PID file';
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/online/, 'cluster is online';
is ((exec_as 'postgres', "pg_ctlcluster $version main stop"), 0, 
    'pg_ctlcluster stop succeeds');
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/down/, 'cluster is down';
ok (! -e "/var/lib/postgresql/$version/main/postmaster.pid", 'no pid file left');

# trying to stop a stopped server cleans up corrupt and stale pid files
my $pf = create_pidfile 'foo';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 2,
    "Removed stale pid file.\nCluster is not running.\n", 
    'pg_ctlcluster stop succeeds with corrupted PID file';
ok (! -e $pf, 'pid file was cleaned up');

create_pidfile 'foo';
is_program_out 'postgres', "pg_ctlcluster --force $version main stop", 2,
    "Removed stale pid file.\nCluster is not running.\n", 
    'pg_ctlcluster --force stop succeeds with corrupted PID file';
ok (! -e $pf, 'pid file was cleaned up');

create_pidfile '99998';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 2,
    "Removed stale pid file.\nCluster is not running.\n", 
    'pg_ctlcluster stop succeeds with stale PID file';
ok (! -e $pf, 'pid file was cleaned up');

create_pidfile '99998';
is_program_out 'postgres', "pg_ctlcluster --force $version main stop", 2,
    "Removed stale pid file.\nCluster is not running.\n", 
    'pg_ctlcluster --force stop succeeds with stale PID file';
ok (! -e $pf, 'pid file was cleaned up');

create_pidfile '';
is_program_out 'postgres', "pg_ctlcluster --force $version main stop", 2,
    "Removed stale pid file.\nCluster is not running.\n", 
    'pg_ctlcluster stop succeeds with empty PID file';
ok (! -e $pf, 'pid file was cleaned up');

# corrupt PID file while server is down
create_pidfile 'foo';
is_program_out 'postgres', "pg_ctlcluster $version main start", 0,
    "Removed stale pid file.\n", 'pg_ctlcluster succeeds with corrupted PID file';
like_program_out 'postgres', 'pg_lsclusters -h', 0, qr/online/, 'cluster is online';

# start running server
is_program_out 'postgres', "pg_ctlcluster $version main start", 2,
    "Cluster is already running.\n", 'pg_ctlcluster start fails on running cluster';
is ((exec_as 'postgres', "pg_ctlcluster $version main stop"), 0, 'pg_ctlcluster stop');

# backup pg_hba.conf
rename "/etc/postgresql/$version/main/pg_hba.conf",
    "/etc/postgresql/$version/main/pg_hba.conf.orig" or die "rename: $!";

# test check for invalid pg_hba.conf
open F, ">/etc/postgresql/$version/main/pg_hba.conf" or die "could not create pg_hba.conf: $!";
print F "foo\n";
close F;
chmod 0644, "/etc/postgresql/$version/main/pg_hba.conf" or die "chmod: $!";

if ($version < '8.4') {
    like_program_out 'postgres', "pg_ctlcluster $version main start", 0, 
	qr/WARNING.*connection to the database failed.*pg_hba.conf/is,
	'pg_ctlcluster start warns about invalid pg_hba.conf';
    is_program_out 'postgres', "pg_ctlcluster $version main stop", 0, '', 'stopping cluster';
} else {
    like_program_out 'postgres', "pg_ctlcluster $version main start", 1, 
	qr/FATAL.*pg_hba.conf/is,
	'pg_ctlcluster start fails on invalid pg_hba.conf';
    is_program_out 'postgres', "pg_ctlcluster $version main stop", 2, 
	"Cluster is not running.\n", 'stopping cluster';
}

# test check for pg_hba.conf with removed passwordless local superuser access
open F, ">/etc/postgresql/$version/main/pg_hba.conf" or die "could not create pg_hba.conf: $!";
print F "local all all md5\n";
close F;
chmod 0644, "/etc/postgresql/$version/main/pg_hba.conf" or die "chmod: $!";

like_program_out 'postgres', "pg_ctlcluster $version main start", 0,
    qr/Warning.*connection to the database failed.*(no password supplied|password authentication failed)/is,
    'pg_ctlcluster start warns about absence of passwordless superuser connection';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 0, '', 'stopping cluster';

# restore pg_hba.conf
unlink "/etc/postgresql/$version/main/pg_hba.conf";
rename "/etc/postgresql/$version/main/pg_hba.conf.orig",
    "/etc/postgresql/$version/main/pg_hba.conf" or die "rename: $!";

# leftover files must not create confusion
open F, '>/etc/postgresql/postgresql.conf';
print F "data_directory = '/nonexisting'\n";
close F;
my @c = get_version_clusters $version;
is_deeply (\@c, ['main'], 
   'leftover /etc/postgresql/postgresql.conf is not regarded as a cluster');
unlink '/etc/postgresql/postgresql.conf';

# fails by default due to access restrictions
# remove cluster and directory; this should work as user "postgres"
is_program_out 'postgres', "pg_dropcluster $version main", 0, '',
    , "pg_dropcluster works as user postgres";

# graceful handling of absent data dir (might not be mounted)
ok ((system "pg_createcluster $version main >/dev/null") == 0,
    "pg_createcluster succeeds");
rename "/var/lib/postgresql/$version", "/var/lib/postgresql/$version.orig" or die "rename: $!";
my $outref;
is ((exec_as 0, "pg_ctlcluster $version main start", $outref, 1), 1,
    'pg_ctlcluster fails on nonexisting /var/lib/postgresql');
like $$outref, qr/^Error:.*\/var\/lib\/postgresql.*not accessible.*$/, 'proper error message for nonexisting /var/lib/postgresql';

rename "/var/lib/postgresql/$version.orig", "/var/lib/postgresql/$version" or die "rename: $!";
is_program_out 'postgres', "pg_ctlcluster $version main start", 0, '',
    'pg_ctlcluster start succeeds again with reappeared /var/lib/postgresql';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 0, '', 'stopping cluster';

# pg_ctlcluster checks colliding ports
ok ((system "pg_createcluster $version other >/dev/null") == 0,
    "pg_createcluster other");
set_cluster_port $version, 'other', '5432';
is ((exec_as 'postgres', "pg_ctlcluster $version main start"), 0,
    'pg_ctlcluster: main cluster on conflicting port starts');

# clusters can run side by side on different socket directories
set_cluster_socketdir $version, 'other', $socketdir;
PgCommon::set_conf_value $version, 'other', 'postgresql.conf',
    'listen_addresses', ''; # otherwise they will conflict on TCP socket
is ((exec_as 'postgres', "pg_ctlcluster $version other start"), 0,
    'pg_ctlcluster: other cluster starts on conflicting port, but different socket dirs');
is ((exec_as 'postgres', "pg_ctlcluster $version other stop"), 0);

# ... but will give an error when running on the same port
set_cluster_socketdir $version, 'other', ($PgCommon::rpm and $version < 9.4) ? '/tmp' : '/var/run/postgresql';
like_program_out 'postgres', "pg_ctlcluster $version other start", 1,
    qr/Port conflict:.*port 5432/,
    'pg_ctlcluster other cluster fails on conflicting port and same socket dir';
is_program_out 'postgres', "pg_ctlcluster $version main stop", 0, '', 
    'stopping main cluster';
is ((exec_as 'postgres', "pg_ctlcluster $version other start"), 0,
    'pg_ctlcluster: other cluster on conflicting port starts after main is down');
ok ((system "pg_dropcluster $version other --stop") == 0, 
    'pg_dropcluster other');

# clean up
ok ((system "pg_dropcluster $version main") == 0, 
    'pg_dropcluster');
ok_dir $socketdir, [], 'No sockets any more';
rmdir $socketdir or die "rmdir: $!";

# ensure sane error messages for nonexisting clusters
check_nonexisting_cluster_error 'pg_lsclusters 4.5 foo';
check_nonexisting_cluster_error 'psql --cluster 4.5/foo';
check_nonexisting_cluster_error "psql --cluster $MAJORS[0]/foo";
check_nonexisting_cluster_error "pg_dropcluster 4.5 foo";
check_nonexisting_cluster_error "pg_dropcluster $MAJORS[0] foo";
check_nonexisting_cluster_error "pg_upgradecluster 4.5 foo";
check_nonexisting_cluster_error "pg_upgradecluster $MAJORS[0] foo";
check_nonexisting_cluster_error "pg_ctlcluster 4.5 foo stop";
check_nonexisting_cluster_error "pg_ctlcluster $MAJORS[0] foo stop";

check_clean;

# check that pg_dropcluster copes with partially existing cluster
# configurations (which can happen if the disk becomes full)

mkdir '/etc/postgresql/';
mkdir "/etc/postgresql/$MAJORS[-1]";
mkdir "/etc/postgresql/$MAJORS[-1]/broken" or die "mkdir: $!";
symlink "/var/lib/postgresql/$MAJORS[-1]/broken", "/etc/postgresql/$MAJORS[-1]/broken/pgdata" or die "symlink: $!";

unlike_program_out 0, "pg_dropcluster $MAJORS[-1] broken", 0, qr/error/i, 
    'pg_dropcluster cleans up broken cluster configuration (only /etc with pgdata)';

check_clean;

mkdir '/etc/postgresql/';
mkdir '/var/lib/postgresql/';
mkdir "/etc/postgresql/$MAJORS[-1]" and 
mkdir "/etc/postgresql/$MAJORS[-1]/broken";
mkdir "/var/lib/postgresql/$MAJORS[-1]";
mkdir "/var/lib/postgresql/$MAJORS[-1]/broken";
mkdir "/var/lib/postgresql/$MAJORS[-1]/broken/base" or die "mkdir: $!";
open F, ">/etc/postgresql/$MAJORS[-1]/broken/postgresql.conf" or die "open: $!";
print F "data_directory = '/var/lib/postgresql/$MAJORS[-1]/broken'\n";
close F;
open F, ">/var/lib/postgresql/$MAJORS[-1]/broken/PG_VERSION" or die "open: $!";
close F;

unlike_program_out 0, "pg_dropcluster $MAJORS[-1] broken", 0, qr/error/i, 
    'pg_dropcluster cleans up broken cluster configuration (/etc with pgdata and postgresql.conf and partial /var)';

check_clean;

# vim: filetype=perl