[pve-devel] r4830 - in pve-manager/pve2: bin bin/init.d lib/PVE

svn-commits at proxmox.com svn-commits at proxmox.com
Tue Jun 22 13:37:05 CEST 2010


Author: dietmar
Date: 2010-06-22 11:37:05 +0000 (Tue, 22 Jun 2010)
New Revision: 4830

Added:
   pve-manager/pve2/lib/PVE/API2Client.pm
   pve-manager/pve2/lib/PVE/APIDaemon.pm
Modified:
   pve-manager/pve2/bin/init.d/pvedaemon
   pve-manager/pve2/bin/pvedaemon
   pve-manager/pve2/lib/PVE/Makefile.am
Log:
started pvedaemon rewrite


Modified: pve-manager/pve2/bin/init.d/pvedaemon
===================================================================
--- pve-manager/pve2/bin/init.d/pvedaemon	2010-06-22 06:13:04 UTC (rev 4829)
+++ pve-manager/pve2/bin/init.d/pvedaemon	2010-06-22 11:37:05 UTC (rev 4830)
@@ -2,8 +2,8 @@
 
 ### BEGIN INIT INFO
 # Provides:        pvedaemon
-# Required-Start:  $network $syslog
-# Required-Stop:   $network $syslog
+# Required-Start:  $remote_fs $network $syslog
+# Required-Stop:   $remote_fs $network $syslog
 # Default-Start:   2 3 4 5
 # Default-Stop:    0 1 6
 # Short-Description: Start PVE Daemon
@@ -23,7 +23,7 @@
 case "$1" in
 	start)
 		log_daemon_msg "Starting PVE daemon" "pvedaemon"
-  		start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -p $PIDFILE
+  		start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
 		log_end_msg $?
   		;;
 	stop)
@@ -36,7 +36,7 @@
 		if ( [ -e $PIDFILE ] && kill -0 `cat $PIDFILE`) then
 		    start-stop-daemon --stop --signal HUP --pidfile $PIDFILE
 		else
-		    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -p $PIDFILE
+		    start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
 		fi
 		log_end_msg $?
   		;;		
@@ -44,7 +44,7 @@
 		log_daemon_msg "Restarting PVE daemon" "pvedaemon"
   		start-stop-daemon --stop --quiet --retry TERM/2/TERM/10/KILL --pidfile $PIDFILE
                 sleep 2
-  		start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- -p $PIDFILE
+  		start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON
 		log_end_msg $?
   		;;
 	*)

Modified: pve-manager/pve2/bin/pvedaemon
===================================================================
--- pve-manager/pve2/bin/pvedaemon	2010-06-22 06:13:04 UTC (rev 4829)
+++ pve-manager/pve2/bin/pvedaemon	2010-06-22 11:37:05 UTC (rev 4830)
@@ -1,28 +1,25 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl -T -w
 
+$ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
+
+delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};  
+
 use strict;
 use Getopt::Long;
 use POSIX ":sys_wait_h";
-use SOAP::Transport::HTTP;
-use PVE::Config;
-use PVE::ConfigServer;
+use Socket;
+use PVE::Utils;
 use PVE::SafeSyslog;
+# use PVE::Config; # fixme
+use PVE::APIDaemon;
 
-my $opt_pidfile;
+my $pidfile = "/var/run/pvedaemon.pid";
 my $opt_debug;
 
-# restrict access exported methods
-my $filterdata = PVE::ConfigServer::filter_data();
-
-my @methods = keys %{$filterdata->{soap_exports}};
-
 initlog ('pvedaemon', 'daemon');
 
