PNG  IHDR pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F@8N ' p @8N@8}' p '#@8N@8N pQ9p!i~}|6-ӪG` VP.@*j>[ K^<֐Z]@8N'KQ<Q(`s" 'hgpKB`R@Dqj '  'P$a ( `D$Na L?u80e J,K˷NI'0eݷ(NI'؀ 2ipIIKp`:O'`ʤxB8Ѥx Ѥx $ $P6 :vRNb 'p,>NB 'P]-->P T+*^h& p '‰a ‰ (ĵt#u33;Nt̵'ޯ; [3W ~]0KH1q@8]O2]3*̧7# *p>us p _6]/}-4|t'|Smx= DoʾM×M_8!)6lq':l7!|4} '\ne t!=hnLn (~Dn\+‰_4k)0e@OhZ`F `.m1} 'vp{F`ON7Srx 'D˸nV`><;yMx!IS钦OM)Ե٥x 'DSD6bS8!" ODz#R >S8!7ّxEh0m$MIPHi$IvS8IN$I p$O8I,sk&I)$IN$Hi$I^Ah.p$MIN$IR8I·N "IF9Ah0m$MIN$IR8IN$I 3jIU;kO$ɳN$+ q.x* tEXtComment

Viewing File: /opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/lib/lvestats_server.py

# -*- coding: utf-8 -*-
#
# Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2019 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

import io
import logging
import os
import signal
import sys
import time
import traceback
import psutil

from lvestats.lib.commons.func import reboot_lock
from lvestats.lib import config, dbengine
from lvestats.lib.commons.func import LVEVersionError, get_lve_version
from lvestats.lib.commons.logsetup import setup_logging

DEV_NULL = '/dev/null'
PIDFILE = ""


class EnvironmentException(Exception):
    def __init__(self, message):
        super().__init__()
        self.message = message


def get_process_pid():
    """
    Check if lvestats already running
    :return int|None: None - if no process found;  pid - if some lvestats-server found
    """
    if PIDFILE and os.path.isfile(PIDFILE):
        try:
            with open(PIDFILE, 'r', encoding='utf-8') as f:
                pid = int(f.read().strip())
                os.kill(pid, 0)  # try to send some
                return pid
        except (IOError, OSError):
            return None  # No pidfile or no process found


def stop_server():
    exit_code = 0

    def kill_process(_pid: int, _signal: signal.Signals):
        """Kill process by pid by sending a specific signal to it"""
        try:
            os.kill(_pid, _signal)
        except (OSError, ProcessLookupError):
            log.info("Process with pid '%d' is already dead", _pid)

    def on_sigusr2(_proc: psutil.Process):
        """callback for psutil.wait_procs()"""
        log.info("Signal 'SIGUSR2' sent to child process: %s", _proc)

    def on_terminate(_proc: psutil.Process):
        """callback for psutil.wait_procs()"""
        log.info("Signal 'SIGTERM' sent to child process: %s", _proc)

    log = setup_logging({}, caller_name='stop_server')
    pid = get_process_pid()
    if pid is None:
        exit_code = 1
    else:
        process = psutil.Process(pid)
        childs = process.children(recursive=True)
        with reboot_lock(timeout=60 * 10):
            for child in childs:
                # There may be stored absent childs, so we need to kill
                # only existing processes
                if psutil.pid_exists(child.pid):
                    kill_process(child.pid, signal.SIGUSR2)
            _, alive = psutil.wait_procs(childs, timeout=3, callback=on_sigusr2)
            for p in alive:
                kill_process(p.pid, signal.SIGTERM)
            _, alive = psutil.wait_procs(childs, timeout=3, callback=on_terminate)
            kill_process(pid, signal.SIGTERM)
            time.sleep(0.15)
            for child in alive:
                kill_process(child.pid, signal.SIGKILL)
    return exit_code


def setup_default_exception_hook():
    def hook(_type, _ex, _trace):
        sio = io.StringIO()
        traceback.print_tb(_trace, file=sio)
        msg = f"Uncaught exception {_type}\nmessage='%s'\n:%s"
        logging.error(msg, str(_ex), sio.getvalue())
        sio.close()
        sys.__excepthook__(_type, _ex, _trace)

    sys.excepthook = hook


def run_main(cnf, singleprocess, plugins, profiling_log, times_):
    from lvestats import main  # pylint: disable=import-outside-toplevel,redefined-outer-name
    main.main(cnf, singleprocess, plugins, profiling_log, times_)


def sigterm_handler(signum, frame):
    log = logging.getLogger('sigterm_handler')
    log.info('SIGTERM handler. Shutting Down.')
    os._exit(0)


def _check_db_connection(cnf, log_):
    """
    Check whether database connection can be
    established and db schema is ok.
    Raises exception if database is broken.
    :type cnf: dict
    :type log_: logging.Logger
    :raises: EnvironmentException
    """
    log_.debug('Check for running SQL server')
    try:
        engine = dbengine.make_db_engine(cnf)
        engine.execute("SELECT 1;")
        validation = dbengine.validate_database(engine)
        if validation['column_error'] or validation['table_error']:
            sys.exit(1)
    except Exception as ex:
        msg = "Error occurred during connecting to SQL server:"
        log_.fatal(str(ex))
        log_.exception(ex)

        raise EnvironmentException(f"\n{msg}\n{ex}\n") from ex


def _check_valid_lve_version(log_):
    """
    Check for possible misconfiguration
    if cpu speed is reported as 0 with /proc/cpuinfo
    and lve version <= 4. Raises exception, if so.
    :type log_: logging.Logger
    :raises: EnvironmentException
    """
    log_.debug('Check for valid LVE version')

    lve_version = get_lve_version()
    if lve_version <= 4:
        msg = "LVE version <= 4"
        log_.fatal('LVE version <= 4. Please, update.')
        raise EnvironmentException(f"\n{msg}\n")


def _check_running_process(log_):
    """
    Check if another instance of lve-stats is
    already running. Raises exception, if so.
    :param log_: logging.Logger
    :raises: EnvironmentException
    """
    log_.debug('Check for running lvestats-server')
    rc = get_process_pid()
    if rc:
        msg = f"Lvestats-server already running with pid {rc}. Exiting"
        log_.warning(msg)

        raise EnvironmentException(msg)


def _is_environment_ok(cnf, log_):
    """
    Checks whether system environment works fine.
    Return True if ok, False otherwise.
    :type cnf: dict
    :type log_: logging.Logger
    :rtype: bool
    """
    try:
        _check_running_process(log_)
        _check_db_connection(cnf, log_)
        _check_valid_lve_version(log_)
    except EnvironmentException as ex:
        sys.stderr.write(ex.message)
        sys.stderr.flush()
        return False

    return True


def daemonize(cnf, singleprocess, plugins, profiling_log, times):
    def fork():
        try:
            return os.fork()
        except OSError as e:
            raise RuntimeError(f"{e.strerror} [{e.errno}]") from e

    setup_logging(cnf, console_level=logging.CRITICAL)
    setup_default_exception_hook()

    log_ = logging.getLogger('server')
    # check for issues with environment
    if not _is_environment_ok(cnf, log_):
        sys.exit(1)

    log_.debug('Starting server')

    pid = fork()
    if pid:
        log_.debug('First fork, pid=%d', pid)
        time.sleep(0.2)
        os._exit(0)

    os.setsid()
    signal.signal(signal.SIGTERM, sigterm_handler)

    pid = fork()
    if pid:
        log_.debug('Second fork, pid=%d', pid)
        if PIDFILE:
            log_.debug('Writing pid to file %s', PIDFILE)
            with open(PIDFILE, 'w', encoding='utf-8') as pidfile:
                pidfile.write(str(pid))
        # exit parent process
        log_.debug('Child daemon fork ok')

        os._exit(0)

    os.nice(10)
    os.setpgrp()
    os.chdir('/')
    previous_umask = os.umask(0)

    sys.stdout.flush()
    sys.stderr.flush()
    # pylint: disable=consider-using-with,unspecified-encoding
    si = open(DEV_NULL, 'r')
    so = open(DEV_NULL, 'a+')  # read + write allowed
    se = open(DEV_NULL, 'a+')  # read + write allowed
    # pylint: enable=consider-using-with,unspecified-encoding
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())

    log_.info('Starting main() in daemon')

    os.umask(previous_umask)
    run_main(cnf, singleprocess, plugins, profiling_log, times)


def process_opts(cnf, _opts, _times):
    if _opts.action == 'stop':
        sys.exit(stop_server())
    elif _opts.action == 'restart':  # stop and start
        stop_server()
        daemonize(cnf, _opts.singleprocess, _opts.plugins, _opts.profiling_log, _times)
    elif _opts.action == 'start':
        if _opts.nodaemon:
            debug_mode = logging.DEBUG if _opts.debug_mode else logging.INFO
            setup_logging(cnf, console_level=debug_mode, file_level=debug_mode)
            setup_default_exception_hook()
            run_main(cnf, _opts.singleprocess, _opts.plugins, _opts.profiling_log, _times)
        else:
            daemonize(cnf, _opts.singleprocess, _opts.plugins, _opts.profiling_log, _times)


def main(opts):
    if not opts.pidfile:
        print("--pidfile should be specified")
        sys.exit(1)
    else:
        global PIDFILE
        PIDFILE = opts.pidfile

    times = None
    if opts.times:
        times = int(opts.times)

    cfg = None
    try:
        cfg = config.read_config()
    except config.ConfigError as ce:
        ce.log_and_exit()

    try:
        process_opts(cfg, opts, times)
    except LVEVersionError as lve_error:
        config.log.error(str(lve_error))
        sys.exit(1)
Back to Directory=ceiIENDB`