User Tools

Site Tools


App service start-stop web tool

“One day coding” mini-application for manage and monitoring application server.
In practice average count of connection for one application is 150-300 users.
Total count of corporate application servers more 20 with more 70 business critical application.

The web application is written because this kind of application server (1C) has a property crash time by time :-/

For install depends:

apt-get -y install libev-perl
apt-get -y install libcrypt-passwdmd5-perl
apt-get -y install libio-socket-socks-perl 
apt-get -y install libapache-htpasswd-perl 
apt-get -y install libjs-prettify 
wget http://wiki.unix7.org/_media/dist/jessi/libmojolicious-perl_7.43_dfsg-1_all.deb
dpkg -i libmojolicious-perl_7.43_dfsg-1_all.deb

For install the app:

wget http://wiki.unix7.org/_media/onecsw/onecsw_0.01-1_all.deb
dpkg -i onecsw_0.01-1_all.deb 

For srv1cv83 you must configure:

/etc/onecsw/onecsw.conf
{
"loglevel":"debug",
"service":"srv1cv83"
}

Screen

Main code

onecsw.pl

onecsw.pl
#!/usr/bin/env perl
 
#------------------
#--- CONTROLLER ---
#------------------
 
package OneCsw::Model;
 
use strict;
use warnings;
use Mojo::Util qw(dumper);
use File::Basename qw(basename dirname);
use POSIX;
 
sub new {
    my ($class, $app) = @_;
    my $self = {
        app => $app,
    };
    bless $self, $class;
    return $self;
}
 
sub app {
    return shift->{app};
}
 
sub systemComm {
    my ($self, $comm) = @_;
    return undef unless $comm;
    open HR, "$comm |" or return undef;
    my $out; 
    while (my $str = <HR>) {
        $out .= $str;
    };
    return $out;
}
 
sub serviceStatus {
    my $self = shift;
    my $name = $self->app->config("service");
    return undef unless $name;
    my $out = $self->systemComm("systemctl status $name 2>&1") || '';
    return 'up' if $out =~ m/Active: active/;
    return 'down' if $out =~ m/Active: inactive/;
    return undef;
}
 
sub serviceStart {
    my $self = shift;
    my $name = $self->app->config("service");
    return undef unless $name;
    my $out = $self->systemComm("systemctl start $name 2>&1") || '';
    $self->app->log->debug("--- serviceStart $name");
    my $s = $self->serviceStatus($name) || '';
    return 1 if $s eq 'up';
    return undef;
}
 
sub serviceStop {
    my $self = shift;
    my $name = $self->app->config("service");
    return undef unless $name;
     my $out = $self->systemComm("systemctl stop $name 2>&1") || '';
    $self->app->log->debug("--- serviceStop $name");
    $self->killAll;
    $self->removepid;
    my $s = $self->serviceStatus($name) || '';
    return 1 if $s eq 'down';
    return undef;
}
 
sub killAll {
    my $self = shift;
    my $list = $self->listproc;
    return undef unless scalar @{$list};
    kill 'INT', @{$list}; sleep 1; kill 'KILL', @{$list};
    $self->app->log->debug("--- killAll: send signame to process list");
}
 
sub removepid {
    my $self = shift;
    my $name = $self->app->config("service");
    return undef unless $name;
    my $pidfile = "/var/run/$name.pid";
    unlink "$pidfile" if -w (dirname($pidfile) && -r $pidfile);
    $self->app->log->debug("--- removepid: try remove pid file $pidfile");
}
 
sub listproc {
    my $self = shift;
    my $dir = "/proc";
    opendir (my $ph, $dir);
    my @list;
    while (my $name = readdir $ph) {
        next unless $name =~ /^\d+/;
        open (my $fh, "<:raw", "$dir/$name/cmdline");
        next unless $fh;
        my $buf;
        eval { read($fh, $buf, 1048576); };
        next if $@;
        close $fh;
        my (@argv) = split(/\0/s, $buf);
        my $procname = $argv[0] || undef;
        next unless $procname;
        next unless $procname =~ /1C/;
        push @list, $name+0;
    }
    @list = sort { $a <=> $b } @list;
    return \@list;
}
 
