]> git.proxmox.com Git - pve-manager.git/blob - PVE/APIDaemon.pm
removed automake/autoconf, removed unused files
[pve-manager.git] / PVE / APIDaemon.pm
1 package PVE::APIDaemon;
2
3 use strict;
4 use warnings;
5 use vars qw(@ISA);
6 use PVE::SafeSyslog;
7 use PVE::INotify;
8 use PVE::RPCEnvironment;
9
10 use POSIX qw(EINTR);
11 use POSIX ":sys_wait_h";
12 use IO::Handle;
13 use IO::Select;
14 use HTTP::Daemon;
15 use HTTP::Status qw(:constants);
16 use CGI;
17 use Data::Dumper; # fixme: remove
18 use PVE::REST;
19 use JSON;
20
21 # This is a quite simple pre-fork server - only listens to local port
22
23 @ISA = qw(HTTP::Daemon);
24
25 my $documentroot = "/usr/share/pve-api/root";
26
27 my $workers = {};
28
29 my $max_workers = 3; # pre-forked worker processes
30 my $max_requests = 500; # max requests per worker
31
32
33 # some global vars
34 my $child_terminate = 0;
35 my $child_reload_config = 0;
36
37 sub worker_finished {
38 my $cpid = shift;
39
40 syslog ('info', "worker $cpid finished");
41 }
42
43 sub finish_workers {
44 local $!; local $?;
45 foreach my $cpid (keys %$workers) {
46 my $waitpid = waitpid ($cpid, WNOHANG);
47 if (defined($waitpid) && ($waitpid == $cpid)) {
48 delete ($workers->{$cpid});
49 worker_finished ($cpid);
50 }
51 }
52 }
53
54 sub test_workers {
55 foreach my $cpid (keys %$workers) {
56 if (!kill(0, $cpid)) {
57 waitpid($cpid, POSIX::WNOHANG());
58 delete $workers->{$cpid};
59 worker_finished ($cpid);
60 }
61 }
62 }
63
64 sub start_workers {
65 my ($self, $rpcenv) = @_;
66
67 my $count = 0;
68 foreach my $cpid (keys %$workers) {
69 $count++;
70 }
71
72 my $need = $max_workers - $count;
73
74 return if $need <= 0;
75
76 syslog ('info', "starting $need worker(s)");
77
78 while ($need > 0) {
79 my $pid = fork;
80
81 if (!defined ($pid)) {
82 syslog ('err', "can't fork worker");
83 sleep (1);
84 } elsif ($pid) { #parent
85 $workers->{$pid} = 1;
86 $0 = 'pvedaemon worker';
87 syslog ('info', "worker $pid started");
88 $need--;
89 } else {
90 $SIG{TERM} = $SIG{QUIT} = sub {
91 $child_terminate = 1;
92 };
93
94 $SIG{USR1} = sub {
95 $child_reload_config = 1;
96 };
97
98 eval {
99 # try to init inotify
100 PVE::INotify::inotify_init();
101
102 $self->handle_requests($rpcenv);
103 };
104 syslog ('err', $@) if $@;
105
106 exit (0);
107 }
108 }
109 }
110
111 sub terminate_server {
112
113 syslog('info', "received terminate request");
114
115 foreach my $cpid (keys %$workers) {
116 kill (15, $cpid); # TERM childs
117 }
118
119 # nicely shutdown childs (give them max 10 seconds to shut down)
120 my $previous_alarm = alarm (10);
121 eval {
122 local $SIG{ALRM} = sub { die "Timed Out!\n" };
123
124 while ((my $pid = waitpid (-1, 0)) > 0) {
125 if (defined($workers->{$pid})) {
126 delete ($workers->{$pid});
127 worker_finished ($pid);
128 }
129 }
130
131 };
132 alarm ($previous_alarm);
133
134 foreach my $cpid (keys %$workers) {
135 # KILL childs still alive!
136 if (kill (0, $cpid)) {
137 delete ($workers->{$cpid});
138 syslog("err", "kill worker $cpid");
139 kill (9, $cpid);
140 }
141 }
142
143 }
144
145 sub new {
146 my $class = shift;
147
148 my $self = $class->SUPER::new(@_) ||
149 die "unable to create socket - $@\n";
150
151 return $self;
152 }
153
154 sub start_server {
155 my $self = shift;
156
157 my $atfork = sub { close($self); };
158 my $rpcenv = PVE::RPCEnvironment->init('priv', atfork => $atfork);
159
160 eval {
161 my $old_sig_chld = $SIG{CHLD};
162 local $SIG{CHLD} = sub {
163 finish_workers ();
164 &$old_sig_chld(@_) if $old_sig_chld;
165 };
166
167 my $old_sig_term = $SIG{TERM};
168 local $SIG{TERM} = sub {
169 terminate_server ();
170 &$old_sig_term(@_) if $old_sig_term;
171 };
172 local $SIG{QUIT} = sub {
173 terminate_server();
174 &$old_sig_term(@_) if $old_sig_term;
175 };
176
177 local $SIG{USR1} = 'IGNORE';
178
179 local $SIG{HUP} = sub {
180 syslog ("info", "received reload request");
181 foreach my $cpid (keys %$workers) {
182 kill (10, $cpid); # SIGUSR1 childs
183 }
184 };
185
186 for (;;) { # forever
187 $self->start_workers ($rpcenv);
188 sleep (5);
189 $self->test_workers ();
190 }
191 };
192 my $err = $@;
193
194 if ($err) {
195 syslog ('err', "ERROR: $err");
196 }
197 }
198
199 sub send_error {
200 my ($c, $code, $msg) = @_;
201
202 $c->send_response(HTTP::Response->new($code, $msg));
203 }
204
205 my $known_methods = {
206 GET => 1,
207 POST => 1,
208 PUT => 1,
209 DELETE => 1,
210 };
211
212 my $extract_params = sub {
213 my ($r, $method) = @_;
214
215 # NOTE: HTTP::Request::Params return undef instead of ''
216 #my $parser = HTTP::Request::Params->new({req => $r});
217 #my $params = $parser->params;
218
219 my $post_params = {};
220
221 if ($method eq 'PUT' || $method eq 'POST') {
222 $post_params = CGI->new($r->content())->Vars;
223 }
224
225 my $query_params = CGI->new($r->url->query)->Vars;
226
227 my $params = $post_params || {};
228
229 foreach my $k (keys %{$query_params}) {
230 $params->{$k} = $query_params->{$k};
231 }
232
233 return $params;
234 };
235
236 sub handle_requests {
237 my ($self, $rpcenv) = @_;
238
239 my $rcount = 0;
240
241 my $sel = IO::Select->new();
242 $sel->add ($self);
243
244 my $timeout = 5;
245 my @ready;
246 while (1) {
247 if (scalar (@ready = $sel->can_read($timeout))) {
248
249 my $c;
250 while (($c = $self->accept) || ($! == EINTR && !$child_terminate)) {
251 next if !$c; # EINTR
252
253 if ($child_reload_config) {
254 $child_reload_config = 0;
255 syslog('info', "child reload config");
256 # fixme: anything to do here?
257 }
258
259 $c->timeout(5);
260
261 # fixme: limit max request length somehow
262
263 # handle requests
264 while (my $r = $c->get_request) {
265
266 my $method = $r->method();
267
268 syslog('info', "perl method $method");
269
270 if (!$known_methods->{$method}) {
271 $c->send_error(HTTP_NOT_IMPLEMENTED);
272 last;
273 }
274
275 my $uri = $r->uri->path();
276 syslog('info', "start $method $uri");
277
278 my ($rel_uri, $format) = PVE::REST::split_abs_uri($uri);
279 if (!$format) {
280
281 $c->send_error(HTTP_NOT_IMPLEMENTED);
282
283 } else {
284
285 my $headers = $r->headers;
286
287 my $cookie = $headers->header('Cookie');
288
289 my $ticket = PVE::REST::extract_auth_cookie($cookie);
290
291 my $params = &$extract_params($r, $method);
292
293 my $clientip = $headers->header('PVEClientIP');
294
295 my $res = PVE::REST::rest_handler($clientip, $method, $uri, $rel_uri,
296 $ticket, undef, $params);
297
298 if ($res->{proxy}) {
299
300 $res->{status} = 500;
301 $c->send_error($res->{status}, "proxy not allowed");
302
303 } else {
304
305 PVE::REST::prepare_response_data($format, $res);
306 my ($raw, $ct) = PVE::REST::format_response_data($format, $res, $uri);
307
308 my $response = HTTP::Response->new($res->{status}, $res->{message});
309 $response->header("Content-Type" => $ct);
310 $response->header("Pragma", "no-cache");
311
312 if ($res->{ticket}) {
313 my $cookie = PVE::REST::create_auth_cookie($res->{ticket});
314 $response->header("Set-Cookie" => $cookie);
315 }
316 $response->content($raw);
317
318 $c->send_response($response);
319 }
320
321 syslog('info', "end $method $uri ($res->{status})");
322 }
323 }
324 $rcount++;
325
326 # we only handle one request per connection, because
327 # we want to minimize the number of connections
328
329 $c->shutdown(2);
330 $c->close();
331 last;
332 }
333
334 last if $child_terminate || !$c || ($rcount >= $max_requests);
335
336 } else {
337 last if $child_terminate;
338
339 # timeout
340 PVE::INotify::poll(); # read inotify events
341 }
342 }
343 }
344
345 1;