-$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
-
-if (!GetOptions ('pidfile=s' => \$opt_pidfile,
-		 'debug' => \$opt_debug)) {
-    die "usage: $0 [pidfile=s]\n";
+if (!GetOptions ('debug' => \$opt_debug)) {
+    die "usage: $0 [--debug]\n";
 }
 
 $SIG{'__WARN__'} = sub {
@@ -36,15 +33,14 @@
 $0 = "pvedaemon";
 
 my $cpid;
-
-my ($soaphost, $soapport) = PVE::Config::soap_host_port();
-
+my $daemon;
 eval {
-    $pve_config_daemon = PVE::SOAPTransport
-	-> new (LocalAddr => $soaphost, LocalPort => $soapport, 
-		ReuseAddr => 1, Proto => 'tcp',
-		serializer => PVE::SOAPSerializer->new)
-	-> dispatch_to(map {"PVE::ConfigServer::$_"} @methods);
+    $daemon = PVE::APIDaemon->new(
+	LocalAddr => "127.0.0.1",
+        LocalPort => 85,
+        Listen => SOMAXCONN,
+        ReuseAddr => 1,
+	);
 };
 
 my $err = $@;
@@ -65,7 +61,7 @@
 
 	$SIG{INT} = 'DEFAULT';
 
-	unlink "$opt_pidfile" if ($opt_pidfile);
+	unlink "$pidfile";
 
 	exit (0);
     };
@@ -86,7 +82,7 @@
     system ("echo > /var/lib/pve-manager/vmops"); # init vmops file
 
     eval {
-	$pve_config_daemon->handle;
+	$daemon->start_server();
     };
     my $err = $@;
 
@@ -98,13 +94,11 @@
 
 } else {
 
-    if ($opt_pidfile) {
-	open (PIDFILE, ">$opt_pidfile") || 
-	    die "cant write '$opt_pidfile' - $! :ERROR";
-	print PIDFILE "$cpid\n";
-	close (PIDFILE) || 
-	    die "cant write '$opt_pidfile' - $! :ERROR";
-    }
+    open (PIDFILE, ">$pidfile") || 
+	die "cant write '$pidfile' - $! :ERROR";
+    print PIDFILE "$cpid\n";
+    close (PIDFILE) || 
+	die "cant write '$pidfile' - $! :ERROR";
 }
 
 exit (0);
@@ -117,28 +111,12 @@
 
 =head1 SYNOPSIS
 
-pvedaemon [--pid <PIDFILE>] [--debug]
+pvedaemon [--debug]
 
 =head1 DESCRIPTION
 
-All configuration is done using this SOAP Server. The Server only
-listens to a local address 127.0.0.1 port 83 for security
-reasons. Simple tunnel this address if you want to do remote
-administration - for example: 
+All configuration is done using this Server. The Server only
+listens to a local address 127.0.0.1 port 85 for security
+reasons.
 
-    ssh -N -L 10000:localhost:83 <SERVER_IP>
 
-The server can do HTTP basic authentication, and uses ticket
-authentication if your SOAP client supports cookies. An example
-can be found in:
-
-    /usr/share/doc/pve-manager/examples/testauth.pl
-
-NOTE: Do not open access to this server for untrusted 
-users (local system users are considered trusted - at least we 
-assume they normally do not start DOS attacks).
-
- 
-
-
-