sub cmdline {
    my ($self, $pid) = @_;
    return undef unless $pid;
    open (my $fh, "<:raw", "/proc/$pid/cmdline");
    return undef unless $fh;
    read($fh, my $buf, 1048576);
    close $fh;
    my @argv = split(/\0/s, $buf);
    return \@argv if @argv;
    return undef;
}
 
 
sub statm {
    my ($self, $pid) = @_;
    return undef unless $pid;
    my $dir = "/proc";
    open (my $fh, "<:raw", "$dir/$pid/statm");
    return undef unless $fh;
    read($fh, my $buf, 1048576);
    close $fh;
    my %statm;
    ($statm{size}, $statm{rss}, $statm{shared}, $statm{text}, $statm{lib}, $statm{data}, $statm{dt}) = split(/ /s, $buf);
    return \%statm;;
}
 
sub loadavg {
    my $self = shift;
    open (my $fh, "<:raw", "/proc/loadavg");
    return undef unless $fh;
    read($fh, my $buf, 1048576);
    close $fh;
    my %loadavg;
    ($loadavg{p1m}, $loadavg{p5m}, $loadavg{p15m}) = split(/ /s, $buf);
    return \%loadavg;;
}
 
sub cpucount {
    my $self = shift;
    open (my $fh, "<:raw", "/proc/cpuinfo");
    return undef unless $fh;
    my $num;
    while (my $line = <$fh>) { $num++ if $line =~/^processor/; }
    close $fh;
    return $num;
}
 
sub meminfo {
    my $self = shift;
    open (my $fh, "<:raw", "/proc/meminfo");
    return undef unless $fh;
    my %mem;
    my $mem1 = 0;
    while (my $line = <$fh>) { 
        chomp $line;
	($mem{free}) = $line =~ /MemFree:\s+(\d+)/ if $line =~ /MemFree/;
	($mem{total}) = $line =~ /MemTotal:\s+(\d+)/ if $line =~ /MemTotal/;
    }
    close $fh;
    print $mem1;
    return \%mem;
}
 
1;
 
package OneCsw::Controller;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::Util qw(md5_sum dumper quote encode);
use Mojo::JSON qw(encode_json decode_json);
use Apache::Htpasswd;
use File::Basename qw(fileparse);
 
sub hello {
    my $self = shift;
    $self->render(template => 'hello');
}
 
#--- controller::agent ---
 
sub sessionAuth {
    return 1 if shift->session('username');
    return undef;
}
 
sub checkPassword {
    my ($self, $username, $password) = @_;
 
    return undef unless $username;
    return undef unless $password;
 
    my $passwdFile = $self->app->config('pwdfile');
    do { 
        $self->app->log->error("Cannot read password file '$passwdFile'");
        return undef;
    } unless -r $passwdFile;
    my $result = undef;
    eval {
        my $ht = Apache::Htpasswd->new({ passwdFile => $passwdFile, ReadOnly => 1 });
        $result = $ht->htCheckPassword($username, $password);
    };
    do { $self->app->log->debug("Auth module error: $@"); return undef; } if $@;
 
    return 1 if $result;
    $self->app->log->info("Bad auth from ".$self->tx->remote_address);
    return undef;
}
 
sub login {
    my $self = shift;
 
    return $self->redirect_to('/hello') if $self->sessionAuth;
 
    my $username = $self->req->param('username') || undef;
    my $password = $self->req->param('password') || undef;
 
    my $auth =  $self->checkPassword($username, $password);
    if ($auth) {
        $self->session(username => $username);
        return $self->redirect_to('/hello');
    }
 
    $self->render(template => 'login', req => $self->req);
}
 
sub onecForm {
    my $self = shift;
    $self->render(template => 'onecForm', req => $self->req);
}
 
sub logout {
    my $self = shift;
    $self->sessionDeAuth;
    $self->render(template => 'login', req => $self->req);
}
 
sub sessionDeAuth {
    return 1 if shift->session(expires => 1);
    return undef;
}
 
 
1;
#-----------
#--- APP ---
#-----------
package OneCsw;
 
use utf8;
use strict;
use warnings;
use Mojo::Base 'Mojolicious';
 
sub startup {
    my $self = shift;
}
 
1;
#------------
#--- MAIN ---
#------------
use strict;
use warnings;
use utf8;
 
use POSIX qw(setuid setgid);
use Mojo::Server::Prefork;
use Mojo::IOLoop::Subprocess;
use Mojo::Util qw(monkey_patch b64_encode b64_decode md5_sum getopt dumper);
use Sys::Hostname qw(hostname);
use File::Basename qw(basename dirname);
use Apache::Htpasswd;
use Cwd qw(getcwd abs_path);
 
 
my $appfile = abs_path(__FILE__);
my $appname = basename($appfile, ".pl");
$0 = $appfile;
 
