3 eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
4 if 0; # not running under some shell
7 # MogileFS storage node daemon
10 # (c) 2004, Brad Fitzpatrick, <brad@danga.com>
11 # (c) 2006-2007, Six Apart, Ltd.
15 use Mogstored
::HTTPServer
;
17 # based on where we found Mogstored::HTTPServer (a pure-perl module),
18 # add the mogdeps/ subdirectory of that base to our @INC search
19 # path, where all the misc Mogile dependencies are installed.
22 if (! $ENV{MOGILE_NO_BUILTIN_DEPS
} &&
23 ($libpath = $INC{"Mogstored/HTTPServer.pm"}) &&
24 $libpath =~ s!Mogstored/HTTPServer.pm$!!)
26 my $dep_dir = "${libpath}mogdeps";
28 unless (($ENV{PERL5LIB
} || "") =~ /$dep_dir/) {
29 $ENV{PERL5LIB
} = join(":",
30 split(/:/, $ENV{PERL5LIB
} || ""),
37 use POSIX
qw(WNOHANG);
39 use FindBin
qw($Bin $RealScript);
41 use Mogstored::HTTPServer::Perlbal;
42 use Mogstored::HTTPServer::Lighttpd;
43 use Mogstored::HTTPServer::None;
44 use Mogstored::HTTPServer::Apache;
45 use Mogstored::SideChannelListener;
46 use Mogstored::SideChannelClient;
48 my $selfexe = "$Bin/$RealScript";
51 my %on_death; # pid -> subref (to run when pid dies)
52 my %devnum_to_device; # mogile device number (eg. 'dev1' would be '1') -> os device path (eg. '/dev/rd0')
53 my %osdevnum_to_device; # os device number (fetched via stat(file)[0]) -> os device path (ec. '/dev/rd0')
54 my %iostat_listeners; # fd => SideChannel client: clients interested in iostat data.
55 my $iostat_available = 1; # bool: iostat working. assume working to start.
56 my ($iostat_pipe_r, $iostat_pipe_w); # pipes for talking to iostat process
61 my $opt_iostat = 1; # default to on now
62 my $max_conns = 10000;
63 my $http_listen = "0.0.0.0:7500";
64 my $mgmt_listen = "0.0.0.0:7501";
65 my $docroot = "/var/mogdata";
66 my $default_config = "/etc/mogilefs/mogstored.conf";
67 my $server = $ENV{MOGSTORED_SERVER_TYPE} || "perlbal";
71 # Rename binary in process list to make init scripts saner
75 'iostat' => \$opt_iostat,
76 'daemonize|d' => \$opt_daemonize,
77 'config=s' => \$opt_config,
78 'httplisten=s' => \$http_listen,
79 'mgmtlisten=s' => \$mgmt_listen,
80 'docroot=s' => \$docroot,
81 'maxconns=i' => \$max_conns,
82 'server=s' => \$server,
83 'serverbin=s' => \$serverbin,
84 'pidfile=s' => \$pidfile,
86 usage() unless Getopt::Long::GetOptions(%config_opts);
88 die "Unknown server type. Valid options: --server={perlbal,lighttpd,apache,none}"
89 unless $server =~ /^perlbal|lighttpd|apache|none$/;
91 $opt_config = $default_config if ! $opt_config && -e $default_config;
92 load_config_file($opt_config => \%config_opts) if $opt_config;
94 # initialize basic required Perlbal machinery, for any HTTP server
95 my $perlbal_init = qq{
96 CREATE SERVICE mogstored
98 SET docroot = $docroot
100 # don't listen... this is just a stub service.
102 SET role = management
105 $perlbal_init .= "\nSERVER pidfile = $pidfile" if defined($pidfile);
106 Perlbal::run_manage_commands($perlbal_init , sub { print STDERR "$_[0]\n"; });
109 my $httpsrv_class = "Mogstored::HTTPServer::" . ucfirst($server);
110 my $httpsrv = $httpsrv_class->new(
111 listen => $http_listen,
113 maxconns => $max_conns,
118 if ($opt_daemonize) {
119 $httpsrv->pre_daemonize;
120 Perlbal::daemonize();
125 $httpsrv->post_daemonize;
127 # kill our children processes on exit:
130 $SIG{TERM} = $SIG{INT} = sub {
131 return unless $$ == $parent_pid; # don't let this be inherited
132 kill 'TERM', grep { $_ } keys %on_death;
136 setup_iostat_pipes();
137 start_disk_usage_process();
138 start_iostat_process() if $opt_iostat;
139 harvest_dead_children(); # every 2 seconds, it reschedules itself
140 setup_sidechannel_listener();
142 # now start the main loop
145 ############################################################################
149 $note = $note ? "NOTE: $note\n\n" : "";
151 die "${note}Usage: mogstored [OPTS]
154 --daemonize -d Daemonize
155 --config=<file> Set config file (default is /etc/mogilefs/mogstored.conf)
156 --httplisten=<ip:port> IP/Port HTTP server listens on
157 --mgmtlisten=<ip:port> IP/Port management/sidechannel listens on
158 --docroot=<path> Docroot above device mount points. Defaults to /var/mogdata
159 --maxconns=<number> Number of simultaneous clients to serve. Default 10000
164 # accessor for SideChannelClient:
165 sub Mogstored::iostat_available {
166 return $iostat_available;
169 sub load_config_file {
170 my ($conffile, $opts) = @_;
172 # parse the mogstored config file, which is just lines of comments and
173 # "key = value" lines, where keys are just the same as command line
175 die "Config file $opt_config doesn't exist.\n" unless -e $conffile;
176 open my $fh, $conffile or die "Couldn't open config file for reading: $!";
180 if (/SERVER max_connect/i || /CREATE SERVICE/i) {
181 usage("Your $opt_config file is the old syntax. The new format is simply lines of <key> = <value> where keys are the same as mogstored's command line options.");
184 die "Unknown config syntax: $_\n" unless /^\s*(\w+)\s*=\s*(.+?)\s*$/;
185 my ($key, $val) = ($1, $2);
187 foreach my $ck (keys %$opts) {
188 next unless $ck =~ /^$key\b/;
189 $dest = $opts->{$ck};
191 die "Unknown config setting: $key\n" unless $dest;
196 sub harvest_dead_children {
197 my $dead = waitpid(-1, WNOHANG);
199 my $code = delete $on_death{$dead};
202 Danga::Socket->AddTimer(2, \&harvest_dead_children);
205 sub Mogstored::on_pid_death {
206 my ($class, $pid, $code) = @_;
207 $on_death{$pid} = $code;
210 # returns $pid of child, if parent, else runs child.
211 sub start_disk_usage_process {
213 unless (defined $child) {
214 Perlbal::log('crit', "Fork error creating disk usage tracking process");
218 # if we're the parent.
220 $on_death{$child} = sub {
221 start_disk_usage_process(); # start a new one
226 require Mogstored::ChildProcess::DiskUsage;
227 my $class = "Mogstored::ChildProcess::DiskUsage";
228 $class->pre_exec_init;
232 sub Mogstored::iostat_subscribe {
233 my ($class, $sock) = @_;
234 $iostat_listeners{fileno($sock->sock)} = $sock;
237 sub Mogstored::iostat_unsubscribe {
238 my ($class, $sock) = @_;
239 my $fdno = fileno($sock->sock);
240 return unless defined $fdno;
241 delete $iostat_listeners{$fdno};
244 # to be honest, I have no clue why this exists. I just had to move it
245 # around for multi-server refactoring, and I felt better not
246 # understanding it but preserving than killing it. in particular, why
247 # is this "graceful"? (gets called from SideChannelClient's
249 sub Mogstored::on_sidechannel_die_gracefully {
250 if ($$ == $parent_pid) {
251 kill 'TERM', grep { $_ } keys %on_death;
255 sub setup_sidechannel_listener {
256 Mogstored::SideChannelListener->new($mgmt_listen);
259 my $iostat_read_buf = "";
260 sub setup_iostat_pipes {
261 pipe ($iostat_pipe_r, $iostat_pipe_w);
262 IO::Handle::blocking($iostat_pipe_r, 0);
263 IO::Handle::blocking($iostat_pipe_w, 0);
265 Danga::Socket->AddOtherFds(fileno($iostat_pipe_r), sub {
266 read_from_iostat_child();
270 sub start_iostat_process {
272 unless (defined $pid) {
273 warn "Fork for iostat failed: $!";
279 $on_death{$pid} = sub {
280 # Try a final read from data on the socket.
281 # Note that this doesn't internally loop... so it might miss data.
282 read_from_iostat_child();
283 # Kill any buffer so partial reads don't hurt us later.
284 drain_iostat_child_pipe();
285 start_iostat_process();
290 require Mogstored::ChildProcess::IOStat;
291 my $class = "Mogstored::ChildProcess::IOStat";
292 $class->pre_exec_init;
296 sub Mogstored::get_iostat_writer_pipe { $iostat_pipe_w }
298 sub drain_iostat_child_pipe {
301 last unless sysread($iostat_pipe_r, $data, 10240) > 0;
303 $iostat_read_buf = '';
306 # (runs in parent event-loop process)
307 sub read_from_iostat_child {
309 my $rv = sysread($iostat_pipe_r, $data, 10240);
310 return unless $rv && $rv > 0;
312 $iostat_read_buf .= $data;
314 # only write complete lines to sockets (in case for some reason we get
315 # a partial read and child process dies...)
316 while ($iostat_read_buf =~ s/(.+)\r?\n//) {
318 foreach my $out_sock (values %iostat_listeners) {
319 # where $line will be like "dev53\t53.23" or a "." to signal end of a group of devices.
320 # stop writing to the socket if the listener isn't picking it up.
321 next if $out_sock->{write_buf_size};
322 $out_sock->write("$line\n");
330 # indent-tabs-mode: nil
337 mogstored -- MogileFS storage daemon
341 This is the MogileFS storage daemon, which is just an HTTP server that
342 supports PUT, DELETE, etc. It's actually a wrapper around L<Perlbal>,
343 doing all the proper Perlbal config for you.
345 In addition, it monitors disk usage, I/O activity, etc, which are
346 checked from the L<MogileFS tracker|mogilefsd>.
350 Brad Fitzpatrick E<lt>brad@danga.comE<gt>
352 Mark Smith E<lt>junior@danga.comE<gt>
354 Jonathan Steinert E<lt>jsteinert@sixapart.comE<gt>
360 =item PERLBAL_XS_HEADERS
362 If defined and 0, Perlbal::XS::HTTPHeaders will not be used, if
363 present. Otherwise, it will be enabled by default, if installed and
370 Copyright 2004, Danga Interactive
371 Copyright 2005-2006, Six Apart Ltd.
375 Same terms as Perl itself. Artistic/GPLv2, at your choosing.
379 L<MogileFS::Overview> -- high level overview of MogileFS
381 L<mogilefsd> -- MogileFS daemon
383 L<http://danga.com/mogilefs/>