Added: pve-manager/pve2/lib/PVE/API2Client.pm
===================================================================
--- pve-manager/pve2/lib/PVE/API2Client.pm	                        (rev 0)
+++ pve-manager/pve2/lib/PVE/API2Client.pm	2010-06-22 11:37:05 UTC (rev 4830)
@@ -0,0 +1,98 @@
+package PVE::API2Client;
+
+use strict;
+use warnings;
+use URI;
+use HTTP::Cookies;
+use LWP::UserAgent;
+use JSON;
+use PVE::API2;
+
+sub BEGIN {
+    no strict 'refs'; 
+    #print "making accessors\n";
+
+    foreach my $member (qw (test)) {
+
+	*{$member} = sub { 
+	    my ($self, %param) = @_;
+	
+	    #print "wrapper called\n";
+       
+	    my $func = PVE::API2->can($member) ||
+		die "no such method";
+
+	    my $method = 'POST';
+
+	    $method = "GET" if PVE::API2->check_attribute($member, "GET");
+
+	    my $uri = URI->new("http://$self->{config}->{host}/api2/$member");
+
+	    if ($method eq 'GET') {
+		$uri->query_form(\%param);
+	    }
+
+	    #print "CALL : " .  $uri->as_string() . "\n";
+	    #print "PATH : " .  $uri->path() . "\n";
+
+	    my $ua = $self->{useragent};
+
+	    my $response;
+	    if ($method eq 'GET') {
+		$response = $ua->get($uri);
+	    } elsif ($method eq 'POST') {
+		$response = $ua->post($uri, \%param);
+	    } else {
+		die "method $method not implemented\n";
+	    }
+
+	    if ($response->is_success) {
+		my $ct = $response->header('Content-Type');
+
+		die "got unexpected content type" if $ct ne 'application/json';
+
+		my $data = decode_json($response->decoded_content);
+
+		die $data->{error} if $data->{error};
+
+		return $data;
+
+	    } else {
+		die $response->status_line . "\n";
+	    }
+
+	}; 
+    }
+}
+
+sub new {
+    my ($class, %param) = @_;
+
+    my $self = { 
+	config => {
+	    ticket => $param{ticket},
+	    username => $param{username},
+	    password => $param{password},
+	    host => $param{host} || 'localhost',
+	    port => $param{port} || 85,
+	    timeout => $param{timeout} || 60,
+	},
+    };
+    bless $self;
+
+    $self->{cookie_jar} = HTTP::Cookies->new (ignore_discard => 1);
+
+    if ($self->{config}->{ticket}) {
+	 $self->{cookie_jar}->set_cookie(0, 'PVEAuthTicket', $self->{config}->{ticket},  
+					 '/', $self->{config}->{host});
+    }
+
+    $self->{useragent} = LWP::UserAgent->new(
+	timeout => $self->{config}->{timeout},
+	cookie_jar => $self->{cookie_jar},
+	);
+  
+    return $self;
+}
+
+1;


Property changes on: pve-manager/pve2/lib/PVE/API2Client.pm
___________________________________________________________________
Added: svn:executable
   + *