getopt
    'h|help' => \my $help,
    '4|ipv4listen=s' => \my $ipv4listen,
    '6|ipv6listen=s' => \my $ipv6listen,
    'c|config=s' => \my $conffile,
    'p|pwdfile=s' => \my $pwdfile,
    'd|confdir=s' => \my $confdir,
    'l|logfile=s' => \my $logfile,
    'i|pidfile=s' => \my $pidfile,
    'v|loglevel=s' => \my $loglevel,
    'f|nofork' => \my $nofork,
    'u|user=s' => \my $user,
    'g|group=s' => \my $group;
 
 
if ($help) {
    print qq(
Usage: app [OPTIONS]
 
Options
    -h | --help                      This help
    -4 | --ipv4listen=address:port      Listen address and port, defaults 127.0.0.1:5100
    -6 | --ipv6listen=[address]:port    Listen address and port, defaults [::1]:5100
 
    -c | --config=path    Path to config file
    -p | --pwdfile=path   Path to user password file
    -l | --logfile=path   Path to log file
    -i | --pidfile=path   Path to process ID file
    -v | --loglevel=level  Verbose level: debug, info, warn, error, fatal
    -u | --user=user      System owner of process
    -g | --group=group    System group 
    -f | --nofork         Dont fork process, for debugging
All path option override option from configuration file
 
    )."\n";
    exit 0;
}
 
my $server = Mojo::Server::Prefork->new;
my $app = $server->build_app('OneCsw');
$app = $app->controller_class('OneCsw::Controller');
 
$app->config(
    hostname => hostname,
    listenIPv4 => $ipv4listen || "0.0.0.0",
    listenIPv6 => $ipv6listen || "[::]",
    listenPort => "1008",
    service => "srv1cv82",
    pwdfile => $pwdfile || "@APP_CONFDIR@/$appname.pw",
    pidfile => $pidfile || "@APP_RUNDIR@/$appname.pid",
    logfile => $logfile || "@APP_LOGDIR@/$appname.log",
    conffile => $conffile || "@APP_CONFDIR@/$appname.conf",
    maxrequestsize => 1024*1024*1024,
    tlscert => "@APP_CONFDIR@/$appname.crt",
    tlskey => "@APP_CONFDIR@/$appname.key",
    appuser => $user || "@APP_USER@",
    appgroup => $group || "@APP_GROUP@",
    mode => 'production',
    loglevel => $loglevel || 'info',
    libdir => '@APP_LIBDIR@',
);
 
$conffile = $app->config('conffile');
do {
    $app->log->debug("Load configuration from $conffile ");
    my $config = $app->plugin( 'JSONConfig', { file => $conffile } );
} if -r $conffile;
 
$app->log->level($app->config('loglevel'));
 
my $tlscert = $app->config('tlscert');
my $tlskey = $app->config('tlskey');
$confdir = $app->config('confdir');
my $rundir = dirname ($app->config('pidfile'));
my $logdir = dirname ($app->config('logfile'));
$pwdfile = $app->config('pwdfile');
 
###do { print "Error: Cannot read from data direcory $confdir\n"; exit 1; } unless -r $confdir;
do { print "Error: Cannot write to run direcory $rundir\n"; exit 1; } unless -w $rundir;
do { print "Error: Cannot write to log direcory $logdir\n"; exit 1; } unless -w $logdir;
 
do { print "Error: Cannot read TLS certificate $tlscert\n"; exit 1; } unless -r $tlscert;
do { print "Error: Cannot read TLS key $tlskey\n"; exit 1; } unless -r $tlskey;
do { print "Error: Cannot read password file $pwdfile\n"; exit 1; } unless -r $pwdfile;
 
my $appUser = $app->config('appuser');
my $appGroup = $app->config('appgroup');
 
my $appUID = getpwnam($appUser);
my $appGID = getgrnam($appGroup);
 
#do { print "System user $appUser not exist.\n"; exit 1; } unless $appUID;
#do { print "System group $appGroup not exist.\n"; exit 1; } unless $appGID;
 
 
$app->moniker($appname);
$app->mode($app->config("mode"));
$app->secrets(['6d578e43ba88260e0375a1a35fd7954b']);
 
$app->helper(
    model => sub {
        state $model = OneCsw::Model->new(
            $app
        );
    }
);
 
