PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/mogstored

http://github.com/mogilefs/MogileFS-Server
Perl | 376 lines | 279 code | 52 blank | 45 comment | 26 complexity | 9d46d9d6556d2d3522814c0669a87f1a MD5 | raw file
  1. #!/usr/bin/perl
  2. eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
  3. if 0; # not running under some shell
  4. #
  5. # MogileFS storage node daemon
  6. # (perlbal front-end)
  7. #
  8. # (c) 2004, Brad Fitzpatrick, <brad@danga.com>
  9. # (c) 2006-2007, Six Apart, Ltd.
  10. use strict;
  11. use lib 'lib';
  12. use Mogstored::HTTPServer;
  13. use IO::Socket::INET;
  14. use POSIX qw(WNOHANG);
  15. use Perlbal 1.73;
  16. use FindBin qw($Bin $RealScript);
  17. use Mogstored::HTTPServer::Perlbal;
  18. use Mogstored::HTTPServer::Lighttpd;
  19. use Mogstored::HTTPServer::None;
  20. use Mogstored::HTTPServer::Apache;
  21. use Mogstored::HTTPServer::Nginx;
  22. use Mogstored::SideChannelListener;
  23. use Mogstored::SideChannelClient;
  24. my $selfexe = "$Bin/$RealScript";
  25. # State:
  26. my %on_death; # pid -> subref (to run when pid dies)
  27. my %devnum_to_device; # mogile device number (eg. 'dev1' would be '1') -> os device path (eg. '/dev/rd0')
  28. my %osdevnum_to_device; # os device number (fetched via stat(file)[0]) -> os device path (ec. '/dev/rd0')
  29. my %iostat_listeners; # fd => SideChannel client: clients interested in iostat data.
  30. my $iostat_available = 1; # bool: iostat working. assume working to start.
  31. my ($iostat_pipe_r, $iostat_pipe_w); # pipes for talking to iostat process
  32. # Config:
  33. my $opt_skipconfig;
  34. my $opt_daemonize;
  35. my $opt_config;
  36. my $opt_iostat = 1; # default to on now
  37. my $max_conns = 10000;
  38. my $http_listen = "0.0.0.0:7500";
  39. my $mgmt_listen = "0.0.0.0:7501";
  40. my $docroot = "/var/mogdata";
  41. my $default_config = "/etc/mogilefs/mogstored.conf";
  42. my $server = $ENV{MOGSTORED_SERVER_TYPE} || "perlbal";
  43. my $serverbin = "";
  44. my $pidfile = undef;
  45. # Rename binary in process list to make init scripts saner
  46. $0 = "mogstored";
  47. my %config_opts = (
  48. 'iostat' => \$opt_iostat,
  49. 's|skipconfig' => \$opt_skipconfig,
  50. 'daemonize|d' => \$opt_daemonize,
  51. 'config=s' => \$opt_config,
  52. 'httplisten=s' => \$http_listen,
  53. 'mgmtlisten=s' => \$mgmt_listen,
  54. 'docroot=s' => \$docroot,
  55. 'maxconns=i' => \$max_conns,
  56. 'server=s' => \$server,
  57. 'serverbin=s' => \$serverbin,
  58. 'pidfile=s' => \$pidfile,
  59. );
  60. usage() unless Getopt::Long::GetOptions(%config_opts);
  61. die "Unknown server type. Valid options: --server={perlbal,lighttpd,apache,nginx,none}"
  62. unless $server =~ /^perlbal|lighttpd|apache|nginx|none$/;
  63. $opt_config = $default_config if ! $opt_config && -e $default_config;
  64. load_config_file($opt_config => \%config_opts) if $opt_config && !$opt_skipconfig;
  65. # initialize basic required Perlbal machinery, for any HTTP server
  66. my $perlbal_init = qq{
  67. CREATE SERVICE mogstored
  68. SET role = web_server
  69. SET docroot = $docroot
  70. # don't listen... this is just a stub service.
  71. CREATE SERVICE mgmt
  72. SET role = management
  73. ENABLE mgmt
  74. };
  75. $perlbal_init .= "\nSERVER pidfile = $pidfile" if defined($pidfile);
  76. Perlbal::run_manage_commands($perlbal_init , sub { print STDERR "$_[0]\n"; });
  77. # start HTTP server
  78. my $httpsrv_class = "Mogstored::HTTPServer::" . ucfirst($server);
  79. my $httpsrv = $httpsrv_class->new(
  80. listen => $http_listen,
  81. docroot => $docroot,
  82. maxconns => $max_conns,
  83. bin => $serverbin,
  84. );
  85. # Configure Perlbal HTTP listener after daemonization since it can create a
  86. # kqueue on *BSD. kqueue descriptors are automatically invalidated on fork(),
  87. # making them unusable after daemonize. For non-Perlbal, starting the
  88. # server before daemonization improves error reporting as daemonization
  89. # redirects stdout/stderr to /dev/null.
  90. $httpsrv->start if $server ne "perlbal";
  91. if ($opt_daemonize) {
  92. $httpsrv->pre_daemonize;
  93. Perlbal::daemonize();
  94. } else {
  95. print "Running.\n";
  96. }
  97. # It is now safe for Perlbal to create a kqueue
  98. $httpsrv->start if $server eq "perlbal";
  99. $httpsrv->post_daemonize;
  100. # kill our children processes on exit:
  101. my $parent_pid = $$;
  102. $SIG{TERM} = $SIG{INT} = sub {
  103. return unless $$ == $parent_pid; # don't let this be inherited
  104. kill 'TERM', grep { $_ } keys %on_death;
  105. POSIX::_exit(0);
  106. };
  107. setup_iostat_pipes();
  108. start_disk_usage_process();
  109. start_iostat_process() if $opt_iostat;
  110. harvest_dead_children(); # every 2 seconds, it reschedules itself
  111. setup_sidechannel_listener();
  112. # now start the main loop
  113. Perlbal::run();
  114. ############################################################################
  115. sub usage {
  116. my $note = shift;
  117. $note = $note ? "NOTE: $note\n\n" : "";
  118. die "${note}Usage: mogstored [OPTS]
  119. OPTS:
  120. --daemonize -d Daemonize
  121. --config=<file> Set config file (default is /etc/mogilefs/mogstored.conf)
  122. --httplisten=<ip:port> IP/Port HTTP server listens on
  123. --mgmtlisten=<ip:port> IP/Port management/sidechannel listens on
  124. --docroot=<path> Docroot above device mount points. Defaults to /var/mogdata
  125. --maxconns=<number> Number of simultaneous clients to serve. Default 10000
  126. ";
  127. }
  128. # accessor for SideChannelClient:
  129. sub Mogstored::iostat_available {
  130. return $iostat_available;
  131. }
  132. sub load_config_file {
  133. my ($conffile, $opts) = @_;
  134. # parse the mogstored config file, which is just lines of comments and
  135. # "key = value" lines, where keys are just the same as command line
  136. # options.
  137. die "Config file $opt_config doesn't exist.\n" unless -e $conffile;
  138. open my $fh, $conffile or die "Couldn't open config file for reading: $!";
  139. while (<$fh>) {
  140. s/\#.*//;
  141. next unless /\S/;
  142. if (/SERVER max_connect/i || /CREATE SERVICE/i) {
  143. 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.");
  144. }
  145. die "Unknown config syntax: $_\n" unless /^\s*(\w+)\s*=\s*(.+?)\s*$/;
  146. my ($key, $val) = ($1, $2);
  147. my $dest;
  148. foreach my $ck (keys %$opts) {
  149. next unless $ck =~ /^$key\b/;
  150. $dest = $opts->{$ck};
  151. }
  152. die "Unknown config setting: $key\n" unless $dest;
  153. $$dest = $val;
  154. }
  155. }
  156. sub harvest_dead_children {
  157. my $dead = waitpid(-1, WNOHANG);
  158. if ($dead > 0) {
  159. my $code = delete $on_death{$dead};
  160. $code->() if $code;
  161. }
  162. Danga::Socket->AddTimer(2, \&harvest_dead_children);
  163. }
  164. sub Mogstored::on_pid_death {
  165. my ($class, $pid, $code) = @_;
  166. $on_death{$pid} = $code;
  167. }
  168. # returns $pid of child, if parent, else runs child.
  169. sub start_disk_usage_process {
  170. my $child = fork;
  171. unless (defined $child) {
  172. Perlbal::log('crit', "Fork error creating disk usage tracking process");
  173. return undef;
  174. }
  175. # if we're the parent.
  176. if ($child) {
  177. $on_death{$child} = sub {
  178. start_disk_usage_process(); # start a new one
  179. };
  180. return $child;
  181. }
  182. require Mogstored::ChildProcess::DiskUsage;
  183. my $class = "Mogstored::ChildProcess::DiskUsage";
  184. $class->pre_exec_init;
  185. $class->exec;
  186. }
  187. sub Mogstored::iostat_subscribe {
  188. my ($class, $sock) = @_;
  189. $iostat_listeners{fileno($sock->sock)} = $sock;
  190. }
  191. sub Mogstored::iostat_unsubscribe {
  192. my ($class, $sock) = @_;
  193. my $fdno = fileno($sock->sock);
  194. return unless defined $fdno;
  195. delete $iostat_listeners{$fdno};
  196. }
  197. # to be honest, I have no clue why this exists. I just had to move it
  198. # around for multi-server refactoring, and I felt better not
  199. # understanding it but preserving than killing it. in particular, why
  200. # is this "graceful"? (gets called from SideChannelClient's
  201. # die_gracefully)
  202. sub Mogstored::on_sidechannel_die_gracefully {
  203. if ($$ == $parent_pid) {
  204. kill 'TERM', grep { $_ } keys %on_death;
  205. }
  206. }
  207. sub setup_sidechannel_listener {
  208. Mogstored::SideChannelListener->new($mgmt_listen);
  209. }
  210. my $iostat_read_buf = "";
  211. sub setup_iostat_pipes {
  212. pipe ($iostat_pipe_r, $iostat_pipe_w);
  213. IO::Handle::blocking($iostat_pipe_r, 0);
  214. IO::Handle::blocking($iostat_pipe_w, 0);
  215. Danga::Socket->AddOtherFds(fileno($iostat_pipe_r), sub {
  216. read_from_iostat_child();
  217. });
  218. }
  219. sub start_iostat_process {
  220. my $pid = fork;
  221. unless (defined $pid) {
  222. warn "Fork for iostat failed: $!";
  223. return;
  224. }
  225. if ($pid) {
  226. # Parent
  227. $on_death{$pid} = sub {
  228. # Try a final read from data on the socket.
  229. # Note that this doesn't internally loop... so it might miss data.
  230. read_from_iostat_child();
  231. # Kill any buffer so partial reads don't hurt us later.
  232. drain_iostat_child_pipe();
  233. start_iostat_process();
  234. };
  235. return;
  236. }
  237. require Mogstored::ChildProcess::IOStat;
  238. my $class = "Mogstored::ChildProcess::IOStat";
  239. $class->pre_exec_init;
  240. $class->exec;
  241. }
  242. sub Mogstored::get_iostat_writer_pipe { $iostat_pipe_w }
  243. sub drain_iostat_child_pipe {
  244. my $data;
  245. while (1) {
  246. last unless sysread($iostat_pipe_r, $data, 10240) > 0;
  247. }
  248. $iostat_read_buf = '';
  249. }
  250. # (runs in parent event-loop process)
  251. sub read_from_iostat_child {
  252. my $data;
  253. my $rv = sysread($iostat_pipe_r, $data, 10240);
  254. return unless $rv && $rv > 0;
  255. $iostat_read_buf .= $data;
  256. # only write complete lines to sockets (in case for some reason we get
  257. # a partial read and child process dies...)
  258. while ($iostat_read_buf =~ s/(.+)\r?\n//) {
  259. my $line = $1;
  260. foreach my $out_sock (values %iostat_listeners) {
  261. # where $line will be like "dev53\t53.23" or a "." to signal end of a group of devices.
  262. # stop writing to the socket if the listener isn't picking it up.
  263. next if $out_sock->{write_buf_size};
  264. $out_sock->write("$line\n");
  265. }
  266. }
  267. }
  268. # Local Variables:
  269. # mode: perl
  270. # c-basic-indent: 4
  271. # indent-tabs-mode: nil
  272. # End:
  273. __END__
  274. =head1 NAME
  275. mogstored -- MogileFS storage daemon
  276. =head1 USAGE
  277. This is the MogileFS storage daemon, which is just an HTTP server that
  278. supports PUT, DELETE, etc. It's actually a wrapper around L<Perlbal>,
  279. doing all the proper Perlbal config for you.
  280. In addition, it monitors disk usage, I/O activity, etc, which are
  281. checked from the L<MogileFS tracker|mogilefsd>.
  282. =head1 AUTHORS
  283. Brad Fitzpatrick E<lt>brad@danga.comE<gt>
  284. Mark Smith E<lt>junior@danga.comE<gt>
  285. Jonathan Steinert E<lt>jsteinert@sixapart.comE<gt>
  286. =head1 ENVIRONMENT
  287. =over 4
  288. =item PERLBAL_XS_HEADERS
  289. If defined and 0, Perlbal::XS::HTTPHeaders will not be used, if
  290. present. Otherwise, it will be enabled by default, if installed and
  291. loadable.
  292. =back
  293. =head1 COPYRIGHT
  294. Copyright 2004, Danga Interactive
  295. Copyright 2005-2006, Six Apart Ltd.
  296. =head1 LICENSE
  297. Same terms as Perl itself. Artistic/GPLv2, at your choosing.
  298. =head1 SEE ALSO
  299. L<MogileFS::Overview> -- high level overview of MogileFS
  300. L<mogilefsd> -- MogileFS daemon
  301. L<http://danga.com/mogilefs/>