Added: pve-manager/pve2/lib/PVE/APIDaemon.pm
===================================================================
--- pve-manager/pve2/lib/PVE/APIDaemon.pm	                        (rev 0)
+++ pve-manager/pve2/lib/PVE/APIDaemon.pm	2010-06-22 11:37:05 UTC (rev 4830)
@@ -0,0 +1,335 @@
+package PVE::APIDaemon;
+
+use strict;
+use warnings;
+use vars qw(@ISA);
+use MIME::Base64;
+#use Sys::Syslog;
+use PVE::SafeSyslog;
+use PVE::Config;
+use POSIX qw(EINTR);
+use POSIX ":sys_wait_h";
+use IO::Handle;
+use IO::Select;
+use vars qw(@ISA);
+use HTTP::Daemon;
+use HTTP::Status qw(:constants);
+use HTTP::Request::Params; # fixme: remove
+use Data::Dumper; # fixme: remove
+use URI;
+use JSON;
+use PVE::API2;
+ 
+# This is a quite simple pre-fork server
+
+ at ISA = qw(HTTP::Daemon);
+
+my $documentroot = "/usr/share/pve-api/root";
+
+my $workers = {};
+
+my $max_workers = 3;    # pre-forked worker processes
+my $max_requests = 500; # max requests per worker
+my $child_terminate = 0;
+my $child_reload_config = 0;
+
+sub worker_finished {
+    my $cpid = shift;
+
+    syslog ('info', "worker $cpid finished");
+}
+    
+sub finish_workers {
+    local $!; local $?;    
+    foreach my $cpid (keys %$workers) {
+        my $waitpid = waitpid ($cpid, WNOHANG);
+        if (defined($waitpid) && ($waitpid == $cpid)) {
+            delete ($workers->{$cpid});
+	    worker_finished ($cpid);
+	}
+    }
+}
+
+sub test_workers {
+    foreach my $cpid (keys %$workers) {
+	if (!kill(0, $cpid)) {
+	    waitpid($cpid, POSIX::WNOHANG());
+	    delete $workers->{$cpid};
+	    worker_finished ($cpid);
+	}
+    }
+}
+
+sub start_workers {
+    my $self = shift;
+
+    my $count = 0;
+    foreach my $cpid (keys %$workers) {
+	$count++;
+    }
+
+    my $need = $max_workers - $count;
+
+    return if $need <= 0;
+
+    syslog ('info', "starting $need worker(s)");
+
+    while ($need > 0) {
+	my $pid = fork;
+
+	if (!defined ($pid)) {
+	    syslog ('err', "can't fork worker");
+	    sleep (1);
+	} elsif ($pid) { #parent
+	    $workers->{$pid} = 1;
+	    $0 = 'pvedaemon worker';
+	    syslog ('info', "worker $pid started");
+	    $need--;
+	} else {
+	    $SIG{TERM} = $SIG{QUIT} = sub {
+		$child_terminate = 1;
+	    };
+
+	    $SIG{USR1} = sub {
+		$child_reload_config = 1;
+	    };
+
+	    eval {
+		# try to init inotify
+		PVE::Config::inotify_init();
+
+	        $self->handle_requests ();
+	    };
+	    syslog ('err', $@) if $@;
+	  
+	    exit (0);
+	}
+    }
+}
+
+sub terminate_server {
+
+    syslog('info', "received terminate request");
+
+    foreach my $cpid (keys %$workers) {
+	kill (15, $cpid); # TERM childs
+    }
+
+    # nicely shutdown childs (give them max 10 seconds to shut down)
+    my $previous_alarm = alarm (10);
+    eval {
+	local $SIG{ALRM} = sub { die "Timed Out!\n" };
+	
+	while ((my $pid = waitpid (-1, 0)) > 0) {
+	    if (defined($workers->{$pid})) {
+		delete ($workers->{$pid});
+		worker_finished ($pid);
+	    }
+	}
+
+    };
+    alarm ($previous_alarm);
+
+    foreach my $cpid (keys %$workers) {
+	# KILL childs still alive!
+	if (kill (0, $cpid)) {
+	    delete ($workers->{$cpid});
+	    syslog("err", "kill worker $cpid");
+	    kill (9, $cpid);
+	}
+    }
+
+}
+
+sub new {
+    my $class = shift;
+
+    my $self = $class->SUPER::new(@_) || 
+	die "unable to create socket - $@\n";
+
+    return $self;
+}
+
+sub start_server {
+    my $self = shift;
+
+    eval {
+	my $old_sig_chld = $SIG{CHLD};
+	local $SIG{CHLD} = sub {
+	    finish_workers ();
+	    &$old_sig_chld(@_) if $old_sig_chld;
+	};
+
+	my $old_sig_term = $SIG{TERM};
+	local $SIG{TERM} = sub { 
+	    terminate_server ();
+	    &$old_sig_term(@_) if $old_sig_term;
+	};
+	local $SIG{QUIT} = sub { 
+	    terminate_server();
+	    &$old_sig_term(@_) if $old_sig_term;
+	};
+
+	local $SIG{USR1} = 'IGNORE';
+
+	local $SIG{HUP} = sub {
+	    syslog ("info", "received reload request");
+	    foreach my $cpid (keys %$workers) {
+		kill (10, $cpid); # SIGUSR1 childs
+	    }
+	};
+
+	for (;;) { # forever
+	    $self->start_workers ();
+	    sleep (5); 
+	    $self->test_workers ();
+	}
+    };
+    my $err = $@;
+
+    if ($err) {
+	syslog ('err', "ERROR: $err");
+    }
+}
+
+sub send_basic_auth_request {
+    my ($c) = @_;
+    
+    my $realm = 'PVE API Daemon';
+    my $auth_request_res = HTTP::Response->new(401, 'Unauthorized');
+    $auth_request_res->header('WWW-Authenticate' => qq{Basic realm="$realm"});
+    $auth_request_res->is_error(1);
+    $auth_request_res->error_as_HTML(1);
+    $c->send_response($auth_request_res);
+}
+
+sub send_error {
+    my ($c, $code, $msg) = @_;
+
+    $c->send_response(HTTP::Response->new($code, $msg));
+}
+
+sub handle_login {
+    my ($daemon, $c, $r) = @_;
+
+    # my $cuser = ident_user ($c->peerport, $c->sockport);
+
+    my $h =  $r->headers;
+
+    print "Head: " . Dumper($h) . "\n";
+}
+
+sub handle_requests {
+    my $self = shift;
+
+    my $rcount = 0;
+
+    my $sel = IO::Select->new();
+    $sel->add ($self);
+
+    my $timeout = 5;
+    my @ready;
+    while (1) {
+	if (scalar (@ready = $sel->can_read($timeout))) {
+
+	    my $c;
+	    while (($c = $self->accept) || ($! == EINTR)) {
+		next if !$c; # EINTR
+
+		if ($child_reload_config) {
+		    $child_reload_config = 0;
+		    syslog('info', "child reload config");
+		    # fixme: anything to do here?
+		}
+
+		$c->timeout(5);
+
+		# fixme: limit max request length somehow
+
+		# handle requests 
+		while (my $r = $c->get_request) {
+		    print "Request: " .  $r->as_string() . "\nEND REQUEST\n";
+		    #print "Method: " .  $r->method() . "\n";
+		    #print "CALL: " .  $r->uri->as_string() . "\n";
+		    #print "PATH: " .  $r->uri->path() . "\n";
+		    #print "Content: " . $r->content() . "\n";
+			
+		    my $method =  $r->method();
+
+		    syslog('info', "$method: " . $r->uri->as_string());
+
+		    my $headers = $r->headers;
+
+		    my $ct = $headers->header('Content-Type');
+
+		    my $parser = HTTP::Request::Params->new({req => $r});
+		    my $params = $parser->params;
+		
+		    #print "params: " .  Dumper($params) . "\n";
+
+		    if ($method eq 'POST' || $method eq 'GET') {
+
+			my $path =  $r->uri->path();
+			print "PATH: $path\n";
+			
+			if ($path =~ m|^/api2/(\w+)$|) {
+
+			    my $rpcmethod = $1;
+
+			    my $data;
+			    eval {
+
+				my $serv = bless {}, 'PVE::API2';
+
+				die "no such method\n" if !$serv->check_attribute($rpcmethod, "Public");
+				
+				my $func = PVE::API2->can($rpcmethod) || die "no such method\n";
+
+				$data = &$func($serv, $params);
+
+			    };
+			    my $error = $@;
+
+			    my $json = encode_json({ result => $data, error => $error || undef});
+
+			    my $response = HTTP::Response->new(200);
+			    my $x = length ($json);
+			    $response->header("Content-Type" => 'application/json');
+			    $response->header("Content-Length" => $x);
+			    $response->header("Pragma", "no-cache");
+			    $response->content($json);
+		       
+			    #print "RESPONSE: " . $response->as_string . "\n";
+			
+			    $c->send_response($response);
+
+			} else {
+			    $c->send_error(HTTP_FORBIDDEN);
+			} 
+
+		    } else {
+			$c->send_error(HTTP_METHOD_NOT_ALLOWED);
+		    }
+		}
+		$rcount++;
+
+		# we only handle one request per connection, because
+		# we want to minimize the number of connections
+
+		$c->shutdown(2);
+		$c->close();
+		last;
+	    }
+
+	    last if $child_terminate || !$c || ($rcount >= $max_requests);
+
+	} else {
+	    last if $child_terminate;
+
+	    # timeout
+	    PVE::Config::poll(); # read inotify events
+	}
+    }
+}
+
+1;


Property changes on: pve-manager/pve2/lib/PVE/APIDaemon.pm
___________________________________________________________________
Added: svn:executable
   + *

Modified: pve-manager/pve2/lib/PVE/Makefile.am
===================================================================
--- pve-manager/pve2/lib/PVE/Makefile.am	2010-06-22 06:13:04 UTC (rev 4829)
+++ pve-manager/pve2/lib/PVE/Makefile.am	2010-06-22 11:37:05 UTC (rev 4830)
@@ -5,6 +5,8 @@
 PERLSOURCE = 			\
 	JSONSchema.pm		\
 	API2.pm			\
+	API2Client.pm		\
+	APIDaemon.pm		\
 	REST.pm			\
 	RESTHandler.pm		\
 	SourceFilter.pm		\



More information about the pve-devel mailing list