$app->hook(before_dispatch => sub {
        my $c = shift;
 
        my $remoteIPaddr = $c->tx->remote_address;
        my $method = $c->req->method;
 
        my $base = $c->req->url->base->to_string;
        my $path = $c->req->url->path->to_string;
 
	my $username = $c->session('username') || 'undef';
 
        my $loglevel = $c->app->log->level;
        unless ($loglevel eq 'debug') {
            my $url = $c->req->url->to_abs->to_string;
            $c->app->log->info("$method $url from $remoteIPaddr as $username");
        }
        if ($loglevel eq 'debug') {
            my $url = $c->req->url->to_abs->to_string;
            $c->app->log->debug("$method $url from $remoteIPaddr as $username");
        }
});
 
$app->static->paths->[0] = $app->config('libdir').'/public';
$app->renderer->paths->[0] = $app->config('libdir').'/templs';
 
my $r = $app->routes;
 
$r->add_condition(
    auth => sub {
        my ($route, $c, $captures, $hash) = @_;
        return 1 if $c->sessionAuth;
        return undef;
    }
);
 
$r->any('/login')->to('Controller#login');
$r->any('/hello')->over('auth')->to('Controller#hello');
$r->any('/onec/service')->over('auth')->to('Controller#onecForm');
 
$r->any('/logout')->to('Controller#logout');
$r->any('/')->over('auth')->to('Controller#hello');
 
 
$app->helper('reply.not_found' => sub { 
        my $c = shift; 
        return $c->redirect_to('/login') unless $c->sessionAuth; 
        $c->render(template => 'not_found.production');
});
 
my $tlsParam = '';
$tlsParam .= '?';
$tlsParam .= 'cert='.$tlscert;
$tlsParam .= '&key='.$tlskey;
 
my $listenPort = $app->config('listenPort');
my $listenIPv4 = $app->config('listenIPv4');
my $listenIPv6 = $app->config('listenIPv6');
 
#$server->listen([
#    "http://$listenIPv4:$listenPort$tlsParam",
#]);
 
$server->listen([
    "https://$listenIPv4:$listenPort$tlsParam",
]);
 
 
 
$server->pid_file($app->config('pidfile'));
 
$server->heartbeat_interval(3);
$server->heartbeat_timeout(60);
 
unless ($nofork) {
    my $pid = fork;
    if ($pid == 0) {
        setuid($appUID) if $appUID;
        setgid($appGID) if $appGID;
        $app->log(Mojo::Log->new( path => $app->config('logfile') ));
        open (my $STDOUT2, '>&', STDOUT); open (STDOUT, '>>', '/dev/null');
        open (my $STDERR2, '>&', STDERR); open (STDERR, '>>', '/dev/null');
        chdir($confdir);
        local $SIG{HUP} = sub {
                $app->log->info('Catch HUP signal'); 
                $app->log(Mojo::Log->new(
                        path => $app->config('logfile'),
                        level => $app->config('loglevel'),
                ));
        };
        $server->run;
    }
} else {
    setuid($appUID) if $appUID;
    setgid($appGID) if $appGID;
    $server->run;
}
#EOF

onecForm.html.ep

onecForm.html.ep
%#
%# $Id$
%#
% layout 'default';
% title 'oneC Sw';
% use Mojo::Util qw(dumper);
 
% use utf8;
% use strict;
 
% sub _roundup { 
%    my $num = shift;
%    return int($num/1024+0.5);
% }
 
% my $m = $self->app->model;
 
% my $action = $self->req->param('action') || 'undef';
% $m->serviceStop if $action eq 'stop';
% $m->serviceStart if $action eq 'start';
 
<div class="text-center">
    <h5>
        Process list
        <a href="/onec/service"><i class="fi-refresh" style="font-size:1.3rem;"></i></a>
    </h5>
 
% my $totalData = 0;
% my $meminfo = $m->meminfo;
% my $loadavg = $m->loadavg;
 
    <div class="row column">
      all mem:<%= _roundup($meminfo->{'total'}) %>M, 
      free mem:<%= _roundup($meminfo->{'free'}) %>M
    </div>
 
    <div class="row column">
       avg load 15,5,1m: <%= $loadavg->{'p15m'} %>, <%= $loadavg->{'p5m'} %>, <%= $loadavg->{'p1m'} %> per <%= $m->cpucount %> cpu
    </div>
 
</div>
 
<table id="table" class="display" >
    <thead>
      <tr>
        <td>#</td>
        <td>pid</td>
        <td>tmem</td>
        <td>rss</td>
        <td>data</td>
        <td>name</td>
      </tr>
    </thead>
 
