App-FuguVM

 view release on metacpan or  search on metacpan

lib/App/FuguVM/Autoinstall.pm  view on Meta::CPAN

# $self->port:
#	Return the recorded port, or undef.
sub port ($self)
{
	return $self->{store}->get('autoinstall_port');
}

# $self->is_running:
#	Report whether the child is alive. The check reaps first, so a
#	child that became a zombie reads as stopped.
sub is_running ($self)
{
	return $self->{pidfile}->is_running ? 1 : 0;
}

# $self->guest_url:
#	Return the response-file URL as the guest reaches it, through
#	the QEMU gateway. The gateway routes the request to the
#	loopback listener of the host, so no port forward is needed.
sub guest_url ($self)
{
	my $port = $self->port;
	return if !defined $port;

	return
	      'http://'
	    . App::FuguVM::Proxy::HOST_GATEWAY()
	    . ":$port"
	    . RESPONSE_PATH;
}

# $self->start:
#	Take a free port, spawn the child, record the PID and the
#	port, and wait until the port answers. Return the port, or
#	undef with the reason in error(). A responder that already
#	runs returns its port and starts nothing.
sub start ($self)
{
	$self->{error} = undef;

	return $self->port if $self->is_running;

	if ( !defined $self->{file} || !-r $self->{file} ) {
		$self->{error} = 'the response file is not readable: '
		    . ( $self->path // '(none)' );
		return;
	}

	my $port = $self->_find_free_port;
	unless ( defined $port ) {
		$self->{error} = sprintf 'no free port in %d-%d', @{ +PORTS };
		return;
	}

	my $child  = ref $self;
	my $result = Fugu::Process->spawn_perl(
		code      => "use $child; $child->run_child(\@ARGV)",
		args      => [ $port, $self->{file}, $self->{proxy_url} // '' ],
		daemonize => 1,
		stdout    => $self->{logfile},
		stderr    => $self->{logfile},
	);
	unless ( $result->{success} ) {
		$self->{error} = "cannot start the responder: $result->{error}";
		return;
	}

	$self->{pidfile}->write_pid( $result->{pid} );
	$self->{store}->set( autoinstall_port => $port );

	unless ( $self->_wait_ready ) {
		$self->{error} = 'the responder did not take connections';
		$self->stop;
		return;
	}

	return $port;
}

# $self->stop:
#	Stop the child and forget the port. The method returns 1.
sub stop ($self)
{
	my $pid = $self->{pidfile}->read_pid;
	Fugu::Process->terminate( $pid, grace_period => 5 )
	    if defined $pid;

	$self->{pidfile}->remove;
	$self->{store}->delete('autoinstall_port');

	return 1;
}

# $class->run_child($port, $file, $proxy_url):
#	The entry point of the spawned child. The child reads the
#	response file, renders it, and serves it until a SIGTERM. It
#	logs each request line, and it never logs the file content.
sub run_child ( $class, $port, $file, $proxy_url )
{
	my $log = Fugu::Log->new( mode => 'stderr', level => 'debug' );

	my $bytes = Fugu::File->read($file);
	die "Cannot read the response file: $file\n" if !defined $bytes;

	my $body = $class->render( $bytes, $proxy_url );

	my $listener = IO::Socket::INET->new(
		LocalAddr => BIND_ADDRESS,
		LocalPort => $port,
		Proto     => 'tcp',
		ReuseAddr => 1,
		Listen    => 5,
	) or die 'Cannot listen on ' . BIND_ADDRESS . ":$port: $!\n";

	$log->info( 'Responder listening on %s:%d', BIND_ADDRESS, $port );

	# A client can disconnect in the middle of an answer
	local $SIG{PIPE} = 'IGNORE';

	# The self-pipe makes the signal safe: the handler writes one
	# byte, and the select loop notices it between requests instead
	# of inside one.
	require IO::Select;
	pipe my $sig_read, my $sig_write or die "pipe: $!";
	$sig_read->blocking(0);
	$sig_write->blocking(0);

	my $running = 1;
	local $SIG{TERM} = sub {
		$running = 0;
		syswrite $sig_write, 'x', 1;
	};

	my $select = IO::Select->new( $listener, $sig_read );

	while ($running) {
		my @ready = $select->can_read;
		last if !$running;

		for my $fh (@ready) {
			if ( $fh == $sig_read ) {
				sysread $sig_read, my $drain, 100;
				next;
			}

			my $client = $listener->accept or next;
			_answer( $client, $body, $log );
			close $client;
		}
	}

	$log->info('Responder shutting down');
	close $sig_read;
	close $sig_write;
	close $listener;

	return 1;
}

# $class->render($bytes, $proxy_url):



( run in 0.749 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )