]> git.proxmox.com Git - pve-manager.git/blob - PVE/APIDaemon.pm
041b7ed2be262496b38de9afd996c5bdb8a008d6
[pve-manager.git] / PVE / APIDaemon.pm
1 package PVE::APIDaemon;
2
3 use strict;
4 use warnings;
5 use POSIX ":sys_wait_h";
6 use Socket qw(IPPROTO_TCP TCP_NODELAY SOMAXCONN);
7 use IO::Socket::INET;
8 use Net::IP;
9
10 use PVE::SafeSyslog;
11 use PVE::HTTPServer;
12
13 my $workers = {};
14
15 sub new {
16 my ($this, %args) = @_;
17
18 my $class = ref($this) || $this;
19
20 die "no lockfile" if !$args{lockfile};
21
22 my $lockfh = IO::File->new(">>$args{lockfile}") ||
23 die "unable to open lock file '$args{lockfile}' - $!\n";
24
25 my $socket = IO::Socket::INET->new(
26 LocalAddr => $args{host} || undef,
27 LocalPort => $args{port} || 80,
28 Listen => SOMAXCONN,
29 Proto => 'tcp',
30 ReuseAddr => 1) ||
31 die "unable to create socket - $@\n";
32
33 # we ofter observe delays when using Nagle algorithm,
34 # so we disable that to maximize performance
35 setsockopt($socket, IPPROTO_TCP, TCP_NODELAY, 1);
36
37 my $cfg = { %args };
38 my $self = bless { cfg => $cfg }, $class;
39
40 $cfg->{socket} = $socket;
41 $cfg->{lockfh} = $lockfh;
42 $cfg->{max_workers} = 3 if !$cfg->{max_workers};
43 $cfg->{trusted_env} = 0 if !defined($cfg->{trusted_env});
44
45 return $self;
46 }
47
48 sub worker_finished {
49 my $cpid = shift;
50
51 syslog('info', "worker $cpid finished");
52 }
53
54 sub finish_workers {
55 local $!; local $?;
56 foreach my $cpid (keys %$workers) {
57 my $waitpid = waitpid ($cpid, WNOHANG);
58 if (defined($waitpid) && ($waitpid == $cpid)) {
59 delete ($workers->{$cpid});
60 worker_finished ($cpid);
61 }
62 }
63 }
64
65 sub test_workers {
66 foreach my $cpid (keys %$workers) {
67 if (!kill(0, $cpid)) {
68 waitpid($cpid, POSIX::WNOHANG());
69 delete $workers->{$cpid};
70 worker_finished ($cpid);
71 }
72 }
73 }
74
75 sub start_workers {
76 my ($self) = @_;
77
78 my $count = 0;
79 foreach my $cpid (keys %$workers) {
80 $count++;
81 }
82
83 my $need = $self->{cfg}->{max_workers} - $count;
84
85 return if $need <= 0;
86
87 syslog('info', "starting $need worker(s)");
88
89 while ($need > 0) {
90 my $pid = fork;
91
92 if (!defined ($pid)) {
93 syslog('err', "can't fork worker");
94 sleep (1);
95 } elsif ($pid) { #parent
96 $workers->{$pid} = 1;
97 syslog('info', "worker $pid started");
98 $need--;
99 } else {
100 $0 = "$0 worker";
101
102 $SIG{TERM} = $SIG{QUIT} = 'DEFAULT'; # we handle that with AnyEvent
103
104 eval {
105 my $server = PVE::HTTPServer->new(%{$self->{cfg}});
106 $server->run();
107 };
108 if (my $err = $@) {
109 syslog('err', $err);
110 sleep(5); # avoid fast restarts
111 }
112 exit (0);
113 }
114 }
115 }
116
117 sub terminate_server {
118
119 syslog('info', "received terminate request");
120
121 foreach my $cpid (keys %$workers) {
122 kill (15, $cpid); # TERM childs
123 }
124
125 # nicely shutdown childs (give them max 10 seconds to shut down)
126 my $previous_alarm = alarm (10);
127 eval {
128 local $SIG{ALRM} = sub { die "timeout\n" };
129
130 while ((my $pid = waitpid (-1, 0)) > 0) {
131 if (defined($workers->{$pid})) {
132 delete ($workers->{$pid});
133 worker_finished ($pid);
134 }
135 }
136 alarm(0); # avoid race condition
137 };
138 my $err = $@;
139
140 alarm ($previous_alarm);
141
142 if ($err) {
143 syslog('err', "error stopping workers (will kill them now) - $err");
144 foreach my $cpid (keys %$workers) {
145 # KILL childs still alive!
146 if (kill (0, $cpid)) {
147 delete ($workers->{$cpid});
148 syslog("err", "kill worker $cpid");
149 kill (9, $cpid);
150 }
151 }
152 }
153 }
154
155 sub start_server {
156 my $self = shift;
157
158 eval {
159 my $old_sig_chld = $SIG{CHLD};
160 local $SIG{CHLD} = sub {
161 finish_workers ();
162 &$old_sig_chld(@_) if $old_sig_chld;
163 };
164
165 my $old_sig_term = $SIG{TERM};
166 local $SIG{TERM} = sub {
167 terminate_server ();
168 &$old_sig_term(@_) if $old_sig_term;
169 };
170 local $SIG{QUIT} = sub {
171 terminate_server();
172 &$old_sig_term(@_) if $old_sig_term;
173 };
174
175 local $SIG{HUP} = sub {
176 syslog("info", "received reload request");
177 foreach my $cpid (keys %$workers) {
178 kill (15, $cpid); # kill childs
179 }
180 };
181
182 for (;;) { # forever
183 $self->start_workers();
184 sleep (5);
185 $self->test_workers();
186 }
187 };
188 my $err = $@;
189
190 if ($err) {
191 syslog('err', "ERROR: $err");
192 }
193 }
194
195 sub read_proxy_config {
196
197 my $conffile = "/etc/default/pveproxy";
198
199 # Note: evaluate with bash
200 my $shcmd = ". $conffile;\n";
201 $shcmd .= 'echo \"ALLOW_FROM:\$ALLOW_FROM\";';
202 $shcmd .= 'echo \"DENY_FROM:\$DENY_FROM\";';
203 $shcmd .= 'echo \"POLICY:\$POLICY\";';
204 $shcmd .= 'echo \"CIPHERS:\$CIPHERS\";';
205
206 my $data = -f $conffile ? `bash -c "$shcmd"` : '';
207
208 my $res = {};
209
210 while ($data =~ s/^(.*)\n//) {
211 my ($key, $value) = split(/:/, $1, 2);
212 next if !$value;
213 if ($key eq 'ALLOW_FROM' || $key eq 'DENY_FROM') {
214 my $ips = [];
215 foreach my $ip (split(/,/, $value)) {
216 $ip = "0/0" if $ip eq 'all';
217 push @$ips, Net::IP->new($ip) || die Net::IP::Error() . "\n";
218 }
219 $res->{$key} = $ips;
220 } elsif ($key eq 'POLICY') {
221 die "unknown policy '$value'\n" if $value !~ m/^(allow|deny)$/;
222 $res->{$key} = $value;
223 } elsif ($key eq 'CIPHERS') {
224 $res->{$key} = $value;
225 } else {
226 # silently skip everythin else?
227 }
228 }
229
230 return $res;
231 }
232
233 1;