% my $list = $m->listproc;
 
    <tbody>
% my $num = 1;
% foreach my $pid (@{$list}) {
 
%     my $procname = @{$m->cmdline($pid)}[0];
%     my $statm = $m->statm($pid);
 
%     my $size = $statm->{'size'} || 0;
%     my $shared = $statm->{'shared'} || 0;
%     my $data = $statm->{'data'} || 0;
%     my $rss = $statm->{'rss'} || 0;
        <tr>
            <td><%= $num %></td>
            <td><%= $pid %></td>
            <td><%= _roundup($size) %></td>
            <td><%= _roundup($rss) %></td>
            <td><%= _roundup($data) %></td>
            <td><%= $procname  %></td>
        </tr>
%     $num++;
% }
    </tbody>
 
</table>
 
<script>
        $.extend(true, $.fn.dataTable.defaults, {
            "searching": true,
            "ordering": true,
            "pageLength": 50,
            "lengthMenu": [ [10, 25, 50,100, -1], [10, 25, 50,100, "All"] ],
            fixedColumns:   {
                leftColumns: 2,
            },
            "language": {
                "search": "",
		"decimal": ".",
		"thousands": ",",
		"lengthMenu": "_MENU_",
            },
        } );
 
        $(document).ready(function() {
            $('#table').DataTable();
        });
</script>
%#EOF

default.html.ep

default.html.ep
<!doctype html>
<html class="no-js" lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title><%= title %></title>
 
        <link rel="stylesheet" href="/icons/foundation-icons.css">
 
        <link rel="stylesheet" href="/css/foundation-float.min.css">
        <link rel="stylesheet" href="/css/datatables.min.css"/>
        <link rel="stylesheet" href="/css/app.css">
 
        <script src="/js/jquery.min.js"></script>
        <script src="/js/datatables.min.js"></script>
        <script src="/js/foundation.min.js"></script>
 
    </head>
 
    <body>
 
        <div class="title-bar" data-responsive-toggle="topbar-menu" data-hide-for="medium">
            <button class="menu-icon" type="button" data-toggle="topbar-menu"></button>
            <div class="title-bar-title">OneC Sw 0.01</div>
        </div>
 
 
        <div class="top-bar" id="topbar-menu">
            <div class="top-bar-left">
                <ul class="dropdown menu" data-dropdown-menu>
                    <li class="menu-text show-for-medium">oneC Sw 0.01</li>
                    <li><a href="/onec/service">Status&nbsp;<i class="fi-zoom-in"></i></a></li>
                    <li><a href="#" data-open="modal-action-start">Start&nbsp;<i class="fi-play"></i></a></li>
                    <li><a href="#" data-open="modal-action-stop">Stop&nbsp;<i class="fi-x"></i></a></li>
                    <li><a href="/logout">[<%= $self->session('username') %>]</a></li>
                </ul>
            </div>
            <div class="top-bar-center">
            </div>
        </div>
 
        <div class="reveal" id="modal-action-stop" data-reveal>
            <div class="text-center">
                <h5>Do stop service?</h5>
            </div>
            <form accept-charset="UTF-8" action="/onec/service" method="get">
                <input type="hidden" name="action" value="stop" />
 
                <p class="text-center">
                    <button type="submit" class="button alert">Yes, I agree</button>
                    <button class="button" data-close="modal-action-stop" type="button">No, Escape</button>
                </p>
            </form>
            <button class="close-button" data-close="modal-action-stop" type="button">&times;</button>
        </div>
 
        <div class="reveal" id="modal-action-start" data-reveal>
            <div class="text-center">
                <h5>Do start service again?</h5>
            </div>
            <form accept-charset="UTF-8" action="/onec/service" method="get">
                <input type="hidden" name="action" value="start" />
 
                <p class="text-center">
                    <button type="submit" class="button alert">Yes, I agree</button>
                    <button class="button" data-close="modal-action-start" type="button">No, Escape</button>
                </p>
            </form>
            <button class="close-button" data-close="modal-action-start" type="button">&times;</button>
        </div>
 
        <div class="row">&nbsp;</div>
        <div class="row">
<!- end of head template ->
 
<%= content %>
 
<!- begin of tail template ->
            </div>
        </div>
        <hr/>
        <div class="row">
            <p class="text-center">Made by <a href="http://wiki.unix7.org">Borodin Oleg</a></p>
        </div>
        <script src="/js/app.js"></script>
    </body>
</html>
<!- end of tail template ->
<!- EOF ->